dolibarr 23.0.3
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 $year -= 1;
4519 }
4520 }
4521
4522 return array_reverse($result);
4523 }
4524
4525
4526 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4537 public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4538 {
4539 // phpcs:enable
4540 global $user;
4541
4542 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4543 if ($mode == 'bynumber') {
4544 $sql .= ", count(DISTINCT f.rowid)";
4545 }
4546 $sql .= ", sum(d.total_ht) as total_ht";
4547 $sql .= " FROM ".$this->db->prefix()."facturedet as d, ".$this->db->prefix()."facture as f, ".$this->db->prefix()."societe as s";
4548 if ($filteronproducttype >= 0) {
4549 $sql .= ", ".$this->db->prefix()."product as p";
4550 }
4551 if (!$user->hasRight('societe', 'client', 'voir')) {
4552 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4553 }
4554 $sql .= " WHERE f.rowid = d.fk_facture";
4555 if ($this->id > 0) {
4556 $sql .= " AND d.fk_product = ".((int) $this->id);
4557 } else {
4558 $sql .= " AND d.fk_product > 0";
4559 }
4560 if ($filteronproducttype >= 0) {
4561 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4562 }
4563 $sql .= " AND f.fk_soc = s.rowid";
4564 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4565 if (!$user->hasRight('societe', 'client', 'voir')) {
4566 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4567 }
4568 if ($socid > 0) {
4569 $sql .= " AND f.fk_soc = $socid";
4570 }
4571 $sql .= $morefilter;
4572 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4573 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4574
4575 return $this->_get_stats($sql, $mode, $year);
4576 }
4577
4578
4579 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4590 public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4591 {
4592 // phpcs:enable
4593 global $user;
4594
4595 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4596 if ($mode == 'bynumber') {
4597 $sql .= ", count(DISTINCT f.rowid)";
4598 }
4599 $sql .= ", sum(d.total_ht) as total_ht";
4600 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as d, ".$this->db->prefix()."facture_fourn as f, ".$this->db->prefix()."societe as s";
4601 if ($filteronproducttype >= 0) {
4602 $sql .= ", ".$this->db->prefix()."product as p";
4603 }
4604 if (!$user->hasRight('societe', 'client', 'voir')) {
4605 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4606 }
4607 $sql .= " WHERE f.rowid = d.fk_facture_fourn";
4608 if ($this->id > 0) {
4609 $sql .= " AND d.fk_product = ".((int) $this->id);
4610 } else {
4611 $sql .= " AND d.fk_product > 0";
4612 }
4613 if ($filteronproducttype >= 0) {
4614 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4615 }
4616 $sql .= " AND f.fk_soc = s.rowid";
4617 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4618 if (!$user->hasRight('societe', 'client', 'voir')) {
4619 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4620 }
4621 if ($socid > 0) {
4622 $sql .= " AND f.fk_soc = $socid";
4623 }
4624 $sql .= $morefilter;
4625 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4626 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4627
4628 return $this->_get_stats($sql, $mode, $year);
4629 }
4630
4631 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4642 public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4643 {
4644 // phpcs:enable
4645 global $user, $hookmanager;
4646
4647 $sql = "SELECT sum(d.qty) as qty, date_format(p.datep, '%Y%m')";
4648 if ($mode == 'bynumber') {
4649 $sql .= ", count(DISTINCT p.rowid)";
4650 }
4651 $sql .= ", sum(d.total_ht) as total_ht";
4652 $sql .= " FROM ".$this->db->prefix()."propaldet as d, ".$this->db->prefix()."propal as p, ".$this->db->prefix()."societe as s";
4653 if ($filteronproducttype >= 0) {
4654 $sql .= ", ".$this->db->prefix()."product as prod";
4655 }
4656 if (!$user->hasRight('societe', 'client', 'voir')) {
4657 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4658 }
4659 $sql .= " WHERE p.rowid = d.fk_propal";
4660 if ($this->id > 0) {
4661 $sql .= " AND d.fk_product = ".((int) $this->id);
4662 } else {
4663 $sql .= " AND d.fk_product > 0";
4664 }
4665 if ($filteronproducttype >= 0) {
4666 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4667 }
4668 $sql .= " AND p.fk_soc = s.rowid";
4669 $sql .= " AND p.entity IN (".getEntity('propal').")";
4670 if (!$user->hasRight('societe', 'client', 'voir')) {
4671 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4672 }
4673 if ($socid > 0) {
4674 $sql .= " AND p.fk_soc = ".((int) $socid);
4675 }
4676 $sql .= $morefilter;
4677 $parameters = array('socid' => $user->socid);
4678 $hookmanager->executeHooks('productGetNbPropal', $parameters, $this); // Note that $action and $object may have been modified by hook
4679 $sql .= $hookmanager->resPrint;
4680 $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
4681 $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
4682 return $this->_get_stats($sql, $mode, $year);
4683 }
4684
4685 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4696 public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4697 {
4698 // phpcs:enable
4699 global $user;
4700
4701 $sql = "SELECT sum(d.qty) as qty, date_format(p.date_valid, '%Y%m')";
4702 if ($mode == 'bynumber') {
4703 $sql .= ", count(DISTINCT p.rowid)";
4704 }
4705 $sql .= ", sum(d.total_ht) as total_ht";
4706 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as d, ".$this->db->prefix()."supplier_proposal as p, ".$this->db->prefix()."societe as s";
4707 if ($filteronproducttype >= 0) {
4708 $sql .= ", ".$this->db->prefix()."product as prod";
4709 }
4710 if (!$user->hasRight('societe', 'client', 'voir')) {
4711 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4712 }
4713 $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
4714 if ($this->id > 0) {
4715 $sql .= " AND d.fk_product = ".((int) $this->id);
4716 } else {
4717 $sql .= " AND d.fk_product > 0";
4718 }
4719 if ($filteronproducttype >= 0) {
4720 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4721 }
4722 $sql .= " AND p.fk_soc = s.rowid";
4723 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
4724 if (!$user->hasRight('societe', 'client', 'voir')) {
4725 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4726 }
4727 if ($socid > 0) {
4728 $sql .= " AND p.fk_soc = ".((int) $socid);
4729 }
4730 $sql .= $morefilter;
4731 $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
4732 $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
4733
4734 return $this->_get_stats($sql, $mode, $year);
4735 }
4736
4737 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4748 public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4749 {
4750 // phpcs:enable
4751 global $user, $hookmanager;
4752
4753 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4754 if ($mode == 'bynumber') {
4755 $sql .= ", count(DISTINCT c.rowid)";
4756 }
4757 $sql .= ", sum(d.total_ht) as total_ht";
4758 $sql .= " FROM ".$this->db->prefix()."commandedet as d, ".$this->db->prefix()."commande as c, ".$this->db->prefix()."societe as s";
4759 if ($filteronproducttype >= 0) {
4760 $sql .= ", ".$this->db->prefix()."product as p";
4761 }
4762 if (!$user->hasRight('societe', 'client', 'voir')) {
4763 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4764 }
4765 $sql .= " WHERE c.rowid = d.fk_commande";
4766 if ($this->id > 0) {
4767 $sql .= " AND d.fk_product = ".((int) $this->id);
4768 } else {
4769 $sql .= " AND d.fk_product > 0";
4770 }
4771 if ($filteronproducttype >= 0) {
4772 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4773 }
4774 $sql .= " AND c.fk_soc = s.rowid";
4775 $sql .= " AND c.entity IN (".getEntity('commande').")";
4776 if (!$user->hasRight('societe', 'client', 'voir')) {
4777 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4778 }
4779 if ($socid > 0) {
4780 $sql .= " AND c.fk_soc = ".((int) $socid);
4781 }
4782 $sql .= $morefilter;
4783 $parameters = array('socid' => $user->socid);
4784 $hookmanager->executeHooks('productGetNbOrder', $parameters, $this); // Note that $action and $object may have been modified by hook
4785 $sql .= $hookmanager->resPrint;
4786 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4787 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4788
4789 return $this->_get_stats($sql, $mode, $year);
4790 }
4791
4792 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4803 public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4804 {
4805 // phpcs:enable
4806 global $user;
4807
4808 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4809 if ($mode == 'bynumber') {
4810 $sql .= ", count(DISTINCT c.rowid)";
4811 }
4812 $sql .= ", sum(d.total_ht) as total_ht";
4813 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as d, ".$this->db->prefix()."commande_fournisseur as c, ".$this->db->prefix()."societe as s";
4814 if ($filteronproducttype >= 0) {
4815 $sql .= ", ".$this->db->prefix()."product as p";
4816 }
4817 if (!$user->hasRight('societe', 'client', 'voir')) {
4818 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4819 }
4820 $sql .= " WHERE c.rowid = d.fk_commande";
4821 if ($this->id > 0) {
4822 $sql .= " AND d.fk_product = ".((int) $this->id);
4823 } else {
4824 $sql .= " AND d.fk_product > 0";
4825 }
4826 if ($filteronproducttype >= 0) {
4827 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4828 }
4829 $sql .= " AND c.fk_soc = s.rowid";
4830 $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
4831 if (!$user->hasRight('societe', 'client', 'voir')) {
4832 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4833 }
4834 if ($socid > 0) {
4835 $sql .= " AND c.fk_soc = ".((int) $socid);
4836 }
4837 $sql .= $morefilter;
4838 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4839 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4840
4841 return $this->_get_stats($sql, $mode, $year);
4842 }
4843
4844 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4855 public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4856 {
4857 // phpcs:enable
4858 global $user;
4859
4860 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_contrat, '%Y%m')";
4861 if ($mode == 'bynumber') {
4862 $sql .= ", count(DISTINCT c.rowid)";
4863 }
4864 $sql .= ", sum(d.total_ht) as total_ht";
4865 $sql .= " FROM ".$this->db->prefix()."contratdet as d, ".$this->db->prefix()."contrat as c, ".$this->db->prefix()."societe as s";
4866 if ($filteronproducttype >= 0) {
4867 $sql .= ", ".$this->db->prefix()."product as p";
4868 }
4869 if (!$user->hasRight('societe', 'client', 'voir')) {
4870 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4871 }
4872 $sql .= " WHERE c.entity IN (".getEntity('contract').")";
4873 $sql .= " AND c.rowid = d.fk_contrat";
4874
4875 if ($this->id > 0) {
4876 $sql .= " AND d.fk_product = ".((int) $this->id);
4877 } else {
4878 $sql .= " AND d.fk_product > 0";
4879 }
4880 if ($filteronproducttype >= 0) {
4881 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4882 }
4883 $sql .= " AND c.fk_soc = s.rowid";
4884
4885 if (!$user->hasRight('societe', 'client', 'voir')) {
4886 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4887 }
4888 if ($socid > 0) {
4889 $sql .= " AND c.fk_soc = ".((int) $socid);
4890 }
4891 $sql .= $morefilter;
4892 $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
4893 $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
4894
4895 return $this->_get_stats($sql, $mode, $year);
4896 }
4897
4898 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4909 public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4910 {
4911 // phpcs:enable
4912 global $user;
4913
4914 $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
4915 if ($mode == 'bynumber') {
4916 $sql .= ", count(DISTINCT d.rowid)";
4917 }
4918 $sql .= " FROM ".$this->db->prefix()."mrp_mo as d LEFT JOIN ".$this->db->prefix()."societe as s ON d.fk_soc = s.rowid";
4919 if ($filteronproducttype >= 0) {
4920 $sql .= ", ".$this->db->prefix()."product as p";
4921 }
4922 if (!$user->hasRight('societe', 'client', 'voir')) {
4923 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4924 }
4925
4926 $sql .= " WHERE d.entity IN (".getEntity('mo').")";
4927 $sql .= " AND d.status > 0";
4928
4929 if ($this->id > 0) {
4930 $sql .= " AND d.fk_product = ".((int) $this->id);
4931 } else {
4932 $sql .= " AND d.fk_product > 0";
4933 }
4934 if ($filteronproducttype >= 0) {
4935 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4936 }
4937
4938 if (!$user->hasRight('societe', 'client', 'voir')) {
4939 $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4940 }
4941 if ($socid > 0) {
4942 $sql .= " AND d.fk_soc = ".((int) $socid);
4943 }
4944 $sql .= $morefilter;
4945 $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
4946 $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
4947
4948 return $this->_get_stats($sql, $mode, $year);
4949 }
4950
4951 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4962 public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4963 {
4964 // phpcs:enable
4965 global $user;
4966
4967 // Clean parameters
4968 if (!is_numeric($id_pere)) {
4969 $id_pere = 0;
4970 }
4971 if (!is_numeric($id_fils)) {
4972 $id_fils = 0;
4973 }
4974 if (!is_numeric($incdec)) {
4975 $incdec = 0;
4976 }
4977
4978 $result = $this->del_sousproduit($id_pere, $id_fils);
4979 if ($result < 0) {
4980 return $result;
4981 }
4982
4983 // Check not already father of id_pere (to avoid father -> child -> father links)
4984 $sql = "SELECT fk_product_pere from ".$this->db->prefix()."product_association";
4985 $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere);
4986 if (!$this->db->query($sql)) {
4987 dol_print_error($this->db);
4988 return -1;
4989 } else {
4990 //Selection of the highest row
4991 $sql = "SELECT MAX(rang) as max_rank FROM ".$this->db->prefix()."product_association";
4992 $sql .= " WHERE fk_product_pere = ".((int) $id_pere);
4993 $resql = $this->db->query($sql);
4994 if ($resql) {
4995 $obj = $this->db->fetch_object($resql);
4996 $rank = $obj->max_rank + 1;
4997 //Addition of a product with the highest rank +1
4998 $sql = "INSERT INTO ".$this->db->prefix()."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)";
4999 $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".price2num($qty, 'MS').", ".((int) $incdec).", ".((int) $rank).")";
5000 if (! $this->db->query($sql)) {
5001 dol_print_error($this->db);
5002 return -1;
5003 } else {
5004 if (!$notrigger) {
5005 // Call trigger
5006 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_ADD', $user);
5007 if ($result < 0) {
5008 $this->error = $this->db->lasterror();
5009 dol_syslog(get_class($this).'::addSubproduct error='.$this->error, LOG_ERR);
5010 return -1;
5011 }
5012 }
5013 // End call triggers
5014
5015 return 1;
5016 }
5017 } else {
5018 dol_print_error($this->db);
5019 return -1;
5020 }
5021 }
5022 }
5023
5024 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5035 public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
5036 {
5037 // phpcs:enable
5038 global $user;
5039
5040 // Clean parameters
5041 if (!is_numeric($id_pere)) {
5042 $id_pere = 0;
5043 }
5044 if (!is_numeric($id_fils)) {
5045 $id_fils = 0;
5046 }
5047 if (!is_numeric($incdec)) {
5048 $incdec = 1;
5049 }
5050 if (!is_numeric($qty)) {
5051 $qty = 1;
5052 }
5053
5054 $sql = 'UPDATE '.$this->db->prefix().'product_association SET ';
5055 $sql .= 'qty = '.price2num($qty, 'MS');
5056 $sql .= ',incdec = '.((int) $incdec);
5057 $sql .= ' WHERE fk_product_pere = '.((int) $id_pere).' AND fk_product_fils = '.((int) $id_fils);
5058
5059 if (!$this->db->query($sql)) {
5060 dol_print_error($this->db);
5061 return -1;
5062 } else {
5063 if (!$notrigger) {
5064 // Call trigger
5065 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_UPDATE', $user);
5066 if ($result < 0) {
5067 $this->error = $this->db->lasterror();
5068 dol_syslog(get_class($this).'::updateSubproduct error='.$this->error, LOG_ERR);
5069 return -1;
5070 }
5071 // End call triggers
5072 }
5073
5074 return 1;
5075 }
5076 }
5077
5078 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5087 public function del_sousproduit($fk_parent, $fk_child, $notrigger = 0)
5088 {
5089 // phpcs:enable
5090 global $user;
5091
5092 if (!is_numeric($fk_parent)) {
5093 $fk_parent = 0;
5094 }
5095 if (!is_numeric($fk_child)) {
5096 $fk_child = 0;
5097 }
5098
5099 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
5100 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
5101 $sql .= " AND fk_product_fils = ".((int) $fk_child);
5102
5103 dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
5104 if (!$this->db->query($sql)) {
5105 dol_print_error($this->db);
5106 return -1;
5107 }
5108
5109 // Updated ranks so that none are missing
5110 $sqlrank = "SELECT rowid, rang FROM ".$this->db->prefix()."product_association";
5111 $sqlrank .= " WHERE fk_product_pere = ".((int) $fk_parent);
5112 $sqlrank .= " ORDER BY rang";
5113 $resqlrank = $this->db->query($sqlrank);
5114 if ($resqlrank) {
5115 $cpt = 0;
5116 while ($objrank = $this->db->fetch_object($resqlrank)) {
5117 $cpt++;
5118 $sql = "UPDATE ".$this->db->prefix()."product_association";
5119 $sql .= " SET rang = ".((int) $cpt);
5120 $sql .= " WHERE rowid = ".((int) $objrank->rowid);
5121 if (! $this->db->query($sql)) {
5122 dol_print_error($this->db);
5123 return -1;
5124 }
5125 }
5126 }
5127
5128 if (!$notrigger) {
5129 // Call trigger
5130 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_DELETE', $user);
5131 if ($result < 0) {
5132 $this->error = $this->db->lasterror();
5133 dol_syslog(get_class($this).'::delSubproduct error='.$this->error, LOG_ERR);
5134 return -1;
5135 }
5136 // End call triggers
5137 }
5138
5139 return 1;
5140 }
5141
5142 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5150 public function is_sousproduit($fk_parent, $fk_child)
5151 {
5152 // phpcs:enable
5153 $sql = "SELECT fk_product_pere, qty, incdec";
5154 $sql .= " FROM ".$this->db->prefix()."product_association";
5155 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
5156 $sql .= " AND fk_product_fils = ".((int) $fk_child);
5157
5158 $result = $this->db->query($sql);
5159 if ($result) {
5160 $num = $this->db->num_rows($result);
5161
5162 if ($num > 0) {
5163 $obj = $this->db->fetch_object($result);
5164
5165 $this->is_sousproduit_qty = $obj->qty;
5166 $this->is_sousproduit_incdec = $obj->incdec;
5167
5168 return 1;
5169 } else {
5170 return 0;
5171 }
5172 } else {
5173 dol_print_error($this->db);
5174 return -1;
5175 }
5176 }
5177
5178
5179 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5190 public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
5191 {
5192 // phpcs:enable
5193 global $conf;
5194
5195 $now = dol_now();
5196
5197 dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
5198
5199 // Clean parameters
5200 $quantity = price2num($quantity, 'MS');
5201
5202 if ($ref_fourn) {
5203 // Check if ref is not already used
5204 $sql = "SELECT rowid, fk_product";
5205 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5206 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
5207 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
5208 $sql .= " AND fk_product <> ".((int) $this->id);
5209 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
5210
5211 $resql = $this->db->query($sql);
5212 if ($resql) {
5213 $obj = $this->db->fetch_object($resql);
5214 if ($obj) {
5215 // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
5216 $this->product_id_already_linked = $obj->fk_product;
5217 return -3;
5218 }
5219 $this->db->free($resql);
5220 }
5221 }
5222
5223 $sql = "SELECT rowid";
5224 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5225 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
5226 if ($ref_fourn) {
5227 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
5228 } else {
5229 $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
5230 }
5231 $sql .= " AND quantity = ".((float) $quantity);
5232 $sql .= " AND fk_product = ".((int) $this->id);
5233 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
5234
5235 $resql = $this->db->query($sql);
5236 if ($resql) {
5237 $obj = $this->db->fetch_object($resql);
5238
5239 // The reference supplier does not exist, we create it for this product.
5240 if (empty($obj)) {
5241 $sql = "INSERT INTO ".$this->db->prefix()."product_fournisseur_price(";
5242 $sql .= "datec";
5243 $sql .= ", entity";
5244 $sql .= ", fk_product";
5245 $sql .= ", fk_soc";
5246 $sql .= ", ref_fourn";
5247 $sql .= ", quantity";
5248 $sql .= ", fk_user";
5249 $sql .= ", tva_tx";
5250 $sql .= ") VALUES (";
5251 $sql .= "'".$this->db->idate($now)."'";
5252 $sql .= ", ".((int) $conf->entity);
5253 $sql .= ", ".((int) $this->id);
5254 $sql .= ", ".((int) $id_fourn);
5255 $sql .= ", '".$this->db->escape($ref_fourn)."'";
5256 $sql .= ", ".((float) $quantity);
5257 $sql .= ", ".((int) $user->id);
5258 $sql .= ", 0";
5259 $sql .= ")";
5260
5261 if ($this->db->query($sql)) {
5262 $this->product_fourn_price_id = $this->db->last_insert_id($this->db->prefix()."product_fournisseur_price");
5263 return 1;
5264 } else {
5265 $this->error = $this->db->lasterror();
5266 return -1;
5267 }
5268 } else {
5269 // If the supplier price already exists for this product and quantity
5270 $this->product_fourn_price_id = $obj->rowid;
5271 return 0;
5272 }
5273 } else {
5274 $this->error = $this->db->lasterror();
5275 return -2;
5276 }
5277 }
5278
5279
5280 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5286 public function list_suppliers()
5287 {
5288 // phpcs:enable
5289 global $conf;
5290
5291 $list = array();
5292
5293 $sql = "SELECT DISTINCT p.fk_soc";
5294 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as p";
5295 $sql .= " WHERE p.fk_product = ".((int) $this->id);
5296 $sql .= " AND p.entity = ".((int) $conf->entity);
5297
5298 $result = $this->db->query($sql);
5299 if ($result) {
5300 $num = $this->db->num_rows($result);
5301 $i = 0;
5302 while ($i < $num) {
5303 $obj = $this->db->fetch_object($result);
5304 $list[$i] = $obj->fk_soc;
5305 $i++;
5306 }
5307 }
5308
5309 return $list;
5310 }
5311
5312 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5320 public function clone_price($fromId, $toId)
5321 {
5322 // phpcs:enable
5323 global $user;
5324
5325 $now = dol_now();
5326
5327 $this->db->begin();
5328
5329 // prices
5330 $sql = "INSERT INTO ".$this->db->prefix()."product_price (";
5331 $sql .= " entity";
5332 $sql .= ", fk_product";
5333 $sql .= ", date_price";
5334 $sql .= ", price_level";
5335 $sql .= ", price";
5336 $sql .= ", price_ttc";
5337 $sql .= ", price_min";
5338 $sql .= ", price_min_ttc";
5339 $sql .= ", price_base_type";
5340 $sql .= ", price_label";
5341 $sql .= ", default_vat_code";
5342 $sql .= ", tva_tx";
5343 $sql .= ", recuperableonly";
5344 $sql .= ", localtax1_tx";
5345 $sql .= ", localtax1_type";
5346 $sql .= ", localtax2_tx";
5347 $sql .= ", localtax2_type";
5348 $sql .= ", fk_user_author";
5349 $sql .= ", tosell";
5350 $sql .= ", price_by_qty";
5351 $sql .= ", fk_price_expression";
5352 $sql .= ", fk_multicurrency";
5353 $sql .= ", multicurrency_code";
5354 $sql .= ", multicurrency_tx";
5355 $sql .= ", multicurrency_price";
5356 $sql .= ", multicurrency_price_ttc";
5357 $sql .= ")";
5358 $sql .= " SELECT";
5359 $sql .= " entity";
5360 $sql .= ", ".((int) $toId);
5361 $sql .= ", '".$this->db->idate($now)."'";
5362 $sql .= ", price_level";
5363 $sql .= ", price";
5364 $sql .= ", price_ttc";
5365 $sql .= ", price_min";
5366 $sql .= ", price_min_ttc";
5367 $sql .= ", price_base_type";
5368 $sql .= ", price_label";
5369 $sql .= ", default_vat_code";
5370 $sql .= ", tva_tx";
5371 $sql .= ", recuperableonly";
5372 $sql .= ", localtax1_tx";
5373 $sql .= ", localtax1_type";
5374 $sql .= ", localtax2_tx";
5375 $sql .= ", localtax2_type";
5376 $sql .= ", ".((int) $user->id);
5377 $sql .= ", tosell";
5378 $sql .= ", price_by_qty";
5379 $sql .= ", fk_price_expression";
5380 $sql .= ", fk_multicurrency";
5381 $sql .= ", multicurrency_code";
5382 $sql .= ", multicurrency_tx";
5383 $sql .= ", multicurrency_price";
5384 $sql .= ", multicurrency_price_ttc";
5385 $sql .= " FROM ".$this->db->prefix()."product_price ps";
5386 $sql .= " WHERE fk_product = ".((int) $fromId);
5387 $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)";
5388 $sql .= " ORDER BY date_price DESC";
5389
5390 dol_syslog(__METHOD__, LOG_DEBUG);
5391 $resql = $this->db->query($sql);
5392 if (!$resql) {
5393 $this->db->rollback();
5394 return -1;
5395 }
5396
5397 $this->db->commit();
5398 return 1;
5399 }
5400
5401 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5409 public function clone_associations($fromId, $toId)
5410 {
5411 // phpcs:enable
5412 $this->db->begin();
5413
5414 $sql = 'INSERT INTO '.$this->db->prefix().'product_association (fk_product_pere, fk_product_fils, qty, incdec)';
5415 $sql .= " SELECT ".((int) $toId).", fk_product_fils, qty, incdec FROM ".$this->db->prefix()."product_association";
5416 $sql .= " WHERE fk_product_pere = ".((int) $fromId);
5417
5418 dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
5419 if (!$this->db->query($sql)) {
5420 $this->db->rollback();
5421 return -1;
5422 }
5423
5424 $this->db->commit();
5425 return 1;
5426 }
5427
5428 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5436 public function clone_fournisseurs($fromId, $toId)
5437 {
5438 // phpcs:enable
5439 $this->db->begin();
5440
5441 $now = dol_now();
5442
5443 // les fournisseurs
5444 /*$sql = "INSERT ".$this->db->prefix()."product_fournisseur ("
5445 . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
5446 . " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, ref_fourn, fk_user_author"
5447 . " FROM ".$this->db->prefix()."product_fournisseur"
5448 . " WHERE fk_product = ".((int) $fromId);
5449
5450 if ( ! $this->db->query($sql ) )
5451 {
5452 $this->db->rollback();
5453 return -1;
5454 }*/
5455
5456 // les prix de fournisseurs.
5457 $sql = "INSERT ".$this->db->prefix()."product_fournisseur_price (";
5458 $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user, tva_tx)";
5459 $sql .= " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, price, quantity, fk_user, tva_tx";
5460 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5461 $sql .= " WHERE fk_product = ".((int) $fromId);
5462
5463 dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
5464 $resql = $this->db->query($sql);
5465 if (!$resql) {
5466 $this->db->rollback();
5467 return -1;
5468 } else {
5469 $this->db->commit();
5470 return 1;
5471 }
5472 }
5473
5474 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5487 public function fetch_prod_arbo($prod, $compl_path = '', $multiply = 1, $level = 1, $id_parent = 0, $ignore_stock_load = 0)
5488 {
5489 // phpcs:enable
5490 $tmpproduct = null;
5491
5492 if ($multiply < 1) {
5493 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);
5494 $multiply = 1;
5495 }
5496
5497 //var_dump($prod);
5498 foreach ($prod as $id_product => $desc_pere) { // $id_product is 0 (first call starting with root top) or an id of a sub_product
5499 if (is_array($desc_pere)) { // If desc_pere is an array, this means it's a child
5500 $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
5501 $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
5502 $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
5503 $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
5504 $incdec = (!empty($desc_pere[4]) ? $desc_pere[4] : 0);
5505
5506 //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
5507 if (is_null($tmpproduct)) {
5508 $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
5509 }
5510 $tmpproduct->fetch($id); // Load product to get ->ref
5511
5512 if (empty($ignore_stock_load) && ($tmpproduct->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
5513 $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
5514 }
5515
5516 $this->res[] = array(
5517 'id' => $id, // Id product
5518 'id_parent' => $id_parent,
5519 'ref' => $tmpproduct->ref, // Ref product
5520 'nb' => $nb, // Nb of units that compose parent product
5521 'nb_total' => $nb * $multiply, // Nb of units for all nb of product
5522 'stock' => $tmpproduct->stock_reel, // Stock
5523 'stock_alert' => $tmpproduct->seuil_stock_alerte, // Stock alert
5524 'label' => $label,
5525 'fullpath' => $compl_path.$label, // Label
5526 'type' => $type, // Nb of units that compose parent product
5527 'desiredstock' => $tmpproduct->desiredstock,
5528 'level' => $level,
5529 'incdec' => $incdec,
5530 'entity' => $tmpproduct->entity
5531 );
5532
5533 // Recursive call if there child has children of its own
5534 if (isset($desc_pere['childs']) && is_array($desc_pere['childs'])) {
5535 if (!is_int($desc_pere[1] * $multiply)) {
5536 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);
5537 }
5538
5539 //print 'YYY We go down for '.$desc_pere[3]." -> \n";
5540 $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", (int) ceil($desc_pere[1] * $multiply), $level + 1, $id, $ignore_stock_load);
5541 }
5542 }
5543 }
5544 }
5545
5546 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5555 public function get_arbo_each_prod($multiply = 1, $ignore_stock_load = 0)
5556 {
5557 // phpcs:enable
5558 $this->res = array();
5559 if (isset($this->sousprods) && is_array($this->sousprods)) {
5560 foreach ($this->sousprods as $prod_name => $desc_product) {
5561 if (is_array($desc_product)) {
5562 $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id, $ignore_stock_load); // This set $this->res
5563 }
5564 }
5565 }
5566 //var_dump($res);
5567 return $this->res;
5568 }
5569
5577 public function hasFatherOrChild($mode = 0)
5578 {
5579 $nb = 0;
5580
5581 $sql = "SELECT COUNT(pa.rowid) as nb";
5582 $sql .= " FROM ".$this->db->prefix()."product_association as pa";
5583 if ($mode == 0) {
5584 $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id)." OR pa.fk_product_pere = ".((int) $this->id);
5585 } elseif ($mode == -1) {
5586 $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)
5587 } elseif ($mode == 1) {
5588 $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)
5589 }
5590
5591 $resql = $this->db->query($sql);
5592 if ($resql) {
5593 $obj = $this->db->fetch_object($resql);
5594 if ($obj) {
5595 $nb = (int) $obj->nb;
5596 }
5597 } else {
5598 return -1;
5599 }
5600
5601 return $nb;
5602 }
5603
5609 public function hasVariants()
5610 {
5611 $nb = 0;
5612 if (!isModEnabled('variants')) {
5613 return $nb;
5614 }
5615 $sql = "SELECT count(rowid) as nb FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_parent = ".((int) $this->id);
5616 $sql .= " AND entity IN (".getEntity('product').")";
5617
5618 $resql = $this->db->query($sql);
5619 if ($resql) {
5620 $obj = $this->db->fetch_object($resql);
5621 if ($obj) {
5622 $nb = (int) $obj->nb;
5623 }
5624 }
5625
5626 return $nb;
5627 }
5628
5629
5635 public function isVariant()
5636 {
5637 if (isModEnabled('variants')) {
5638 $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_child = ".((int) $this->id)." AND entity IN (".getEntity('product').")";
5639
5640 $query = $this->db->query($sql);
5641
5642 if ($query) {
5643 if (!$this->db->num_rows($query)) {
5644 return false;
5645 }
5646 return true;
5647 } else {
5648 dol_print_error($this->db);
5649 return -1;
5650 }
5651 } else {
5652 return false;
5653 }
5654 }
5655
5662 public function getFather()
5663 {
5664 $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";
5665 $sql .= ", p.tosell as status, p.tobuy as status_buy";
5666 $sql .= " FROM ".$this->db->prefix()."product_association as pa,";
5667 $sql .= " ".$this->db->prefix()."product as p";
5668 $sql .= " WHERE p.rowid = pa.fk_product_pere";
5669 $sql .= " AND pa.fk_product_fils = ".((int) $this->id);
5670
5671 $res = $this->db->query($sql);
5672 if ($res) {
5673 $prods = array();
5674 while ($record = $this->db->fetch_array($res)) {
5675 // $record['id'] = $record['rowid'] = id of father
5676 $prods[$record['id']] = array();
5677 $prods[$record['id']]['id'] = $record['rowid'];
5678 $prods[$record['id']]['ref'] = $record['ref'];
5679 $prods[$record['id']]['label'] = $record['label'];
5680 $prods[$record['id']]['qty'] = $record['qty'];
5681 $prods[$record['id']]['incdec'] = $record['incdec'];
5682 $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
5683 $prods[$record['id']]['entity'] = $record['entity'];
5684 $prods[$record['id']]['status'] = $record['status'];
5685 $prods[$record['id']]['status_buy'] = $record['status_buy'];
5686 }
5687 return $prods;
5688 } else {
5689 dol_print_error($this->db);
5690 return -1;
5691 }
5692 }
5693
5694
5704 public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array())
5705 {
5706 if (empty($id)) {
5707 return array();
5708 }
5709
5710 $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
5711 $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
5712 $sql .= " pa.rowid as fk_association, pa.rang";
5713 $sql .= " FROM ".$this->db->prefix()."product as p,";
5714 $sql .= " ".$this->db->prefix()."product_association as pa";
5715 $sql .= " WHERE p.rowid = pa.fk_product_fils";
5716 $sql .= " AND pa.fk_product_pere = ".((int) $id);
5717 $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
5718 $sql .= " ORDER BY pa.rang";
5719
5720 dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG);
5721
5722 // Protection against infinite loop
5723 if ($level > 30) {
5724 return array();
5725 }
5726
5727 $res = $this->db->query($sql);
5728 if ($res) {
5729 $prods = array();
5730 if ($this->db->num_rows($res) > 0) {
5731 $parents[] = $id;
5732 }
5733
5734 while ($rec = $this->db->fetch_array($res)) {
5735 if (in_array($rec['id'], $parents)) {
5736 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);
5737 continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5738 }
5739
5740 $prods[$rec['rowid']] = array(
5741 0 => $rec['rowid'],
5742 1 => $rec['qty'],
5743 2 => $rec['fk_product_type'],
5744 3 => $this->db->escape($rec['label']),
5745 4 => $rec['incdec'],
5746 5 => $rec['ref'],
5747 6 => $rec['fk_association'],
5748 7 => $rec['rang']
5749 );
5750 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5751 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5752 if (empty($firstlevelonly)) {
5753 $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents);
5754 foreach ($listofchilds as $keyChild => $valueChild) {
5755 $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5756 }
5757 }
5758 }
5759
5760 return $prods;
5761 } else {
5762 dol_print_error($this->db);
5763 return -1;
5764 }
5765 }
5766
5767 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5774 public function get_sousproduits_arbo()
5775 {
5776 // phpcs:enable
5777 $parent = array();
5778
5779 foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5780 $parent[$this->label][$keyChild] = $valueChild;
5781 }
5782 foreach ($parent as $key => $value) { // key=label, value is array of children
5783 $this->sousprods[$key] = $value; // @phan-suppress-current-line PhanTypeMismatchProperty
5784 }
5785 }
5786
5794 public function getTooltipContentArray($params)
5795 {
5796 global $conf, $langs, $user;
5797
5798 $langs->loadLangs(array('products', 'other'));
5799
5800 $datas = array();
5801 $nofetch = !empty($params['nofetch']);
5802
5803 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5804 return ['optimize' => $langs->trans("ShowProduct")];
5805 }
5806
5807 // Does user has permission to read product/service
5808 $permissiontoreadproduct = 0;
5809 if ($this->type == self::TYPE_PRODUCT && $user->hasRight('product', 'read')) {
5810 $permissiontoreadproduct = 1;
5811 }
5812 if ($this->type == self::TYPE_SERVICE && $user->hasRight('service', 'read')) {
5813 $permissiontoreadproduct = 1;
5814 }
5815
5816 if (!empty($this->entity) && $permissiontoreadproduct) {
5817 $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, '1');
5818 if ($this->nbphoto > 0) {
5819 $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5820 }
5821 }
5822
5823 if ($this->isProduct()) {
5824 $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5825 } elseif ($this->isService()) {
5826 $datas['picto'] = img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5827 }
5828 if (isset($this->status) && isset($this->status_buy)) {
5829 $datas['status'] = ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5830 }
5831
5832 if (!empty($this->ref)) {
5833 $datas['ref'] = '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5834 }
5835 if (!empty($this->label)) {
5836 $datas['label'] = '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label.'<br>';
5837 }
5838
5839 if ($permissiontoreadproduct) {
5840 if (!empty($this->description) && getDolGlobalString('PRODUCT_SHOW_DESCRIPTION_IN_TOOLTIP')) {
5841 $datas['description'] = '<br><b>'.$langs->trans('ProductDescription').':</b> '.dolGetFirstLineOfText($this->description, 5);
5842 }
5843
5844 $datas['stockmanaged'] = "<br><b>".$langs->trans("StockableProduct").'</b>: '.yn($this->isStockManaged());
5845
5846 if ($this->isStockManaged()) {
5847 if (isModEnabled('productbatch')) {
5848 $langs->load("productbatch");
5849 $datas['batchstatus'] = "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5850 if ($this->status_batch) {
5851 $datas['batchdlc'] = "<br><b>".$langs->trans("BatchSellOrEatByMandatoryList", $langs->transnoentitiesnoconv("SellByDate"), $langs->transnoentitiesnoconv("EatByDate")).'</b>: '.$this->getSellOrEatByMandatoryLabel();
5852 }
5853 }
5854 }
5855 if (isModEnabled('barcode')) {
5856 $datas['barcode'] = '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5857 }
5858
5859 if ($this->isProduct()) {
5860 if ($this->weight) {
5861 $datas['weight'] = "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5862 }
5863 $labelsize = "";
5864 if ($this->length) {
5865 $labelsize .= "<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5866 }
5867 if ($this->width) {
5868 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5869 }
5870 if ($this->height) {
5871 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5872 }
5873 if ($labelsize) {
5874 $datas['size'] = "<br>".$labelsize;
5875 }
5876
5877 $labelsurfacevolume = "";
5878 if ($this->surface) {
5879 $labelsurfacevolume .= "<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5880 }
5881 if ($this->volume) {
5882 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5883 }
5884 if ($labelsurfacevolume) {
5885 $datas['surface'] = "<br>" . $labelsurfacevolume;
5886 }
5887 }
5888 if ($this->isService() && !empty($this->duration_value)) {
5889 // Duration
5890 $datas['duration'] = '<br><b>'.$langs->trans("Duration").':</b> '.$this->duration_value;
5891 if ($this->duration_value > 1) {
5892 $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"));
5893 } elseif ($this->duration_value > 0) {
5894 $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"));
5895 }
5896 $datas['duration'] .= (!empty($this->duration_unit) && isset($dur[$this->duration_unit]) ? "&nbsp;".$langs->trans($dur[$this->duration_unit]) : '');
5897 }
5898 if (empty($user->socid)) {
5899 if ($this->isStockManaged() && isset($this->pmp) && $this->pmp) {
5900 $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5901 }
5902
5903 if (isModEnabled('accounting')) {
5904 if ($this->status && isset($this->accountancy_code_sell)) {
5905 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5906 $selllabel = '<br>';
5907 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5908 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5909 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5910 $datas['accountancysell'] = $selllabel;
5911 }
5912 if ($this->status_buy && isset($this->accountancy_code_buy)) {
5913 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5914 $buylabel = '';
5915 if (empty($this->status)) {
5916 $buylabel .= '<br>';
5917 }
5918 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5919 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5920 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5921 $datas['accountancybuy'] = $buylabel;
5922 }
5923 }
5924 }
5925 // show categories for this record only in ajax to not overload lists
5926 if (isModEnabled('category') && !$nofetch) {
5927 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5928 $form = new Form($this->db);
5929 $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5930 }
5931 }
5932
5933 return $datas;
5934 }
5935
5949 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5950 {
5951 global $langs, $hookmanager;
5952
5953 include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5954
5955 $result = '';
5956
5957 $newref = (string) $this->ref;
5958 if ($maxlength) {
5959 $newref = dol_trunc($newref, $maxlength, 'middle');
5960 }
5961 $params = [
5962 'id' => $this->id,
5963 'objecttype' => ($this->type == 1 ? 'service' : 'product'),
5964 'option' => $option,
5965 'nofetch' => 1,
5966 ];
5967 $classfortooltip = 'classfortooltip';
5968 $dataparams = '';
5969 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5970 $classfortooltip = 'classforajaxtooltip';
5971 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5972 $label = '';
5973 } else {
5974 $label = implode($this->getTooltipContentArray($params));
5975 }
5976
5977 $linkclose = '';
5978 if (empty($notooltip)) {
5979 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5980 $label = $langs->trans("ShowProduct");
5981 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5982 }
5983 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5984 $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5985 } else {
5986 $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5987 }
5988
5989 if ($option == 'supplier' || $option == 'category') {
5990 $url = DOL_URL_ROOT.'/product/price_suppliers.php?id='.$this->id;
5991 } elseif ($option == 'stock') {
5992 $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5993 } elseif ($option == 'composition') {
5994 $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5995 } else {
5996 $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5997 }
5998
5999 if ($option !== 'nolink') {
6000 // Add param to save lastsearch_values or not
6001 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
6002 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
6003 $add_save_lastsearch_values = 1;
6004 }
6005 if ($add_save_lastsearch_values) {
6006 $url .= '&save_lastsearch_values=1';
6007 }
6008 }
6009
6010 $linkstart = '<a href="'.$url.'"';
6011 $linkstart .= $linkclose.'>';
6012 $linkend = '</a>';
6013
6014 $result .= $linkstart;
6015 if ($withpicto) {
6016 if ($this->isProduct()) {
6017 $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
6018 }
6019 if ($this->isService()) {
6020 $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
6021 }
6022 }
6023 $result .= '<span class="aaa">'.dol_escape_htmltag($newref).'</span>';
6024 $result .= $linkend;
6025 if ($withpicto != 2) {
6026 $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
6027 }
6028
6029 global $action;
6030 $hookmanager->initHooks(array('productdao'));
6031 $parameters = array('id' => $this->id, 'getnomurl' => &$result, 'label' => &$label);
6032 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
6033 if ($reshook > 0) {
6034 $result = $hookmanager->resPrint;
6035 } else {
6036 $result .= $hookmanager->resPrint;
6037 }
6038
6039 return $result;
6040 }
6041
6042
6053 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
6054 {
6055 global $langs;
6056
6057 $langs->load("products");
6058 $outputlangs->load("products");
6059
6060 // Positionne le modele sur le nom du modele a utiliser
6061 if (!dol_strlen($modele)) {
6062 $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
6063 }
6064
6065 $modelpath = "core/modules/product/doc/";
6066
6067 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
6068 }
6069
6077 public function getLibStatut($mode = 0, $type = 0)
6078 {
6079 switch ($type) {
6080 case 0:
6081 return $this->LibStatut($this->status, $mode, $type);
6082 case 1:
6083 return $this->LibStatut($this->status_buy, $mode, $type);
6084 case 2:
6085 return $this->LibStatut($this->status_batch, $mode, $type);
6086 default:
6087 //Simulate previous behavior but should return an error string
6088 return $this->LibStatut($this->status_buy, $mode, $type);
6089 }
6090 }
6091
6092 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6101 public function LibStatut($status, $mode = 0, $type = 0)
6102 {
6103 // phpcs:enable
6104 global $langs;
6105
6106 $labelStatus = $labelStatusShort = '';
6107
6108 $langs->load('products');
6109 if (isModEnabled('productbatch')) {
6110 $langs->load("productbatch");
6111 }
6112
6113 if ($type == 2) {
6114 switch ($mode) {
6115 case 0:
6116 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
6117 return dolGetStatus($label);
6118 case 1:
6119 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
6120 return dolGetStatus($label);
6121 case 2:
6122 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
6123 case 3:
6124 return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
6125 case 4:
6126 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
6127 case 5:
6128 return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
6129 default:
6130 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
6131 }
6132 }
6133
6134 $statuttrans = empty($status) ? 'status5' : 'status4';
6135
6136 if ($status == 0) {
6137 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
6138 if ($type == 0) {
6139 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
6140 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
6141 } elseif ($type == 1) {
6142 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
6143 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
6144 } elseif ($type == 2) {
6145 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
6146 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
6147 }
6148 } elseif ($status == 1) {
6149 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
6150 if ($type == 0) {
6151 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
6152 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
6153 } elseif ($type == 1) {
6154 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
6155 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
6156 } elseif ($type == 2) {
6157 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBatch');
6158 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBatchShort');
6159 }
6160 } elseif ($type == 2 && $status == 2) {
6161 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
6162 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
6163 }
6164
6165 if ($mode > 6) {
6166 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
6167 } else {
6168 return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
6169 }
6170 }
6171
6172
6178 public function getLibFinished()
6179 {
6180 global $langs;
6181
6182 $langs->load('products');
6183 $label = '';
6184
6185 if (isset($this->finished) && $this->finished >= 0) {
6186 $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
6187 $resql = $this->db->query($sql);
6188 if (!$resql) {
6189 $this->error = $this->db->error().' sql='.$sql;
6190 dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
6191 return -1;
6192 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
6193 $label = $langs->trans($res['label']);
6194 }
6195 $this->db->free($resql);
6196 }
6197
6198 return $label;
6199 }
6200
6201
6202 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6219 public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
6220 {
6221 // phpcs:enable
6222 if ($id_entrepot) {
6223 $this->db->begin();
6224
6225 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
6226
6227 // Ensure $nbpiece is a number and positive
6228 $nbpiece = (float) $nbpiece;
6229 if ($nbpiece < 0) {
6230 if (!$movement) {
6231 $movement = 1;
6232 }
6233 $nbpiece = abs($nbpiece);
6234 }
6235 $op = array();
6236 $op[0] = $nbpiece;
6237 $op[1] = -$nbpiece;
6238
6239 $movementstock = new MouvementStock($this->db);
6240 $movementstock->setOrigin($origin_element, (int) $origin_id); // Set ->origin_type and ->origin_id
6241 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
6242
6243 if ($result >= 0) {
6244 if ($extrafields) {
6245 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
6246 $movementstock->array_options = $array_options;
6247 $movementstock->insertExtraFields();
6248 }
6249 $this->db->commit();
6250 return 1;
6251 } else {
6252 $this->setErrorsFromObject($movementstock);
6253
6254 $this->db->rollback();
6255 return -1;
6256 }
6257 }
6258
6259 return -1;
6260 }
6261
6262 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6283 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)
6284 {
6285 // phpcs:enable
6286 if ($id_entrepot) {
6287 $this->db->begin();
6288
6289 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
6290
6291 // Ensure $nbpiece is a number and positive
6292 $nbpiece = (float) $nbpiece;
6293 if ($nbpiece < 0) {
6294 if (!$movement) {
6295 $movement = 1;
6296 }
6297 $nbpiece = abs($nbpiece);
6298 }
6299
6300 $op = array();
6301 $op[0] = $nbpiece;
6302 $op[1] = -$nbpiece;
6303
6304 $movementstock = new MouvementStock($this->db);
6305 $movementstock->setOrigin($origin_element, (int) $origin_id); // Set ->origin_type and ->fk_origin
6306 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct, 0, $force_update_batch);
6307
6308 if ($result >= 0) {
6309 if ($extrafields) {
6310 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
6311 $movementstock->array_options = $array_options;
6312 $movementstock->insertExtraFields();
6313 }
6314 $this->db->commit();
6315 return 1;
6316 } else {
6317 $this->error = $movementstock->error;
6318 $this->errors = $movementstock->errors;
6319
6320 $this->db->rollback();
6321 return -1;
6322 }
6323 }
6324 return -1;
6325 }
6326
6327 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6340 public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
6341 {
6342 // phpcs:enable
6343 $this->stock_reel = 0;
6344 $this->stock_warehouse = array();
6345 $this->stock_theorique = 0;
6346
6347 // Set filter on warehouse status
6348 $warehouseStatus = array();
6349 if (preg_match('/warehouseclosed/', $option)) {
6351 }
6352 if (preg_match('/warehouseopen/', $option)) {
6354 }
6355 if (preg_match('/warehouseinternal/', $option)) {
6356 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
6358 } else {
6360 }
6361 }
6362
6363 $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
6364 $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
6365 $sql .= ", ".$this->db->prefix()."entrepot as w";
6366 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
6367 $sql .= " AND w.rowid = ps.fk_entrepot";
6368 $sql .= " AND ps.fk_product = ".((int) $this->id);
6369 if (count($warehouseStatus)) {
6370 $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
6371 }
6372
6373 $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;
6374
6375 dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
6376 $result = $this->db->query($sql);
6377 if ($result) {
6378 $num = $this->db->num_rows($result);
6379 $i = 0;
6380 if ($num > 0) {
6381 while ($i < $num) {
6382 $row = $this->db->fetch_object($result);
6383 $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
6384 $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
6385 $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
6386 if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
6387 $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
6388 }
6389 $this->stock_reel += $row->reel;
6390 $i++;
6391 }
6392 $this->stock_reel = (float) price2num($this->stock_reel, 'MS');
6393 }
6394 $this->db->free($result);
6395
6396 if (!preg_match('/novirtual/', $option)) {
6397 $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
6398 }
6399
6400 return 1;
6401 } else {
6402 $this->error = $this->db->lasterror();
6403 return -1;
6404 }
6405 }
6406
6407
6408 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6418 public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
6419 {
6420 // phpcs:enable
6421 global $hookmanager, $action;
6422
6423 $stock_commande_client = 0;
6424 $stock_commande_fournisseur = 0;
6425 $stock_sending_client = 0;
6426 $stock_reception_fournisseur = 0;
6427 $stock_inproduction = 0;
6428
6429 //dol_syslog("load_virtual_stock");
6430
6431 if (isModEnabled('order')) {
6432 $result = $this->load_stats_commande(0, '1,2', 1);
6433 if ($result < 0) {
6434 dol_print_error($this->db, $this->error);
6435 }
6436 $stock_commande_client = $this->stats_commande['qty'];
6437 }
6438 if (isModEnabled("shipping")) {
6439 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
6440 $filterShipmentStatus = '';
6441 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
6442 $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
6443 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
6444 $filterShipmentStatus = Expedition::STATUS_CLOSED;
6445 }
6446 $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
6447 if ($result < 0) {
6448 dol_print_error($this->db, $this->error);
6449 }
6450 $stock_sending_client = $this->stats_expedition['qty'];
6451 }
6452 // Include supplier order lines
6453 if (isModEnabled("supplier_order")) {
6454 $filterStatus = getDolGlobalString('SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK', '3,4');
6455 if (isset($includedraftpoforvirtual)) {
6456 $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
6457 }
6458 $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
6459 if ($result < 0) {
6460 dol_print_error($this->db, $this->error);
6461 }
6462 $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
6463 }
6464 // Include reception lines
6465 if (isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) {
6466 $filterStatus = '4';
6467 if (isset($includedraftpoforvirtual)) {
6468 $filterStatus = '0,'.$filterStatus;
6469 }
6470 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
6471 if ($result < 0) {
6472 dol_print_error($this->db, $this->error);
6473 }
6474 $stock_reception_fournisseur = $this->stats_reception['qty'];
6475 }
6476 // Include manufacturing
6477 if (isModEnabled('mrp')) {
6478 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
6479 if ($result < 0) {
6480 dol_print_error($this->db, $this->error);
6481 }
6482 $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
6483 }
6484
6485 $this->stock_theorique = $this->stock_reel + $stock_inproduction;
6486
6487 // $weBillOrderOrShipmentReception is set to 'order' or 'shipmentreception'. it will be used to know how to make virtual stock
6488 // calculation when we have a stock increase or decrease on billing. Do we have to count orders to bill or shipment/reception to bill ?
6489 $weBillOrderOrShipmentReception = getDolGlobalString('STOCK_DO_WE_BILL_ORDER_OR_SHIPMENTECEPTION_FOR_VIRTUALSTOCK', 'order');
6490
6491 // Stock decrease mode
6492 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
6493 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
6494 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER')) {
6495 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
6496 $tmpnewprod = dol_clone($this, 1);
6497 $result = $tmpnewprod->load_stats_commande(0, '0', 1); // Get qty in draft orders
6498 $this->stock_theorique += $tmpnewprod->stats_commande['qty'];
6499 }
6500 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'order') {
6501 $this->stock_theorique -= $stock_commande_client;
6502 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
6503 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
6504 }
6505
6506 // Stock Increase mode
6507 if (getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION') || getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
6508 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6509 } 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
6510 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6511 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) { // Warning: stock change "on approval", not on validation !
6512 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
6513 $tmpnewprod = dol_clone($this, 1);
6514 $result = $tmpnewprod->load_stats_commande_fournisseur(0, '0', 1); // Get qty in draft orders
6515 $this->stock_theorique += $this->stats_commande_fournisseur['qty'];
6516 }
6517 $this->stock_theorique -= $stock_reception_fournisseur;
6518 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'order') {
6519 $this->stock_theorique += $stock_commande_fournisseur;
6520 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
6521 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6522 }
6523
6524 $parameters = array('id' => $this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
6525 // Note that $action and $object may have been modified by some hooks
6526 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
6527 if ($reshook > 0) {
6528 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
6529 } elseif ($reshook == 0 && isset($hookmanager->resArray['stock_stats_hook'])) {
6530 $this->stock_theorique += $hookmanager->resArray['stock_stats_hook'];
6531 }
6532
6533 //Virtual Stock by Warehouse
6534 if (!empty($this->stock_warehouse) && getDolGlobalString('STOCK_ALLOW_VIRTUAL_STOCK_PER_WAREHOUSE')) {
6535 foreach ($this->stock_warehouse as $warehouseid => $stockwarehouse) {
6536 if (isModEnabled('mrp')) {
6537 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock, $warehouseid);
6538 if ($result < 0) {
6539 dol_print_error($this->db, $this->error);
6540 }
6541 }
6542
6543 if ($this->fk_default_warehouse == $warehouseid) {
6544 $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']);
6545 } else {
6546 $this->stock_warehouse[$warehouseid]->virtual = $this->stock_warehouse[$warehouseid]->real + $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'];
6547 }
6548 }
6549 }
6550
6551 return 1;
6552 }
6553
6561 public function loadStockForVirtualProduct($option = '', $qtyWish = 1)
6562 {
6563 $this->stock_warehouse = array();
6564 $error = 0;
6565
6566 $this->get_sousproduits_arbo();
6567 $prods_arbo = $this->get_arbo_each_prod($qtyWish, 1);
6568 if (count($prods_arbo) > 0) {
6569 $productCachedList = array();
6570 $stockByComponentList = array();
6571
6572 foreach ($prods_arbo as $componentArr) {
6573 $componentId = $componentArr['id'];
6574 // only component whose manage stock
6575 if ($componentArr['incdec'] == 1) {
6576 if (!isset($productCachedList[$componentId])) {
6577 $componentStatic = new self($this->db);
6578 $componentStatic->fetch($componentId);
6579 // check if it's a sub-kit
6580 $childrenNb = $componentStatic->hasFatherOrChild(1);
6581 if ($childrenNb == 0) {
6582 $componentStatic->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
6583 if (!isset($stockByComponentList[$componentId])) {
6584 $stockByComponentList[$componentId] = array(
6585 'qty_need' => 0
6586 );
6587 }
6588 $stockByComponentList[$componentId]['qty_need'] += $componentArr['nb_total'];
6589 }
6590 $productCachedList[$componentId] = $componentStatic;
6591 }
6592 }
6593 }
6594
6595 if (!empty($stockByComponentList)) {
6596 foreach ($stockByComponentList as $componentId => $stockByComponentArr) {
6597 if (!isset($productCachedList[$componentId])) {
6598 $componentStatic = new self($this->db);
6599 $componentStatic->fetch($componentId);
6600 $componentStatic->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
6601 $productCachedList[$componentId] = $componentStatic;
6602 }
6603 $component = $productCachedList[$componentId];
6604
6605
6606 if ($component->stock_reel < $stockByComponentArr['qty_need']) {
6607 // not enough stock for this component to assemble this virtual product
6608 $error++;
6609 $this->error = 'Not enough component [id='.$componentId.'] in stock, real='.$component->stock_reel.' and need='.$stockByComponentArr['qty_need'];
6610 $this->errors[] = $this->error;
6611 dol_syslog(__METHOD__.' : '.$this->error, LOG_ERR);
6612 } else {
6613 if (!empty($component->stock_warehouse)) {
6614 foreach ($component->stock_warehouse as $warehouseId => $warehouseObj) {
6615 $kitWarehouseAvailable = new stdClass();
6616 $kitWarehouseAvailable->id = $warehouseObj->id;
6617 $kitWarehouseAvailable->real = $qtyWish;
6618 $this->stock_warehouse[$warehouseId] = $kitWarehouseAvailable;
6619 }
6620 }
6621 }
6622
6623 if ($error) {
6624 break;
6625 }
6626 }
6627 } elseif (getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE') && empty($productCachedList)) {
6628 // if all sub product are not stock managed when use parent stock
6629 $this->load_stock('warehouseopen');
6630 }
6631 }
6632
6633 if ($error) {
6634 return -1;
6635 } else {
6636 return 1;
6637 }
6638 }
6639
6647 public function loadBatchInfo($batch)
6648 {
6649 $result = array();
6650
6651 $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";
6652 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
6653 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
6654 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
6655 $resql = $this->db->query($sql);
6656 if ($resql) {
6657 $num = $this->db->num_rows($resql);
6658 $i = 0;
6659 while ($i < $num) {
6660 $obj = $this->db->fetch_object($resql);
6661 $result[] = array('batch' => $batch, 'eatby' => $this->db->jdate($obj->eatby), 'sellby' => $this->db->jdate($obj->sellby), 'qty' => $obj->qty);
6662 $i++;
6663 }
6664 return $result;
6665 } else {
6666 dol_print_error($this->db);
6667 $this->db->rollback();
6668 return array();
6669 }
6670 }
6671
6672 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6681 public function add_photo($sdir, $file)
6682 {
6683 // phpcs:enable
6684 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6685
6686 $result = 0;
6687
6688 $dir = $sdir;
6689 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6690 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
6691 } else {
6692 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
6693 }
6694
6695 dol_mkdir($dir);
6696
6697 $dir_osencoded = $dir;
6698
6699 if (is_dir($dir_osencoded)) {
6700 $originImage = $dir.'/'.$file['name'];
6701
6702 // Cree fichier en taille origine
6703 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
6704
6705 if (file_exists(dol_osencode($originImage))) {
6706 // Create thumbs
6707 $this->addThumbs($originImage);
6708 }
6709 }
6710
6711 if (is_numeric($result) && $result > 0) {
6712 return 1;
6713 } else {
6714 return -1;
6715 }
6716 }
6717
6718 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6725 public function is_photo_available($sdir)
6726 {
6727 // phpcs:enable
6728 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6729 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6730
6731 $dir = $sdir;
6732 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6733 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
6734 } else {
6735 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
6736 }
6737
6738 $dir_osencoded = dol_osencode($dir);
6739 if (file_exists($dir_osencoded)) {
6740 $handle = opendir($dir_osencoded);
6741 if (is_resource($handle)) {
6742 while (($file = readdir($handle)) !== false) {
6743 if (!utf8_check($file)) {
6744 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
6745 }
6746 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6747 return true;
6748 }
6749 }
6750 }
6751 }
6752
6753 return false;
6754 }
6755
6756 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6764 public function liste_photos($dir, $nbmax = 0)
6765 {
6766 // phpcs:enable
6767 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6768 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6769
6770 $nbphoto = 0;
6771 $tabobj = array();
6772
6773 $dir_osencoded = dol_osencode($dir);
6774 $handle = @opendir($dir_osencoded);
6775 if (is_resource($handle)) {
6776 while (($file = readdir($handle)) !== false) {
6777 if (!utf8_check($file)) {
6778 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // readdir returns ISO
6779 }
6780 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6781 $nbphoto++;
6782
6783 // We forge name of thumb.
6784 $photo = $file;
6785 $photo_vignette = '';
6786 $regs = array();
6787 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
6788 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
6789 }
6790
6791 $dirthumb = $dir.'thumbs/';
6792
6793 // Object
6794 $obj = array();
6795 $obj['photo'] = $photo;
6796 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
6797 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
6798 } else {
6799 $obj['photo_vignette'] = "";
6800 }
6801
6802 $tabobj[$nbphoto - 1] = $obj;
6803
6804 // Do we have to continue with next photo ?
6805 if ($nbmax && $nbphoto >= $nbmax) {
6806 break;
6807 }
6808 }
6809 }
6810
6811 closedir($handle);
6812 }
6813
6814 return $tabobj;
6815 }
6816
6817 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6824 public function delete_photo($file)
6825 {
6826 // phpcs:enable
6827 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6828 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6829
6830 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
6831 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
6832 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
6833
6834 // On efface l'image d'origine
6835 dol_delete_file($file, 0, 0, 0, $this); // For triggers
6836
6837 // Si elle existe, on efface la vignette
6838 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
6839 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
6840 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6841 dol_delete_file($dirthumb.$photo_vignette);
6842 }
6843
6844 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
6845 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6846 dol_delete_file($dirthumb.$photo_vignette);
6847 }
6848 }
6849 }
6850
6851 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6858 public function get_image_size($file)
6859 {
6860 // phpcs:enable
6861 $file_osencoded = dol_osencode($file);
6862 $infoImg = getimagesize($file_osencoded); // Get information on image
6863 $this->imgWidth = $infoImg[0]; // Largeur de l'image
6864 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
6865 }
6866
6872 public function loadStateBoard()
6873 {
6874 global $hookmanager;
6875
6876 $this->nb = array();
6877
6878 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6879 $sql .= " FROM ".$this->db->prefix()."product as p";
6880 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6881 // Add where from hooks
6882 if (is_object($hookmanager)) {
6883 $parameters = array();
6884 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6885 $sql .= $hookmanager->resPrint;
6886 }
6887 $sql .= ' GROUP BY fk_product_type';
6888
6889 $resql = $this->db->query($sql);
6890 if ($resql) {
6891 while ($obj = $this->db->fetch_object($resql)) {
6892 if ($obj->fk_product_type == 1) {
6893 $this->nb["services"] = $obj->nb;
6894 } else {
6895 $this->nb["products"] = $obj->nb;
6896 }
6897 }
6898 $this->db->free($resql);
6899 return 1;
6900 } else {
6901 dol_print_error($this->db);
6902 $this->error = $this->db->error();
6903 return -1;
6904 }
6905 }
6906
6912 public function isProduct()
6913 {
6914 return $this->type == Product::TYPE_PRODUCT;
6915 }
6916
6922 public function isService()
6923 {
6924 return $this->type == Product::TYPE_SERVICE;
6925 }
6926
6932 public function isStockManaged()
6933 {
6934 return (($this->isProduct() || ($this->isService() && getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) && ($this->stockable_product > 0));
6935 }
6936
6942 public function isMandatoryPeriod()
6943 {
6944 return $this->mandatory_period == 1;
6945 }
6946
6952 public function hasbatch()
6953 {
6954 return $this->status_batch > 0;
6955 }
6956
6957
6958 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6967 public function get_barcode($object, $type = '')
6968 {
6969 // phpcs:enable
6970 global $conf;
6971
6972 $result = '';
6973 if (getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
6974 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6975 foreach ($dirsociete as $dirroot) {
6976 $res = dol_include_once($dirroot . getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM').'.php');
6977 if ($res) {
6978 break;
6979 }
6980 }
6981 $var = getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM');
6982 $mod = new $var();
6983 '@phan-var-force ModeleNumRefBarCode $mod';
6984
6985 $result = $mod->getNextValue($object, $type);
6986
6987 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6988 }
6989 return $result;
6990 }
6991
6999 public function initAsSpecimen()
7000 {
7001 $now = dol_now();
7002
7003 // Initialize parameters
7004 $this->specimen = 1;
7005 $this->id = 0;
7006 $this->ref = 'PRODUCT_SPEC';
7007 $this->label = 'PRODUCT SPECIMEN';
7008 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
7009 $this->specimen = 1;
7010 $this->country_id = 1;
7011 $this->status = 1;
7012 $this->status_buy = 1;
7013 $this->tobatch = 0;
7014 $this->sell_or_eat_by_mandatory = 0;
7015 $this->note_private = 'This is a comment (private)';
7016 $this->note_public = 'This is a comment (public)';
7017 $this->date_creation = $now;
7018 $this->date_modification = $now;
7019
7020 $this->weight = 4;
7021 $this->weight_units = 3;
7022
7023 $this->length = 5;
7024 $this->length_units = 1;
7025 $this->width = 6;
7026 $this->width_units = 0;
7027 $this->height = null;
7028 $this->height_units = null;
7029
7030 $this->surface = 30;
7031 $this->surface_units = 0;
7032 $this->volume = 300;
7033 $this->volume_units = 0;
7034
7035 $this->barcode = -1; // Create barcode automatically
7036
7037 return 1;
7038 }
7039
7049 public function getLabelOfUnit($type = 'long', $outputlangs = null, $noentities = 0)
7050 {
7051 global $langs;
7052
7053 if (empty($this->fk_unit)) {
7054 return '';
7055 }
7056 if (empty($outputlangs)) {
7057 $outputlangs = $langs;
7058 }
7059
7060 $outputlangs->load('products');
7061 $label = '';
7062
7063 $sql = "SELECT code, label, short_label FROM ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
7064
7065 $resql = $this->db->query($sql);
7066 if (!$resql) {
7067 $this->error = $this->db->error();
7068 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
7069 return -1;
7070 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
7071 if ($type == 'short') {
7072 if ($noentities) {
7073 $label = $outputlangs->transnoentitiesnoconv($res['short_label']);
7074 } else {
7075 $label = $outputlangs->trans($res['short_label']);
7076 }
7077 } elseif ($type == 'code') {
7078 $label = $res['code'];
7079 } else {
7080 if ($outputlangs->trans('unit'.$res['code']) == 'unit'.$res['code']) {
7081 // No translation available
7082 $label = $res['label'];
7083 } else {
7084 // Return the translated value
7085 if ($noentities) {
7086 $label = $outputlangs->transnoentitiesnoconv('unit'.$res['code']);
7087 } else {
7088 $label = $outputlangs->trans('unit'.$res['code']);
7089 }
7090 }
7091 }
7092 }
7093 $this->db->free($resql);
7094
7095 return $label;
7096 }
7097
7098 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7104 public function min_recommended_price()
7105 {
7106 // phpcs:enable
7107 $maxpricesupplier = 0;
7108
7109 if (getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE')) {
7110 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
7111 $product_fourn = new ProductFournisseur($this->db);
7112 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
7113
7114 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
7115 foreach ($product_fourn_list as $productfourn) {
7116 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
7117 $maxpricesupplier = $productfourn->fourn_unitprice;
7118 }
7119 }
7120
7121 $maxpricesupplier *= getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE');
7122 }
7123 }
7124
7125 return $maxpricesupplier;
7126 }
7127
7128
7139 public function setCategories($categories)
7140 {
7141 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
7142 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
7143 }
7144
7153 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
7154 {
7155 $tables = array(
7156 'product_customer_price',
7157 'product_customer_price_log'
7158 );
7159
7160 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
7161 }
7162
7174 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
7175 {
7176 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
7177 $query = $this->db->query($sql);
7178
7179 $rules = array();
7180
7181 while ($result = $this->db->fetch_object($query)) {
7182 $rules[$result->level] = $result;
7183 }
7184
7185 //Because prices can be based on other level's prices, we temporarily store them
7186 $prices = array(
7187 1 => $baseprice
7188 );
7189
7190 $nbofproducts = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
7191 for ($i = 1; $i <= $nbofproducts; $i++) {
7192 $price = $baseprice;
7193 $price_min = $baseprice;
7194
7195 //We have to make sure it does exist and it is > 0
7196 //First price level only allows changing min_price
7197 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
7198 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
7199 }
7200
7201 $prices[$i] = $price;
7202
7203 //We have to make sure it does exist and it is > 0
7204 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
7205 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
7206 }
7207
7208 //Little check to make sure the price is modified before triggering generation
7209 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
7210 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
7211
7212 if ($check_amount && $check_type) {
7213 continue;
7214 }
7215
7216 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, 1) < 0) {
7217 return -1;
7218 }
7219 }
7220
7221 return 1;
7222 }
7223
7229 public function getRights()
7230 {
7231 global $user;
7232
7233 if ($this->isProduct()) {
7234 return $user->rights->produit;
7235 } else {
7236 return $user->rights->service;
7237 }
7238 }
7239
7246 public function info($id)
7247 {
7248 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, GREATEST(p.tms, pef.tms) as date_modification,";
7249 $sql .= " p.fk_user_author, p.fk_user_modif";
7250 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
7251 $sql .= " LEFT JOIN ".$this->db->prefix().$this->table_element."_extrafields as pef ON pef.fk_object=p.rowid";
7252 $sql .= " WHERE p.rowid = ".((int) $id);
7253
7254 $result = $this->db->query($sql);
7255 if ($result) {
7256 if ($this->db->num_rows($result)) {
7257 $obj = $this->db->fetch_object($result);
7258
7259 $this->id = $obj->rowid;
7260 $this->ref = $obj->ref;
7261
7262 $this->user_creation_id = $obj->fk_user_author;
7263 $this->user_modification_id = $obj->fk_user_modif;
7264
7265 $this->date_creation = $this->db->jdate($obj->date_creation);
7266 $this->date_modification = $this->db->jdate($obj->date_modification);
7267 }
7268
7269 $this->db->free($result);
7270 } else {
7271 dol_print_error($this->db);
7272 }
7273 }
7274
7275
7281 public function getProductDurationHours()
7282 {
7283 if (empty($this->duration_value)) {
7284 $this->errors[] = 'ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
7285 return -1;
7286 }
7287 if ($this->duration_unit == 's') {
7288 $prodDurationHours = 1. / 3600;
7289 } elseif ($this->duration_unit == 'i' || $this->duration_unit == 'mn' || $this->duration_unit == 'min') {
7290 $prodDurationHours = 1. / 60;
7291 } elseif ($this->duration_unit == 'h') {
7292 $prodDurationHours = 1.;
7293 } elseif ($this->duration_unit == 'd') {
7294 $prodDurationHours = 24.;
7295 } elseif ($this->duration_unit == 'w') {
7296 $prodDurationHours = 24. * 7;
7297 } elseif ($this->duration_unit == 'm') {
7298 $prodDurationHours = 24. * 30;
7299 } elseif ($this->duration_unit == 'y') {
7300 $prodDurationHours = 24. * 365;
7301 } else {
7302 $prodDurationHours = 0.0;
7303 }
7304 $prodDurationHours *= $this->duration_value;
7305
7306 return $prodDurationHours;
7307 }
7308
7309
7317 public function getKanbanView($option = '', $arraydata = null)
7318 {
7319 global $langs, $conf;
7320
7321 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
7322
7323 $return = '<div class="box-flex-item box-flex-grow-zero">';
7324 $return .= '<div class="info-box info-box-sm">';
7325 $return .= '<div class="info-box-img">';
7326 $label = '';
7327 if ($this->entity !== null && $this->is_photo_available($conf->product->multidir_output[$this->entity])) {
7328 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
7329 $return .= $label;
7330 } else {
7331 if ($this->isProduct()) {
7332 $label .= img_picto('', 'product');
7333 } elseif ($this->isService()) {
7334 $label .= img_picto('', 'service');
7335 }
7336 $return .= $label;
7337 }
7338 $return .= '</div>';
7339 $return .= '<div class="info-box-content">';
7340 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl() . '</span>';
7341 if ($selected >= 0) {
7342 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
7343 }
7344 $return .= '<br><span class="info-box-label opacitymedium inline-block tdoverflowmax150 valignmiddle" title="'.dol_escape_htmltag($this->label).'">'.dol_escape_htmltag($this->label).'</span>';
7345 if ($this->price_base_type == 'TTC') {
7346 $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
7347 } else {
7348 if ($this->status) {
7349 $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
7350 }
7351 }
7352 $br = 1;
7353 if ($this->isProduct()) {
7354 $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>';
7355 $br = 0;
7356 }
7357 if ($br) {
7358 $return .= '<br>';
7359 $return .= '<div class="info-box-status inline-block valignmiddle">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
7360 } else {
7361 $return .= '<div class="info-box-status inline-block valignmiddle marginleftonly paddingleft">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
7362 }
7363 $return .= '</div>';
7364 $return .= '</div>';
7365 $return .= '</div>';
7366 return $return;
7367 }
7368}
7369
7375{
7376 public $picto = 'service';
7377}
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.
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