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