dolibarr 20.0.0
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
98 public $picto = 'product';
99
103 protected $table_ref_field = 'ref';
104
105 public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.xpm|\.xbm'; // See also into images.lib.php
106
111 public $libelle;
112
118 public $label;
119
125 public $description;
126
132 public $other;
133
139 public $type = self::TYPE_PRODUCT;
140
146 public $price;
147
148 public $price_formated; // used by takepos/ajax/ajax.php
149
155 public $price_ttc;
156
157 public $price_ttc_formated; // used by takepos/ajax/ajax.php
158
164 public $price_min;
165
171 public $price_min_ttc;
172
177 public $price_base_type;
178 public $price_label;
179
181 public $multiprices = array();
182 public $multiprices_ttc = array();
183 public $multiprices_base_type = array();
184 public $multiprices_default_vat_code = array();
185 public $multiprices_min = array();
186 public $multiprices_min_ttc = array();
187 public $multiprices_tva_tx = array();
188 public $multiprices_recuperableonly = array();
189
192 public $prices_by_qty = array();
193 public $prices_by_qty_id = array();
194 public $prices_by_qty_list = array();
195
199 public $level;
200
202 public $multilangs = array();
203
206
208 public $tva_tx;
209
213 public $tva_npr = 0;
214
217
220 public $localtax2_tx;
221 public $localtax1_type;
222 public $localtax2_type;
223
224 // Properties set by get_buyprice() for return
225
226 public $desc_supplier;
227 public $vatrate_supplier;
228 public $default_vat_code_supplier;
229 public $fourn_multicurrency_price;
230 public $fourn_multicurrency_unitprice;
231 public $fourn_multicurrency_tx;
232 public $fourn_multicurrency_id;
233 public $fourn_multicurrency_code;
234 public $packaging;
235
236
237 public $lifetime; // In seconds
238
239 public $qc_frequency;
240
246 public $stock_reel = 0;
247
253 public $stock_theorique;
254
260 public $cost_price;
261
263 public $pmp;
264
270 public $seuil_stock_alerte = 0;
271
275 public $desiredstock = 0;
276
288 public $duration;
289
293 public $fk_default_workstation;
294
300 public $status = 0;
301
308 public $tosell;
309
315 public $status_buy = 0;
316
323 public $tobuy;
324
330 public $finished;
331
337 public $fk_default_bom;
338
344 public $product_fourn_price_id;
345
351 public $buyprice;
352
358 public $tobatch;
359
360
366 public $status_batch = 0;
367
373 public $sell_or_eat_by_mandatory = 0;
374
380 public $batch_mask = '';
381
387 public $customcode;
388
394 public $url;
395
397 public $weight;
398 public $weight_units; // scale -3, 0, 3, 6
399 public $length;
400 public $length_units; // scale -3, 0, 3, 6
401 public $width;
402 public $width_units; // scale -3, 0, 3, 6
403 public $height;
404 public $height_units; // scale -3, 0, 3, 6
405 public $surface;
406 public $surface_units; // scale -3, 0, 3, 6
407 public $volume;
408 public $volume_units; // scale -3, 0, 3, 6
409
410 public $net_measure;
411 public $net_measure_units; // scale -3, 0, 3, 6
412
413 public $accountancy_code_sell;
414 public $accountancy_code_sell_intra;
415 public $accountancy_code_sell_export;
416 public $accountancy_code_buy;
417 public $accountancy_code_buy_intra;
418 public $accountancy_code_buy_export;
419
423 public $barcode;
424
428 public $barcode_type;
429
433 public $barcode_type_code;
434
435 public $stats_propale = array();
436 public $stats_commande = array();
437 public $stats_contrat = array();
438 public $stats_facture = array();
439 public $stats_proposal_supplier = array();
440 public $stats_commande_fournisseur = array();
441 public $stats_expedition = array();
442 public $stats_reception = array();
443 public $stats_mo = array();
444 public $stats_bom = array();
445 public $stats_mrptoconsume = array();
446 public $stats_mrptoproduce = array();
447 public $stats_facturerec = array();
448 public $stats_facture_fournisseur = array();
449
451 public $imgWidth;
452 public $imgHeight;
453
457 public $date_creation;
458
462 public $date_modification;
463
466
469
470 public $nbphoto = 0;
471
473 public $stock_warehouse = array();
474
478 public $fk_default_warehouse;
482 public $fk_price_expression;
483
484 /* To store supplier price found */
485 public $fourn_qty;
486 public $fourn_pu;
487 public $fourn_price_base_type;
488
492 public $fourn_socid;
493
499
503 public $ref_supplier;
504
510 public $fk_unit;
511
517 public $price_autogen = 0;
518
524 public $supplierprices;
525
531 public $sousprods;
532
536 public $res;
537
538
544 public $is_object_used;
545
546 public $is_sousproduit_qty;
547 public $is_sousproduit_incdec;
548
549 public $mandatory_period;
550
551
580 public $fields = array(
581 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'index' => 1, 'position' => 1, 'comment' => 'Id'),
582 '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'),
583 'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'default' => '1', 'notnull' => 1, 'index' => 1, 'position' => 5),
584 'label' => array('type' => 'varchar(255)', 'label' => 'Label', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'showoncombobox' => 2, 'position' => 15, 'csslist' => 'tdoverflowmax250'),
585 'barcode' => array('type' => 'varchar(255)', 'label' => 'Barcode', 'enabled' => 'isModEnabled("barcode")', 'position' => 20, 'visible' => -1, 'showoncombobox' => 3, 'cssview' => 'tdwordbreak', 'csslist' => 'tdoverflowmax125'),
586 'fk_barcode_type' => array('type' => 'integer', 'label' => 'BarcodeType', 'enabled' => 1, 'position' => 21, 'notnull' => 0, 'visible' => -1,),
587 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 61),
588 'note' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 62),
589 'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 500),
590 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 501),
591 //'date_valid' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>502),
592 'fk_user_author' => array('type' => 'integer', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 510, 'foreignkey' => 'llx_user.rowid'),
593 'fk_user_modif' => array('type' => 'integer', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 511),
594 //'fk_user_valid' =>array('type'=>'integer', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>512),
595 'localtax1_tx' => array('type' => 'double(6,3)', 'label' => 'Localtax1tx', 'enabled' => 1, 'position' => 150, 'notnull' => 0, 'visible' => -1,),
596 'localtax1_type' => array('type' => 'varchar(10)', 'label' => 'Localtax1type', 'enabled' => 1, 'position' => 155, 'notnull' => 1, 'visible' => -1,),
597 'localtax2_tx' => array('type' => 'double(6,3)', 'label' => 'Localtax2tx', 'enabled' => 1, 'position' => 160, 'notnull' => 0, 'visible' => -1,),
598 'localtax2_type' => array('type' => 'varchar(10)', 'label' => 'Localtax2type', 'enabled' => 1, 'position' => 165, 'notnull' => 1, 'visible' => -1,),
599 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 170),
600 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'index' => 0, 'position' => 1000),
601 //'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')),
602 //'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')),
603 'mandatory_period' => array('type' => 'integer', 'label' => 'mandatoryperiod', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'default' => '0', 'index' => 1, 'position' => 1000),
604 );
605
609 const TYPE_PRODUCT = 0;
613 const TYPE_SERVICE = 1;
614
620 public function __construct($db)
621 {
622 $this->db = $db;
623
624 $this->ismultientitymanaged = 1;
625 $this->isextrafieldmanaged = 1;
626
627 $this->canvas = '';
628 }
629
635 public function check()
636 {
637 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
638 $this->ref = trim($this->ref);
639 } else {
640 $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
641 }
642
643 $err = 0;
644 if (dol_strlen(trim($this->ref)) == 0) {
645 $err++;
646 }
647
648 if (dol_strlen(trim($this->label)) == 0) {
649 $err++;
650 }
651
652 if ($err > 0) {
653 return 0;
654 } else {
655 return 1;
656 }
657 }
658
666 public function create($user, $notrigger = 0)
667 {
668 global $conf, $langs;
669
670 $error = 0;
671
672 // Clean parameters
673 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
674 $this->ref = trim($this->ref);
675 } else {
676 $this->ref = dol_sanitizeFileName(dol_string_nospecial(trim($this->ref)));
677 }
678 $this->label = trim($this->label);
679 $this->price_ttc = (float) price2num($this->price_ttc);
680 $this->price = (float) price2num($this->price);
681 $this->price_min_ttc = (float) price2num($this->price_min_ttc);
682 $this->price_min = (float) price2num($this->price_min);
683 $this->price_label = trim($this->price_label);
684 if (empty($this->tva_tx)) {
685 $this->tva_tx = 0;
686 }
687 if (empty($this->tva_npr)) {
688 $this->tva_npr = 0;
689 }
690 //Local taxes
691 if (empty($this->localtax1_tx)) {
692 $this->localtax1_tx = 0;
693 }
694 if (empty($this->localtax2_tx)) {
695 $this->localtax2_tx = 0;
696 }
697 if (empty($this->localtax1_type)) {
698 $this->localtax1_type = '0';
699 }
700 if (empty($this->localtax2_type)) {
701 $this->localtax2_type = '0';
702 }
703 if (empty($this->price)) {
704 $this->price = 0;
705 }
706 if (empty($this->price_min)) {
707 $this->price_min = 0;
708 }
709 // Price by quantity
710 if (empty($this->price_by_qty)) {
711 $this->price_by_qty = 0;
712 }
713
714 if (empty($this->status)) {
715 $this->status = 0;
716 }
717 if (empty($this->status_buy)) {
718 $this->status_buy = 0;
719 }
720
721 $price_ht = 0;
722 $price_ttc = 0;
723 $price_min_ht = 0;
724 $price_min_ttc = 0;
725
726 //
727 if ($this->price_base_type == 'TTC' && $this->price_ttc > 0) {
728 $price_ttc = price2num($this->price_ttc, 'MU');
729 $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)), 'MU');
730 }
731
732 //
733 if ($this->price_base_type != 'TTC' && $this->price > 0) {
734 $price_ht = price2num($this->price, 'MU');
735 $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)), 'MU');
736 }
737
738 //
739 if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC')) {
740 $price_min_ttc = price2num($this->price_min_ttc, 'MU');
741 $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)), 'MU');
742 }
743
744 //
745 if (($this->price_min > 0) && ($this->price_base_type != 'TTC')) {
746 $price_min_ht = price2num($this->price_min, 'MU');
747 $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)), 'MU');
748 }
749
750 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
751 $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
752 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
753 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
754 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
755 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
756
757 // Barcode value
758 $this->barcode = trim($this->barcode);
759 $this->mandatory_period = empty($this->mandatory_period) ? 0 : $this->mandatory_period;
760 // Check parameters
761 if (empty($this->label)) {
762 $this->error = 'ErrorMandatoryParametersNotProvided';
763 return -1;
764 }
765
766 if (empty($this->ref) || $this->ref == 'auto') {
767 // Load object modCodeProduct
768 $module = getDolGlobalString('PRODUCT_CODEPRODUCT_ADDON', 'mod_codeproduct_leopard');
769 if ($module != 'mod_codeproduct_leopard') { // Do not load module file for leopard
770 if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
771 $module = substr($module, 0, dol_strlen($module) - 4);
772 }
773 dol_include_once('/core/modules/product/'.$module.'.php');
774 $modCodeProduct = new $module();
775 '@phan-var-force ModeleProductCode $modCodeProduct';
776 if (!empty($modCodeProduct->code_auto)) {
777 $this->ref = $modCodeProduct->getNextValue($this, $this->type);
778 }
779 unset($modCodeProduct);
780 }
781
782 if (empty($this->ref)) {
783 $this->error = 'ProductModuleNotSetupForAutoRef';
784 return -2;
785 }
786 }
787
788 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);
789
790 $now = dol_now();
791
792 if (empty($this->date_creation)) {
793 $this->date_creation = $now;
794 }
795
796 $this->db->begin();
797
798 // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
799 if ($this->barcode == '-1' || $this->barcode == 'auto') {
800 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
801 }
802
803 // Check more parameters
804 // If error, this->errors[] is filled
805 $result = $this->verify();
806
807 if ($result >= 0) {
808 $sql = "SELECT count(*) as nb";
809 $sql .= " FROM ".$this->db->prefix()."product";
810 $sql .= " WHERE entity IN (".getEntity('product').")";
811 $sql .= " AND ref = '".$this->db->escape($this->ref)."'";
812
813 $result = $this->db->query($sql);
814 if ($result) {
815 $obj = $this->db->fetch_object($result);
816 if ($obj->nb == 0) {
817 // Insert new product, no previous one found
818 $sql = "INSERT INTO ".$this->db->prefix()."product (";
819 $sql .= "datec";
820 $sql .= ", entity";
821 $sql .= ", ref";
822 $sql .= ", ref_ext";
823 $sql .= ", price_min";
824 $sql .= ", price_min_ttc";
825 $sql .= ", label";
826 $sql .= ", fk_user_author";
827 $sql .= ", fk_product_type";
828 $sql .= ", price";
829 $sql .= ", price_ttc";
830 $sql .= ", price_base_type";
831 $sql .= ", price_label";
832 $sql .= ", tobuy";
833 $sql .= ", tosell";
834 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
835 $sql .= ", accountancy_code_buy";
836 $sql .= ", accountancy_code_buy_intra";
837 $sql .= ", accountancy_code_buy_export";
838 $sql .= ", accountancy_code_sell";
839 $sql .= ", accountancy_code_sell_intra";
840 $sql .= ", accountancy_code_sell_export";
841 }
842 $sql .= ", canvas";
843 $sql .= ", finished";
844 $sql .= ", tobatch";
845 $sql .= ", sell_or_eat_by_mandatory";
846 $sql .= ", batch_mask";
847 $sql .= ", fk_unit";
848 $sql .= ", mandatory_period";
849 $sql .= ") VALUES (";
850 $sql .= "'".$this->db->idate($this->date_creation)."'";
851 $sql .= ", ".(!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
852 $sql .= ", '".$this->db->escape($this->ref)."'";
853 $sql .= ", ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
854 $sql .= ", ".price2num($price_min_ht);
855 $sql .= ", ".price2num($price_min_ttc);
856 $sql .= ", ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
857 $sql .= ", ".((int) $user->id);
858 $sql .= ", ".((int) $this->type);
859 $sql .= ", ".price2num($price_ht, 'MT');
860 $sql .= ", ".price2num($price_ttc, 'MT');
861 $sql .= ", '".$this->db->escape($this->price_base_type)."'";
862 $sql .= ", ".(!empty($this->price_label) ? "'".$this->db->escape($this->price_label)."'" : "null");
863 $sql .= ", ".((int) $this->status);
864 $sql .= ", ".((int) $this->status_buy);
865 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
866 $sql .= ", '".$this->db->escape($this->accountancy_code_buy)."'";
867 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_intra)."'";
868 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_export)."'";
869 $sql .= ", '".$this->db->escape($this->accountancy_code_sell)."'";
870 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
871 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
872 }
873 $sql .= ", '".$this->db->escape($this->canvas)."'";
874 $sql .= ", ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'NULL' : (int) $this->finished);
875 $sql .= ", ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : ((int) $this->status_batch));
876 $sql .= ", ".((empty($this->sell_or_eat_by_mandatory) || $this->sell_or_eat_by_mandatory < 0) ? 0 : ((int) $this->sell_or_eat_by_mandatory));
877 $sql .= ", '".$this->db->escape($this->batch_mask)."'";
878 $sql .= ", ".($this->fk_unit > 0 ? ((int) $this->fk_unit) : 'NULL');
879 $sql .= ", '".$this->db->escape($this->mandatory_period)."'";
880 $sql .= ")";
881
882 dol_syslog(get_class($this)."::Create", LOG_DEBUG);
883
884 $result = $this->db->query($sql);
885 if ($result) {
886 $id = $this->db->last_insert_id($this->db->prefix()."product");
887
888 if ($id > 0) {
889 $this->id = $id;
890 $this->price = $price_ht;
891 $this->price_ttc = $price_ttc;
892 $this->price_min = $price_min_ht;
893 $this->price_min_ttc = $price_min_ttc;
894
895 $result = $this->_log_price($user);
896 if ($result > 0) {
897 if ($this->update($id, $user, true, 'add') <= 0) {
898 $error++;
899 }
900 } else {
901 $error++;
902 $this->error = $this->db->lasterror();
903 }
904
905 // update accountancy for this entity
906 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
907 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " .((int) $this->id) . " AND entity = " . ((int) $conf->entity));
908
909 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
910 $sql .= " fk_product";
911 $sql .= ", entity";
912 $sql .= ", accountancy_code_buy";
913 $sql .= ", accountancy_code_buy_intra";
914 $sql .= ", accountancy_code_buy_export";
915 $sql .= ", accountancy_code_sell";
916 $sql .= ", accountancy_code_sell_intra";
917 $sql .= ", accountancy_code_sell_export";
918 $sql .= ") VALUES (";
919 $sql .= $this->id;
920 $sql .= ", " . $conf->entity;
921 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
922 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
923 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
924 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
925 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
926 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
927 $sql .= ")";
928 $result = $this->db->query($sql);
929 if (!$result) {
930 $error++;
931 $this->error = 'ErrorFailedToInsertAccountancyForEntity';
932 }
933 }
934 } else {
935 $error++;
936 $this->error = 'ErrorFailedToGetInsertedId';
937 }
938 } else {
939 $error++;
940 $this->error = $this->db->lasterror();
941 }
942 } else {
943 // Product already exists with this ref
944 $langs->load("products");
945 $error++;
946 $this->error = "ErrorProductAlreadyExists";
947 dol_syslog(get_class($this)."::Create fails, ref ".$this->ref." already exists");
948 }
949 } else {
950 $error++;
951 $this->error = $this->db->lasterror();
952 }
953
954 if (!$error && !$notrigger) {
955 // Call trigger
956 $result = $this->call_trigger('PRODUCT_CREATE', $user);
957 if ($result < 0) {
958 $error++;
959 }
960 // End call triggers
961 }
962
963 if (!$error) {
964 $this->db->commit();
965 return $this->id;
966 } else {
967 $this->db->rollback();
968 return -$error;
969 }
970 } else {
971 $this->db->rollback();
972 dol_syslog(get_class($this)."::Create fails verify ".implode(',', $this->errors), LOG_WARNING);
973 return -3;
974 }
975 }
976
977
984 public function verify()
985 {
986 global $langs;
987
988 $this->errors = array();
989
990 $result = 0;
991 $this->ref = trim($this->ref);
992
993 if (!$this->ref) {
994 $this->errors[] = 'ErrorBadRef';
995 $result = -2;
996 }
997
998 $arrayofnonnegativevalue = array('weight' => 'Weight', 'width' => 'Width', 'height' => 'Height', 'length' => 'Length', 'surface' => 'Surface', 'volume' => 'Volume');
999 foreach ($arrayofnonnegativevalue as $key => $value) {
1000 if (property_exists($this, $key) && !empty($this->$key) && ($this->$key < 0)) {
1001 $langs->loadLangs(array("main", "other"));
1002 $this->error = $langs->trans("FieldCannotBeNegative", $langs->transnoentitiesnoconv($value));
1003 $this->errors[] = $this->error;
1004 $result = -4;
1005 }
1006 }
1007
1008 $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
1009 if ($rescode) {
1010 if ($rescode == -1) {
1011 $this->errors[] = 'ErrorBadBarCodeSyntax';
1012 } elseif ($rescode == -2) {
1013 $this->errors[] = 'ErrorBarCodeRequired';
1014 } elseif ($rescode == -3) {
1015 // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
1016 $this->errors[] = 'ErrorBarCodeAlreadyUsed';
1017 }
1018
1019 $result = -3;
1020 }
1021
1022 return $result;
1023 }
1024
1025 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1036 public function check_barcode($valuetotest, $typefortest)
1037 {
1038 // phpcs:enable
1039 global $conf;
1040 if (isModEnabled('barcode') && getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
1041 $module = strtolower(getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM'));
1042
1043 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
1044 foreach ($dirsociete as $dirroot) {
1045 $res = dol_include_once($dirroot.$module.'.php');
1046 if ($res) {
1047 break;
1048 }
1049 }
1050
1051 $mod = new $module();
1052 '@phan-var-force ModeleNumRefBarCode $mod';
1053
1054 dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
1055 $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
1056 return $result;
1057 } else {
1058 return 0;
1059 }
1060 }
1061
1073 public function update($id, $user, $notrigger = 0, $action = 'update', $updatetype = false)
1074 {
1075 global $langs, $conf, $hookmanager;
1076
1077 $error = 0;
1078
1079 // Check parameters
1080 if (!$this->label) {
1081 $this->label = 'MISSING LABEL';
1082 }
1083
1084 // Clean parameters
1085 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
1086 $this->ref = trim($this->ref);
1087 } else {
1088 $this->ref = dol_string_nospecial(trim($this->ref));
1089 }
1090 $this->label = trim($this->label);
1091 $this->description = trim($this->description);
1092 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : null);
1093 $this->note_public = (isset($this->note_public) ? trim($this->note_public) : null);
1094 $this->net_measure = price2num($this->net_measure);
1095 $this->net_measure_units = (empty($this->net_measure_units) ? '' : trim($this->net_measure_units));
1096 $this->weight = price2num($this->weight);
1097 $this->weight_units = (empty($this->weight_units) ? '' : trim($this->weight_units));
1098 $this->length = price2num($this->length);
1099 $this->length_units = (empty($this->length_units) ? '' : trim($this->length_units));
1100 $this->width = price2num($this->width);
1101 $this->width_units = (empty($this->width_units) ? '' : trim($this->width_units));
1102 $this->height = price2num($this->height);
1103 $this->height_units = (empty($this->height_units) ? '' : trim($this->height_units));
1104 $this->surface = price2num($this->surface);
1105 $this->surface_units = (empty($this->surface_units) ? '' : trim($this->surface_units));
1106 $this->volume = price2num($this->volume);
1107 $this->volume_units = (empty($this->volume_units) ? '' : trim($this->volume_units));
1108
1109 // set unit not defined
1110 if (is_numeric($this->length_units)) {
1111 $this->width_units = $this->length_units; // Not used yet
1112 }
1113 if (is_numeric($this->length_units)) {
1114 $this->height_units = $this->length_units; // Not used yet
1115 }
1116
1117 // Automated compute surface and volume if not filled
1118 if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
1119 $this->surface = (float) $this->length * (float) $this->width;
1120 $this->surface_units = measuring_units_squared($this->length_units);
1121 }
1122 if (empty($this->volume) && !empty($this->surface) && !empty($this->height) && $this->length_units == $this->height_units) {
1123 $this->volume = $this->surface * (float) $this->height;
1124 $this->volume_units = measuring_units_cubed($this->height_units);
1125 }
1126
1127 if (empty($this->tva_tx)) {
1128 $this->tva_tx = 0;
1129 }
1130 if (empty($this->tva_npr)) {
1131 $this->tva_npr = 0;
1132 }
1133 if (empty($this->localtax1_tx)) {
1134 $this->localtax1_tx = 0;
1135 }
1136 if (empty($this->localtax2_tx)) {
1137 $this->localtax2_tx = 0;
1138 }
1139 if (empty($this->localtax1_type)) {
1140 $this->localtax1_type = '0';
1141 }
1142 if (empty($this->localtax2_type)) {
1143 $this->localtax2_type = '0';
1144 }
1145 if (empty($this->status)) {
1146 $this->status = 0;
1147 }
1148 if (empty($this->status_buy)) {
1149 $this->status_buy = 0;
1150 }
1151
1152 if (empty($this->country_id)) {
1153 $this->country_id = 0;
1154 }
1155
1156 if (empty($this->state_id)) {
1157 $this->state_id = 0;
1158 }
1159
1160 // Barcode value
1161 $this->barcode = (empty($this->barcode) ? '' : trim($this->barcode));
1162
1163 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1164 $this->accountancy_code_buy_intra = (!empty($this->accountancy_code_buy_intra) ? trim($this->accountancy_code_buy_intra) : '');
1165 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1166 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1167 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1168 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1169
1170
1171 $this->db->begin();
1172
1173 $result = 0;
1174 // Check name is required and codes are ok or unique. If error, this->errors[] is filled
1175 if ($action != 'add') {
1176 $result = $this->verify(); // We don't check when update called during a create because verify was already done
1177 } else {
1178 // we can continue
1179 $result = 0;
1180 }
1181
1182 if ($result >= 0) {
1183 // $this->oldcopy should have been set by the caller of update (here properties were already modified)
1184 if (is_null($this->oldcopy) || (is_object($this->oldcopy) && $this->oldcopy->isEmpty())) {
1185 $this->oldcopy = dol_clone($this, 1);
1186 }
1187 // Test if batch management is activated on existing product
1188 // If yes, we create missing entries into product_batch
1189 if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
1190 //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
1191 $valueforundefinedlot = '000000';
1192 if (getDolGlobalString('STOCK_DEFAULT_BATCH')) {
1193 $valueforundefinedlot = getDolGlobalString('STOCK_DEFAULT_BATCH');
1194 }
1195
1196 dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
1197
1198 $this->load_stock();
1199 foreach ($this->stock_warehouse as $idW => $ObjW) { // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
1200 $qty_batch = 0;
1201 foreach ($ObjW->detail_batch as $detail) { // Each lines of detail in product_batch of the current $ObjW = product_stock
1202 if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
1203 // We discard this line, we will create it later
1204 $sqlclean = "DELETE FROM ".$this->db->prefix()."product_batch WHERE batch in('Undefined', '".$this->db->escape($valueforundefinedlot)."') AND fk_product_stock = ".((int) $ObjW->id);
1205 $result = $this->db->query($sqlclean);
1206 if (!$result) {
1207 dol_print_error($this->db);
1208 exit;
1209 }
1210 continue;
1211 }
1212
1213 $qty_batch += $detail->qty;
1214 }
1215 // Quantities in batch details are not same as stock quantity,
1216 // so we add a default batch record to complete and get same qty in parent and child table
1217 if ($ObjW->real != $qty_batch) {
1218 $ObjBatch = new Productbatch($this->db);
1219 $ObjBatch->batch = $valueforundefinedlot;
1220 $ObjBatch->qty = ($ObjW->real - $qty_batch);
1221 $ObjBatch->fk_product_stock = $ObjW->id;
1222
1223 if ($ObjBatch->create($user, 1) < 0) {
1224 $error++;
1225 $this->errors = $ObjBatch->errors;
1226 } else {
1227 // we also add lot record if not exist
1228 $ObjLot = new Productlot($this->db);
1229 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
1230 if ($ObjLot->fetch(0, $this->id, $valueforundefinedlot) == 0) {
1231 $ObjLot->fk_product = $this->id;
1232 $ObjLot->entity = $this->entity;
1233 $ObjLot->fk_user_creat = $user->id;
1234 $ObjLot->batch = $valueforundefinedlot;
1235 if ($ObjLot->create($user, true) < 0) {
1236 $error++;
1237 $this->errors = $ObjLot->errors;
1238 }
1239 }
1240 }
1241 }
1242 }
1243 }
1244
1245 // For automatic creation
1246 if ($this->barcode == -1) {
1247 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1248 }
1249
1250 $sql = "UPDATE ".$this->db->prefix()."product";
1251 $sql .= " SET label = '".$this->db->escape($this->label)."'";
1252
1253 if ($updatetype && ($this->isProduct() || $this->isService())) {
1254 $sql .= ", fk_product_type = ".((int) $this->type);
1255 }
1256
1257 $sql .= ", ref = '".$this->db->escape($this->ref)."'";
1258 $sql .= ", ref_ext = ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1259 $sql .= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
1260 $sql .= ", tva_tx = ".((float) $this->tva_tx);
1261 $sql .= ", recuperableonly = ".((int) $this->tva_npr);
1262 $sql .= ", localtax1_tx = ".((float) $this->localtax1_tx);
1263 $sql .= ", localtax2_tx = ".((float) $this->localtax2_tx);
1264 $sql .= ", localtax1_type = ".($this->localtax1_type != '' ? "'".$this->db->escape($this->localtax1_type)."'" : "'0'");
1265 $sql .= ", localtax2_type = ".($this->localtax2_type != '' ? "'".$this->db->escape($this->localtax2_type)."'" : "'0'");
1266
1267 $sql .= ", barcode = ".(empty($this->barcode) ? "null" : "'".$this->db->escape($this->barcode)."'");
1268 $sql .= ", fk_barcode_type = ".(empty($this->barcode_type) ? "null" : $this->db->escape($this->barcode_type));
1269
1270 $sql .= ", tosell = ".(int) $this->status;
1271 $sql .= ", tobuy = ".(int) $this->status_buy;
1272 $sql .= ", tobatch = ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
1273 $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);
1274 $sql .= ", batch_mask = '".$this->db->escape($this->batch_mask)."'";
1275
1276 $sql .= ", finished = ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? "null" : (int) $this->finished);
1277 $sql .= ", fk_default_bom = ".((!isset($this->fk_default_bom) || $this->fk_default_bom < 0 || $this->fk_default_bom == '') ? "null" : (int) $this->fk_default_bom);
1278 $sql .= ", net_measure = ".($this->net_measure != '' ? "'".$this->db->escape($this->net_measure)."'" : 'null');
1279 $sql .= ", net_measure_units = ".($this->net_measure_units != '' ? "'".$this->db->escape($this->net_measure_units)."'" : 'null');
1280 $sql .= ", weight = ".($this->weight != '' ? "'".$this->db->escape($this->weight)."'" : 'null');
1281 $sql .= ", weight_units = ".($this->weight_units != '' ? "'".$this->db->escape($this->weight_units)."'" : 'null');
1282 $sql .= ", length = ".($this->length != '' ? "'".$this->db->escape($this->length)."'" : 'null');
1283 $sql .= ", length_units = ".($this->length_units != '' ? "'".$this->db->escape($this->length_units)."'" : 'null');
1284 $sql .= ", width= ".($this->width != '' ? "'".$this->db->escape($this->width)."'" : 'null');
1285 $sql .= ", width_units = ".($this->width_units != '' ? "'".$this->db->escape($this->width_units)."'" : 'null');
1286 $sql .= ", height = ".($this->height != '' ? "'".$this->db->escape($this->height)."'" : 'null');
1287 $sql .= ", height_units = ".($this->height_units != '' ? "'".$this->db->escape($this->height_units)."'" : 'null');
1288 $sql .= ", surface = ".($this->surface != '' ? "'".$this->db->escape($this->surface)."'" : 'null');
1289 $sql .= ", surface_units = ".($this->surface_units != '' ? "'".$this->db->escape($this->surface_units)."'" : 'null');
1290 $sql .= ", volume = ".($this->volume != '' ? "'".$this->db->escape($this->volume)."'" : 'null');
1291 $sql .= ", volume_units = ".($this->volume_units != '' ? "'".$this->db->escape($this->volume_units)."'" : 'null');
1292 $sql .= ", fk_default_warehouse = ".($this->fk_default_warehouse > 0 ? ((int) $this->fk_default_warehouse) : 'null');
1293 $sql .= ", fk_default_workstation = ".($this->fk_default_workstation > 0 ? ((int) $this->fk_default_workstation) : 'null');
1294 $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (float) $this->seuil_stock_alerte : 'null');
1295 $sql .= ", description = '".$this->db->escape($this->description)."'";
1296 $sql .= ", url = ".($this->url ? "'".$this->db->escape($this->url)."'" : 'null');
1297 $sql .= ", customcode = '".$this->db->escape($this->customcode)."'";
1298 $sql .= ", fk_country = ".($this->country_id > 0 ? (int) $this->country_id : 'null');
1299 $sql .= ", fk_state = ".($this->state_id > 0 ? (int) $this->state_id : 'null');
1300 $sql .= ", lifetime = ".($this->lifetime > 0 ? (int) $this->lifetime : 'null');
1301 $sql .= ", qc_frequency = ".($this->qc_frequency > 0 ? (int) $this->qc_frequency : 'null');
1302 $sql .= ", note = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : 'null');
1303 $sql .= ", note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : 'null');
1304 $sql .= ", duration = '".$this->db->escape($this->duration_value.$this->duration_unit)."'";
1305 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1306 $sql .= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy) . "'";
1307 $sql .= ", accountancy_code_buy_intra = '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1308 $sql .= ", accountancy_code_buy_export = '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1309 $sql .= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell) . "'";
1310 $sql .= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1311 $sql .= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1312 }
1313 $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (float) $this->desiredstock : "null");
1314 $sql .= ", cost_price = ".($this->cost_price != '' ? $this->db->escape($this->cost_price) : 'null');
1315 $sql .= ", fk_unit= ".(!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
1316 $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1);
1317 $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
1318 $sql .= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL');
1319 $sql .= ", mandatory_period = ".($this->mandatory_period);
1320 // stock field is not here because it is a denormalized value from product_stock.
1321 $sql .= " WHERE rowid = ".((int) $id);
1322
1323 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1324
1325 $resql = $this->db->query($sql);
1326 if ($resql) {
1327 $this->id = $id;
1328
1329 // Multilangs
1330 if (getDolGlobalInt('MAIN_MULTILANGS')) {
1331 if ($this->setMultiLangs($user) < 0) {
1332 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1333 $this->db->rollback();
1334 return -2;
1335 }
1336 }
1337
1338 $action = 'update';
1339
1340 // update accountancy for this entity
1341 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1342 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " . ((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1343
1344 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1345 $sql .= " fk_product";
1346 $sql .= ", entity";
1347 $sql .= ", accountancy_code_buy";
1348 $sql .= ", accountancy_code_buy_intra";
1349 $sql .= ", accountancy_code_buy_export";
1350 $sql .= ", accountancy_code_sell";
1351 $sql .= ", accountancy_code_sell_intra";
1352 $sql .= ", accountancy_code_sell_export";
1353 $sql .= ") VALUES (";
1354 $sql .= $this->id;
1355 $sql .= ", " . $conf->entity;
1356 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1357 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1358 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1359 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1360 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1361 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1362 $sql .= ")";
1363 $result = $this->db->query($sql);
1364 if (!$result) {
1365 $error++;
1366 $this->error = 'ErrorFailedToUpdateAccountancyForEntity';
1367 }
1368 }
1369
1370 if (!$this->hasbatch() && $this->oldcopy->hasbatch()) {
1371 // Selection of all product stock movements that contains batchs
1372 $sql = 'SELECT pb.qty, ps.fk_entrepot, pb.batch FROM '.MAIN_DB_PREFIX.'product_batch as pb';
1373 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_stock as ps ON (ps.rowid = pb.fk_product_stock)';
1374 $sql .= ' WHERE ps.fk_product = '.(int) $this->id;
1375
1376 $resql = $this->db->query($sql);
1377 if ($resql) {
1378 $inventorycode = dol_print_date(dol_now(), '%Y%m%d%H%M%S');
1379
1380 while ($obj = $this->db->fetch_object($resql)) {
1381 $value = $obj->qty;
1382 $fk_entrepot = $obj->fk_entrepot;
1383 $price = 0;
1384 $dlc = '';
1385 $dluo = '';
1386 $batch = $obj->batch;
1387
1388 // To know how to revert stockMouvement (add or remove)
1389 $addOremove = $value > 0 ? 1 : 0; // 1 if remove, 0 if add
1390 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1391 $res = $this->correct_stock_batch($user, $fk_entrepot, abs($value), $addOremove, $label, $price, $dlc, $dluo, $batch, $inventorycode, '', null, 0, null, true);
1392
1393 if ($res > 0) {
1394 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1395 $res = $this->correct_stock($user, $fk_entrepot, abs($value), (int) empty($addOremove), $label, $price, $inventorycode, '', null, 0);
1396 if ($res < 0) {
1397 $error++;
1398 }
1399 } else {
1400 $error++;
1401 }
1402 }
1403 }
1404 }
1405
1406 // Actions on extra fields
1407 if (!$error) {
1408 $result = $this->insertExtraFields();
1409 if ($result < 0) {
1410 $error++;
1411 }
1412 }
1413
1414 if (!$error && !$notrigger) {
1415 // Call trigger
1416 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1417 if ($result < 0) {
1418 $error++;
1419 }
1420 // End call triggers
1421 }
1422
1423 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
1424 // We remove directory
1425 if ($conf->product->dir_output) {
1426 $olddir = $conf->product->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
1427 $newdir = $conf->product->dir_output."/".dol_sanitizeFileName($this->ref);
1428 if (file_exists($olddir)) {
1429 //include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1430 //$res = dol_move($olddir, $newdir);
1431 // do not use dol_move with directory
1432 $res = @rename($olddir, $newdir);
1433 if (!$res) {
1434 $langs->load("errors");
1435 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
1436 $error++;
1437 }
1438 }
1439 }
1440 }
1441
1442 if (!$error) {
1443 if (isModEnabled('variants')) {
1444 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1445
1446 $comb = new ProductCombination($this->db);
1447
1448 foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1449 $currcomb->updateProperties($this, $user);
1450 }
1451 }
1452
1453 $this->db->commit();
1454 return 1;
1455 } else {
1456 $this->db->rollback();
1457 return -$error;
1458 }
1459 } else {
1460 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1461 $langs->load("errors");
1462 if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1463 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1464 } else {
1465 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1466 }
1467 $this->errors[] = $this->error;
1468 $this->db->rollback();
1469 return -1;
1470 } else {
1471 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1472 $this->errors[] = $this->error;
1473 $this->db->rollback();
1474 return -2;
1475 }
1476 }
1477 } else {
1478 $this->db->rollback();
1479 dol_syslog(get_class($this)."::Update fails verify ".implode(',', $this->errors), LOG_WARNING);
1480 return -3;
1481 }
1482 }
1483
1491 public function delete(User $user, $notrigger = 0)
1492 {
1493 global $conf, $langs;
1494 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1495
1496 $error = 0;
1497
1498 // Check parameters
1499 if (empty($this->id)) {
1500 $this->error = "Object must be fetched before calling delete";
1501 return -1;
1502 }
1503 if (($this->type == Product::TYPE_PRODUCT && !$user->hasRight('produit', 'supprimer')) || ($this->type == Product::TYPE_SERVICE && !$user->hasRight('service', 'supprimer'))) {
1504 $this->error = "ErrorForbidden";
1505 return 0;
1506 }
1507
1508 $objectisused = $this->isObjectUsed($this->id);
1509 if (empty($objectisused)) {
1510 $this->db->begin();
1511
1512 if (!$error && empty($notrigger)) {
1513 // Call trigger
1514 $result = $this->call_trigger('PRODUCT_DELETE', $user);
1515 if ($result < 0) {
1516 $error++;
1517 }
1518 // End call triggers
1519 }
1520
1521 // Delete from product_batch on product delete
1522 if (!$error) {
1523 $sql = "DELETE FROM ".$this->db->prefix().'product_batch';
1524 $sql .= " WHERE fk_product_stock IN (";
1525 $sql .= "SELECT rowid FROM ".$this->db->prefix().'product_stock';
1526 $sql .= " WHERE fk_product = ".((int) $this->id).")";
1527
1528 $result = $this->db->query($sql);
1529 if (!$result) {
1530 $error++;
1531 $this->errors[] = $this->db->lasterror();
1532 }
1533 }
1534
1535 // Delete all child tables
1536 if (!$error) {
1537 $elements = array('product_fournisseur_price', 'product_price', 'product_lang', 'categorie_product', 'product_stock', 'product_customer_price', 'product_lot'); // product_batch is done before
1538 foreach ($elements as $table) {
1539 if (!$error) {
1540 $sql = "DELETE FROM ".$this->db->prefix().$table;
1541 $sql .= " WHERE fk_product = ".(int) $this->id;
1542
1543 $result = $this->db->query($sql);
1544 if (!$result) {
1545 $error++;
1546 $this->errors[] = $this->db->lasterror();
1547 }
1548 }
1549 }
1550 }
1551
1552 if (!$error) {
1553 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1554 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1555
1556 //If it is a parent product, then we remove the association with child products
1557 $prodcomb = new ProductCombination($this->db);
1558
1559 if ($prodcomb->deleteByFkProductParent($user, $this->id) < 0) {
1560 $error++;
1561 $this->errors[] = 'Error deleting combinations';
1562 }
1563
1564 //We also check if it is a child product
1565 if (!$error && ($prodcomb->fetchByFkProductChild($this->id) > 0) && ($prodcomb->delete($user) < 0)) {
1566 $error++;
1567 $this->errors[] = 'Error deleting child combination';
1568 }
1569 }
1570
1571 // Delete from product_association
1572 if (!$error) {
1573 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
1574 $sql .= " WHERE fk_product_pere = ".(int) $this->id." OR fk_product_fils = ".(int) $this->id;
1575
1576 $result = $this->db->query($sql);
1577 if (!$result) {
1578 $error++;
1579 $this->errors[] = $this->db->lasterror();
1580 }
1581 }
1582
1583 // Remove extrafields
1584 if (!$error) {
1585 $result = $this->deleteExtraFields();
1586 if ($result < 0) {
1587 $error++;
1588 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1589 }
1590 }
1591
1592 // Delete product
1593 if (!$error) {
1594 $sqlz = "DELETE FROM ".$this->db->prefix()."product";
1595 $sqlz .= " WHERE rowid = ".(int) $this->id;
1596
1597 $resultz = $this->db->query($sqlz);
1598 if (!$resultz) {
1599 $error++;
1600 $this->errors[] = $this->db->lasterror();
1601 }
1602 }
1603
1604 // Delete record into ECM index and physically
1605 if (!$error) {
1606 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1607 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1608 if (!$res) {
1609 $error++;
1610 }
1611 }
1612
1613 if (!$error) {
1614 // We remove directory
1615 $ref = dol_sanitizeFileName($this->ref);
1616 if ($conf->product->dir_output) {
1617 $dir = $conf->product->dir_output."/".$ref;
1618 if (file_exists($dir)) {
1619 $res = @dol_delete_dir_recursive($dir);
1620 if (!$res) {
1621 $this->errors[] = 'ErrorFailToDeleteDir';
1622 $error++;
1623 }
1624 }
1625 }
1626 }
1627
1628 if (!$error) {
1629 $this->db->commit();
1630 return 1;
1631 } else {
1632 foreach ($this->errors as $errmsg) {
1633 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1634 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1635 }
1636 $this->db->rollback();
1637 return -$error;
1638 }
1639 } else {
1640 $this->error = "ErrorRecordIsUsedCantDelete";
1641 return 0;
1642 }
1643 }
1644
1650 public static function getSellOrEatByMandatoryList()
1651 {
1652 global $langs;
1653
1654 $sellByLabel = $langs->trans('SellByDate');
1655 $eatByLabel = $langs->trans('EatByDate');
1656 return array(
1657 self::SELL_OR_EAT_BY_MANDATORY_ID_NONE => $langs->trans('BatchSellOrEatByMandatoryNone'),
1658 self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY => $sellByLabel,
1659 self::SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY => $eatByLabel,
1660 self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT => $langs->trans('BatchSellOrEatByMandatoryAll', $sellByLabel, $eatByLabel),
1661 );
1662 }
1663
1670 {
1671 $sellOrEatByMandatoryLabel = '';
1672
1673 $sellOrEatByMandatoryList = self::getSellOrEatByMandatoryList();
1674 if (isset($sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory])) {
1675 $sellOrEatByMandatoryLabel = $sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory];
1676 }
1677
1678 return $sellOrEatByMandatoryLabel;
1679 }
1680
1687 public function setMultiLangs($user)
1688 {
1689 global $conf, $langs;
1690
1691 $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1692 $current_lang = $langs->getDefaultLang();
1693
1694 foreach ($langs_available as $key => $value) {
1695 if ($key == $current_lang) {
1696 $sql = "SELECT rowid";
1697 $sql .= " FROM ".$this->db->prefix()."product_lang";
1698 $sql .= " WHERE fk_product = ".((int) $this->id);
1699 $sql .= " AND lang = '".$this->db->escape($key)."'";
1700
1701 $result = $this->db->query($sql);
1702
1703 if ($this->db->num_rows($result)) { // if there is already a description line for this language
1704 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1705 $sql2 .= " SET ";
1706 $sql2 .= " label='".$this->db->escape($this->label)."',";
1707 $sql2 .= " description='".$this->db->escape($this->description)."'";
1708 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1709 $sql2 .= ", note='".$this->db->escape($this->other)."'";
1710 }
1711 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1712 } else {
1713 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1714 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1715 $sql2 .= ", note";
1716 }
1717 $sql2 .= ")";
1718 $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->label)."',";
1719 $sql2 .= " '".$this->db->escape($this->description)."'";
1720 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1721 $sql2 .= ", '".$this->db->escape($this->other)."'";
1722 }
1723 $sql2 .= ")";
1724 }
1725 dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
1726 if (!$this->db->query($sql2)) {
1727 $this->error = $this->db->lasterror();
1728 return -1;
1729 }
1730 } elseif (isset($this->multilangs[$key])) {
1731 if (empty($this->multilangs["$key"]["label"])) {
1732 $this->errors[] = $key . ' : ' . $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Label"));
1733 return -1;
1734 }
1735
1736 $sql = "SELECT rowid";
1737 $sql .= " FROM ".$this->db->prefix()."product_lang";
1738 $sql .= " WHERE fk_product = ".((int) $this->id);
1739 $sql .= " AND lang = '".$this->db->escape($key)."'";
1740
1741 $result = $this->db->query($sql);
1742
1743 if ($this->db->num_rows($result)) { // if there is already a description line for this language
1744 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1745 $sql2 .= " SET ";
1746 $sql2 .= " label = '".$this->db->escape($this->multilangs["$key"]["label"])."',";
1747 $sql2 .= " description = '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1748 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1749 $sql2 .= ", note = '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1750 }
1751 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1752 } else {
1753 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1754 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1755 $sql2 .= ", note";
1756 }
1757 $sql2 .= ")";
1758 $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->multilangs["$key"]["label"])."',";
1759 $sql2 .= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1760 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1761 $sql2 .= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1762 }
1763 $sql2 .= ")";
1764 }
1765
1766 // We do not save if main fields are empty
1767 if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
1768 if (!$this->db->query($sql2)) {
1769 $this->error = $this->db->lasterror();
1770 return -1;
1771 }
1772 }
1773 } else {
1774 // language is not current language and we didn't provide a multilang description for this language
1775 }
1776 }
1777
1778 // Call trigger
1779 $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
1780 if ($result < 0) {
1781 $this->error = $this->db->lasterror();
1782 return -1;
1783 }
1784 // End call triggers
1785
1786 return 1;
1787 }
1788
1797 public function delMultiLangs($langtodelete, $user)
1798 {
1799 $sql = "DELETE FROM ".$this->db->prefix()."product_lang";
1800 $sql .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
1801
1802 dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
1803 $result = $this->db->query($sql);
1804 if ($result) {
1805 // Call trigger
1806 $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
1807 if ($result < 0) {
1808 $this->error = $this->db->lasterror();
1809 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1810 return -1;
1811 }
1812 // End call triggers
1813 return 1;
1814 } else {
1815 $this->error = $this->db->lasterror();
1816 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1817 return -1;
1818 }
1819 }
1820
1829 public function setAccountancyCode($type, $value)
1830 {
1831 global $user, $langs, $conf;
1832
1833 $error = 0;
1834
1835 $this->db->begin();
1836
1837 if ($type == 'buy') {
1838 $field = 'accountancy_code_buy';
1839 } elseif ($type == 'buy_intra') {
1840 $field = 'accountancy_code_buy_intra';
1841 } elseif ($type == 'buy_export') {
1842 $field = 'accountancy_code_buy_export';
1843 } elseif ($type == 'sell') {
1844 $field = 'accountancy_code_sell';
1845 } elseif ($type == 'sell_intra') {
1846 $field = 'accountancy_code_sell_intra';
1847 } elseif ($type == 'sell_export') {
1848 $field = 'accountancy_code_sell_export';
1849 } else {
1850 return -1;
1851 }
1852
1853 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ";
1854 $sql .= "$field = '".$this->db->escape($value)."'";
1855 $sql .= " WHERE rowid = ".((int) $this->id);
1856
1857 dol_syslog(__METHOD__, LOG_DEBUG);
1858 $resql = $this->db->query($sql);
1859
1860 if ($resql) {
1861 // Call trigger
1862 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1863 if ($result < 0) {
1864 $error++;
1865 }
1866 // End call triggers
1867
1868 if ($error) {
1869 $this->db->rollback();
1870 return -1;
1871 }
1872
1873 $this->$field = $value;
1874
1875 $this->db->commit();
1876 return 1;
1877 } else {
1878 $this->error = $this->db->lasterror();
1879 $this->db->rollback();
1880 return -1;
1881 }
1882 }
1883
1889 public function getMultiLangs()
1890 {
1891 global $langs;
1892
1893 $current_lang = $langs->getDefaultLang();
1894
1895 $sql = "SELECT lang, label, description, note as other";
1896 $sql .= " FROM ".$this->db->prefix()."product_lang";
1897 $sql .= " WHERE fk_product = ".((int) $this->id);
1898
1899 $result = $this->db->query($sql);
1900 if ($result) {
1901 while ($obj = $this->db->fetch_object($result)) {
1902 //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
1903 if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
1904 $this->label = $obj->label;
1905 $this->description = $obj->description;
1906 $this->other = $obj->other;
1907 }
1908 $this->multilangs[(string) $obj->lang]["label"] = $obj->label;
1909 $this->multilangs[(string) $obj->lang]["description"] = $obj->description;
1910 $this->multilangs[(string) $obj->lang]["other"] = $obj->other;
1911 }
1912 return 1;
1913 } else {
1914 $this->error = "Error: ".$this->db->lasterror()." - ".$sql;
1915 return -1;
1916 }
1917 }
1918
1925 private function getArrayForPriceCompare($level = 0)
1926 {
1927 $testExit = array('multiprices','multiprices_ttc','multiprices_base_type','multiprices_min','multiprices_min_ttc','multiprices_tva_tx','multiprices_recuperableonly');
1928
1929 foreach ($testExit as $field) {
1930 if (!isset($this->$field)) {
1931 return array();
1932 }
1933 $tmparray = $this->$field;
1934 if (!isset($tmparray[$level])) {
1935 return array();
1936 }
1937 }
1938
1939 $lastPrice = array(
1940 'level' => $level ? $level : 1,
1941 'multiprices' => (float) $this->multiprices[$level],
1942 'multiprices_ttc' => (float) $this->multiprices_ttc[$level],
1943 'multiprices_base_type' => $this->multiprices_base_type[$level],
1944 'multiprices_min' => (float) $this->multiprices_min[$level],
1945 'multiprices_min_ttc' => (float) $this->multiprices_min_ttc[$level],
1946 'multiprices_tva_tx' => (float) $this->multiprices_tva_tx[$level],
1947 'multiprices_recuperableonly' => (float) $this->multiprices_recuperableonly[$level],
1948 );
1949
1950 return $lastPrice;
1951 }
1952
1953
1954 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1962 private function _log_price($user, $level = 0)
1963 {
1964 // phpcs:enable
1965 global $conf;
1966
1967 $now = dol_now();
1968
1969 // Clean parameters
1970 if (empty($this->price_by_qty)) {
1971 $this->price_by_qty = 0;
1972 }
1973
1974 // Add new price
1975 $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,";
1976 $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
1977 $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).",";
1978 $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');
1979 $sql .= ")";
1980
1981 dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
1982 $resql = $this->db->query($sql);
1983 if (!$resql) {
1984 $this->error = $this->db->lasterror();
1985 dol_print_error($this->db);
1986 return -1;
1987 } else {
1988 return 1;
1989 }
1990 }
1991
1992
1993 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2001 public function log_price_delete($user, $rowid)
2002 {
2003 // phpcs:enable
2004 $sql = "DELETE FROM ".$this->db->prefix()."product_price_by_qty";
2005 $sql .= " WHERE fk_product_price = ".((int) $rowid);
2006 $resql = $this->db->query($sql);
2007
2008 $sql = "DELETE FROM ".$this->db->prefix()."product_price";
2009 $sql .= " WHERE rowid=".((int) $rowid);
2010 $resql = $this->db->query($sql);
2011 if ($resql) {
2012 return 1;
2013 } else {
2014 $this->error = $this->db->lasterror();
2015 return -1;
2016 }
2017 }
2018
2019
2029 public function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp = 0)
2030 {
2031 global $conf, $hookmanager, $action;
2032
2033 // Call hook if any
2034 if (is_object($hookmanager)) {
2035 $parameters = array('thirdparty_seller' => $thirdparty_seller, 'thirdparty_buyer' => $thirdparty_buyer, 'pqp' => $pqp);
2036 // Note that $action and $object may have been modified by some hooks
2037 $reshook = $hookmanager->executeHooks('getSellPrice', $parameters, $this, $action);
2038 if ($reshook > 0) {
2039 return $hookmanager->resArray;
2040 }
2041 }
2042
2043 // Update if prices fields are defined
2044 $tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id);
2045 $tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id);
2046 if (empty($tva_tx)) {
2047 $tva_npr = 0;
2048 }
2049
2050 $pu_ht = $this->price;
2051 $pu_ttc = $this->price_ttc;
2052 $price_min = $this->price_min;
2053 $price_base_type = $this->price_base_type;
2054
2055 // If price per segment
2056 if (getDolGlobalString('PRODUIT_MULTIPRICES') && !empty($thirdparty_buyer->price_level)) {
2057 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
2058 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
2059 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
2060 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2061 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
2062 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2063 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2064 }
2065 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2066 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2067 }
2068 if (empty($tva_tx)) {
2069 $tva_npr = 0;
2070 }
2071 }
2072 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
2073 // If price per customer
2074 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2075
2076 $prodcustprice = new ProductCustomerPrice($this->db);
2077
2078 $filter = array('t.fk_product' => $this->id, 't.fk_soc' => $thirdparty_buyer->id);
2079
2080 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2081 if ($result) {
2082 if (count($prodcustprice->lines) > 0) {
2083 $pu_ht = price($prodcustprice->lines[0]->price);
2084 $price_min = price($prodcustprice->lines[0]->price_min);
2085 $pu_ttc = price($prodcustprice->lines[0]->price_ttc);
2086 $price_base_type = $prodcustprice->lines[0]->price_base_type;
2087 $tva_tx = $prodcustprice->lines[0]->tva_tx;
2088 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2089 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
2090 }
2091 $tva_npr = $prodcustprice->lines[0]->recuperableonly;
2092 if (empty($tva_tx)) {
2093 $tva_npr = 0;
2094 }
2095 }
2096 }
2097 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY')) {
2098 // If price per quantity
2099 if ($this->prices_by_qty[0]) {
2100 // yes, this product has some prices per quantity
2101 // Search price into product_price_by_qty from $this->id
2102 foreach ($this->prices_by_qty_list[0] as $priceforthequantityarray) {
2103 if ($priceforthequantityarray['rowid'] != $pqp) {
2104 continue;
2105 }
2106 // We found the price
2107 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2108 $pu_ht = $priceforthequantityarray['unitprice'];
2109 } else {
2110 $pu_ttc = $priceforthequantityarray['unitprice'];
2111 }
2112 break;
2113 }
2114 }
2115 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
2116 // If price per quantity and customer
2117 if ($this->prices_by_qty[$thirdparty_buyer->price_level]) {
2118 // yes, this product has some prices per quantity
2119 // Search price into product_price_by_qty from $this->id
2120 foreach ($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray) {
2121 if ($priceforthequantityarray['rowid'] != $pqp) {
2122 continue;
2123 }
2124 // We found the price
2125 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2126 $pu_ht = $priceforthequantityarray['unitprice'];
2127 } else {
2128 $pu_ttc = $priceforthequantityarray['unitprice'];
2129 }
2130 break;
2131 }
2132 }
2133 }
2134
2135 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);
2136 }
2137
2138 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2152 public function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
2153 {
2154 // phpcs:enable
2155 global $action, $hookmanager;
2156
2157 // Call hook if any
2158 if (is_object($hookmanager)) {
2159 $parameters = array(
2160 'prodfournprice' => $prodfournprice,
2161 'qty' => $qty,
2162 'product_id' => $product_id,
2163 'fourn_ref' => $fourn_ref,
2164 'fk_soc' => $fk_soc,
2165 );
2166 // Note that $action and $object may have been modified by some hooks
2167 $reshook = $hookmanager->executeHooks('getBuyPrice', $parameters, $this, $action);
2168 if ($reshook > 0) {
2169 return $hookmanager->resArray;
2170 }
2171 }
2172
2173 $result = 0;
2174
2175 // 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)
2176 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2177 $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,";
2178 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2179 $sql .= " pfp.packaging";
2180 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2181 $sql .= " WHERE pfp.rowid = ".((int) $prodfournprice);
2182 if ($qty > 0) {
2183 $sql .= " AND pfp.quantity <= ".((float) $qty);
2184 }
2185 $sql .= " ORDER BY pfp.quantity DESC";
2186
2187 dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
2188 $resql = $this->db->query($sql);
2189 if ($resql) {
2190 $obj = $this->db->fetch_object($resql);
2191 if ($obj && $obj->quantity > 0) { // If we found a supplier prices from the id of supplier price
2192 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2193 $prod_supplier = new ProductFournisseur($this->db);
2194 $prod_supplier->product_fourn_price_id = $obj->rowid;
2195 $prod_supplier->id = $obj->fk_product;
2196 $prod_supplier->fourn_qty = $obj->quantity;
2197 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2198 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2199
2200 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2201 $priceparser = new PriceParser($this->db);
2202 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2203 if ($price_result >= 0) {
2204 $obj->price = $price_result;
2205 }
2206 }
2207 $this->product_fourn_price_id = $obj->rowid;
2208 $this->buyprice = $obj->price; // deprecated
2209 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
2210 $this->fourn_price_base_type = 'HT'; // Price base type
2211 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2212 $this->ref_fourn = $obj->ref_supplier; // deprecated
2213 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2214 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2215 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2216 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2217 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2218 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2219 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2220 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2221 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2222 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2223 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2224 $this->packaging = $obj->packaging;
2225 }
2226 $result = $obj->fk_product;
2227 return $result;
2228 } else { // If not found
2229 // 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.
2230 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2231 $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,";
2232 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2233 $sql .= " pfp.packaging";
2234 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2235 $sql .= " WHERE 1 = 1";
2236 if ($product_id > 0) {
2237 $sql .= " AND pfp.fk_product = ".((int) $product_id);
2238 }
2239 if ($fourn_ref != 'none') {
2240 $sql .= " AND pfp.ref_fourn = '".$this->db->escape($fourn_ref)."'";
2241 }
2242 if ($fk_soc > 0) {
2243 $sql .= " AND pfp.fk_soc = ".((int) $fk_soc);
2244 }
2245 if ($qty > 0) {
2246 $sql .= " AND pfp.quantity <= ".((float) $qty);
2247 }
2248 $sql .= " ORDER BY pfp.quantity DESC";
2249 $sql .= " LIMIT 1";
2250
2251 dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
2252 $resql = $this->db->query($sql);
2253 if ($resql) {
2254 $obj = $this->db->fetch_object($resql);
2255 if ($obj && $obj->quantity > 0) { // If found
2256 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2257 $prod_supplier = new ProductFournisseur($this->db);
2258 $prod_supplier->product_fourn_price_id = $obj->rowid;
2259 $prod_supplier->id = $obj->fk_product;
2260 $prod_supplier->fourn_qty = $obj->quantity;
2261 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2262 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2263
2264 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2265 $priceparser = new PriceParser($this->db);
2266 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2267 if ($result >= 0) {
2268 $obj->price = $price_result;
2269 }
2270 }
2271 $this->product_fourn_price_id = $obj->rowid;
2272 $this->buyprice = $obj->price; // deprecated
2273 $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
2274 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
2275 $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
2276 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2277 $this->ref_fourn = $obj->ref_supplier; // deprecated
2278 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2279 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2280 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2281 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2282 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2283 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2284 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2285 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2286 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2287 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2288 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2289 $this->packaging = $obj->packaging;
2290 }
2291 $result = $obj->fk_product;
2292 return $result;
2293 } else {
2294 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é.
2295 }
2296 } else {
2297 $this->error = $this->db->lasterror();
2298 return -3;
2299 }
2300 }
2301 } else {
2302 $this->error = $this->db->lasterror();
2303 return -2;
2304 }
2305 }
2306
2307
2326 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)
2327 {
2328 global $conf, $langs;
2329
2330 $lastPriceData = $this->getArrayForPriceCompare($level); // temporary store current price before update
2331
2332 $id = $this->id;
2333
2334 dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
2335
2336 // Clean parameters
2337 if (empty($this->tva_tx)) {
2338 $this->tva_tx = 0;
2339 }
2340 if (empty($newnpr)) {
2341 $newnpr = 0;
2342 }
2343 if (empty($newminprice)) {
2344 $newminprice = 0;
2345 }
2346
2347 // Check parameters
2348 if ($newvat === null || $newvat == '') { // Maintain '' for backwards compatibility
2349 $newvat = $this->tva_tx;
2350 }
2351
2352 // If multiprices are enabled, then we check if the current product is subject to price autogeneration
2353 // Price will be modified ONLY when the first one is the one that is being modified
2354 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
2355 return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
2356 }
2357
2358 if (!empty($newminprice) && ($newminprice > $newprice)) {
2359 $this->error = 'ErrorPriceCantBeLowerThanMinPrice';
2360 return -1;
2361 }
2362
2363 if ($newprice !== '' || $newprice === 0) {
2364 if ($newpricebase == 'TTC') {
2365 $price_ttc = price2num($newprice, 'MU');
2366 $price = (float) price2num($newprice) / (1 + ((float) $newvat / 100));
2367 $price = price2num($price, 'MU');
2368
2369 if ($newminprice != '' || $newminprice == 0) {
2370 $price_min_ttc = price2num($newminprice, 'MU');
2371 $price_min = (float) price2num($newminprice) / (1 + ($newvat / 100));
2372 $price_min = price2num($price_min, 'MU');
2373 } else {
2374 $price_min = 0;
2375 $price_min_ttc = 0;
2376 }
2377 } else {
2378 $price = (float) price2num($newprice, 'MU');
2379 $price_ttc = ($newnpr != 1) ? price2num($newprice) * (1 + ($newvat / 100)) : $price;
2380 $price_ttc = (float) price2num($price_ttc, 'MU');
2381
2382 if ($newminprice !== '' || $newminprice === 0) {
2383 $price_min = price2num($newminprice, 'MU');
2384 $price_min_ttc = (float) price2num($newminprice) * (1 + ($newvat / 100));
2385 $price_min_ttc = price2num($price_min_ttc, 'MU');
2386 //print 'X'.$newminprice.'-'.$price_min;
2387 } else {
2388 $price_min = 0;
2389 $price_min_ttc = 0;
2390 }
2391 }
2392 //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
2393
2394 if (count($localtaxes_array) > 0) {
2395 $localtaxtype1 = $localtaxes_array['0'];
2396 $localtax1 = $localtaxes_array['1'];
2397 $localtaxtype2 = $localtaxes_array['2'];
2398 $localtax2 = $localtaxes_array['3'];
2399 } else {
2400 // if array empty, we try to use the vat code
2401 if (!empty($newdefaultvatcode)) {
2402 global $mysoc;
2403 // Get record from code
2404 $sql = "SELECT t.rowid, t.code, t.recuperableonly as tva_npr, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
2405 $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
2406 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$this->db->escape($mysoc->country_code)."'";
2407 $sql .= " AND t.taux = ".((float) $newdefaultvatcode)." AND t.active = 1";
2408 $sql .= " AND t.code = '".$this->db->escape($newdefaultvatcode)."'";
2409 $resql = $this->db->query($sql);
2410 if ($resql) {
2411 $obj = $this->db->fetch_object($resql);
2412 if ($obj) {
2413 $npr = $obj->tva_npr;
2414 $localtax1 = $obj->localtax1;
2415 $localtax2 = $obj->localtax2;
2416 $localtaxtype1 = $obj->localtax1_type;
2417 $localtaxtype2 = $obj->localtax2_type;
2418 }
2419 }
2420 } else {
2421 // old method. deprecated because we can't retrieve type
2422 $localtaxtype1 = '0';
2423 $localtax1 = get_localtax($newvat, 1);
2424 $localtaxtype2 = '0';
2425 $localtax2 = get_localtax($newvat, 2);
2426 }
2427 }
2428 if (empty($localtax1)) {
2429 $localtax1 = 0; // If = '' then = 0
2430 }
2431 if (empty($localtax2)) {
2432 $localtax2 = 0; // If = '' then = 0
2433 }
2434
2435 $this->db->begin();
2436
2437 // Ne pas mettre de quote sur les numeriques decimaux.
2438 // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
2439 $sql = "UPDATE ".$this->db->prefix()."product SET";
2440 $sql .= " price_base_type = '".$this->db->escape($newpricebase)."',";
2441 $sql .= " price = ".(float) $price.",";
2442 $sql .= " price_ttc = ".(float) $price_ttc.",";
2443 $sql .= " price_min = ".(float) $price_min.",";
2444 $sql .= " price_min_ttc = ".(float) $price_min_ttc.",";
2445 $sql .= " localtax1_tx = ".($localtax1 >= 0 ? (float) $localtax1 : 'NULL').",";
2446 $sql .= " localtax2_tx = ".($localtax2 >= 0 ? (float) $localtax2 : 'NULL').",";
2447 $sql .= " localtax1_type = ".($localtaxtype1 != '' ? "'".$this->db->escape($localtaxtype1)."'" : "'0'").",";
2448 $sql .= " localtax2_type = ".($localtaxtype2 != '' ? "'".$this->db->escape($localtaxtype2)."'" : "'0'").",";
2449 $sql .= " default_vat_code = ".($newdefaultvatcode ? "'".$this->db->escape($newdefaultvatcode)."'" : "null").",";
2450 $sql .= " price_label = ".(!empty($price_label) ? "'".$this->db->escape($price_label)."'" : "null").",";
2451 $sql .= " tva_tx = ".(float) price2num($newvat).",";
2452 $sql .= " recuperableonly = '".$this->db->escape($newnpr)."'";
2453 $sql .= " WHERE rowid = ".((int) $id);
2454
2455 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2456 $resql = $this->db->query($sql);
2457 if ($resql) {
2458 $this->multiprices[$level] = $price;
2459 $this->multiprices_ttc[$level] = $price_ttc;
2460 $this->multiprices_min[$level] = $price_min;
2461 $this->multiprices_min_ttc[$level] = $price_min_ttc;
2462 $this->multiprices_base_type[$level] = $newpricebase;
2463 $this->multiprices_default_vat_code[$level] = $newdefaultvatcode;
2464 $this->multiprices_tva_tx[$level] = $newvat;
2465 $this->multiprices_recuperableonly[$level] = $newnpr;
2466
2467 $this->price = $price;
2468 $this->price_label = $price_label;
2469 $this->price_ttc = $price_ttc;
2470 $this->price_min = $price_min;
2471 $this->price_min_ttc = $price_min_ttc;
2472 $this->price_base_type = $newpricebase;
2473 $this->default_vat_code = $newdefaultvatcode;
2474 $this->tva_tx = $newvat;
2475 $this->tva_npr = $newnpr;
2476
2477 //Local taxes
2478 $this->localtax1_tx = $localtax1;
2479 $this->localtax2_tx = $localtax2;
2480 $this->localtax1_type = $localtaxtype1;
2481 $this->localtax2_type = $localtaxtype2;
2482
2483 // Price by quantity
2484 $this->price_by_qty = $newpbq;
2485
2486 // check if price have really change before log
2487 $newPriceData = $this->getArrayForPriceCompare($level);
2488 if (!empty(array_diff_assoc($newPriceData, $lastPriceData)) || !getDolGlobalString('PRODUIT_MULTIPRICES')) {
2489 $this->_log_price($user, $level); // Save price for level into table product_price
2490 }
2491
2492 $this->level = $level; // Store level of price edited for trigger
2493
2494 // Call trigger
2495 if (!$notrigger) {
2496 $result = $this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
2497 if ($result < 0) {
2498 $this->db->rollback();
2499 return -1;
2500 }
2501 }
2502 // End call triggers
2503
2504 $this->db->commit();
2505 } else {
2506 $this->db->rollback();
2507 $this->error = $this->db->lasterror();
2508 return -1;
2509 }
2510 }
2511
2512 return 1;
2513 }
2514
2522 public function setPriceExpression($expression_id)
2523 {
2524 global $user;
2525
2526 $this->fk_price_expression = $expression_id;
2527
2528 return $this->update($this->id, $user);
2529 }
2530
2543 public function fetch($id = 0, $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0, $ignore_price_load = 0, $ignore_lang_load = 0)
2544 {
2545 include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2546
2547 global $langs, $conf;
2548
2549 dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
2550
2551 // Check parameters
2552 if (!$id && !$ref && !$ref_ext && !$barcode) {
2553 $this->error = 'ErrorWrongParameters';
2554 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2555 return -1;
2556 }
2557
2558 $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,";
2559 $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,";
2560 $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,";
2561 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.last_main_doc,";
2562 $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,";
2563 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2564 $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,";
2565 } else {
2566 $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,";
2567 }
2568
2569 //For MultiCompany
2570 //PMP per entity & Stocks Sharings stock_reel includes only stocks shared with this entity
2571 $separatedEntityPMP = false; // Set to true to get the AWP from table llx_product_perentity instead of field 'pmp' into llx_product.
2572 $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.
2573 $visibleWarehousesEntities = $conf->entity;
2574 if (getDolGlobalString('MULTICOMPANY_PRODUCT_SHARING_ENABLED')) {
2575 if (getDolGlobalString('MULTICOMPANY_PMP_PER_ENTITY_ENABLED')) {
2576 $checkPMPPerEntity = $this->db->query("SELECT pmp FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = ".((int) $id)." AND entity = ".(int) $conf->entity);
2577 if ($this->db->num_rows($checkPMPPerEntity) > 0) {
2578 $separatedEntityPMP = true;
2579 }
2580 }
2581 global $mc;
2582 $separatedStock = true;
2583 if (isset($mc->sharings['stock']) && !empty($mc->sharings['stock'])) {
2584 $visibleWarehousesEntities .= "," . implode(",", $mc->sharings['stock']);
2585 }
2586 }
2587 if ($separatedEntityPMP) {
2588 $sql .= " ppe.pmp,";
2589 } else {
2590 $sql .= " p.pmp,";
2591 }
2592 $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,";
2593 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf,";
2594 $sql .= " p.price_label,";
2595 if ($separatedStock) {
2596 $sql .= " SUM(sp.reel) as stock";
2597 } else {
2598 $sql .= " p.stock";
2599 }
2600 $sql .= " FROM ".$this->db->prefix()."product as p";
2601 if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') || $separatedEntityPMP) {
2602 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
2603 }
2604 if ($separatedStock) {
2605 $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)."))";
2606 }
2607
2608 if ($id) {
2609 $sql .= " WHERE p.rowid = ".((int) $id);
2610 } else {
2611 $sql .= " WHERE p.entity IN (".getEntity($this->element).")";
2612 if ($ref) {
2613 $sql .= " AND p.ref = '".$this->db->escape($ref)."'";
2614 } elseif ($ref_ext) {
2615 $sql .= " AND p.ref_ext = '".$this->db->escape($ref_ext)."'";
2616 } elseif ($barcode) {
2617 $sql .= " AND p.barcode = '".$this->db->escape($barcode)."'";
2618 }
2619 }
2620 if ($separatedStock) {
2621 $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,";
2622 $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,";
2623 $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,";
2624 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
2625 $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,";
2626 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2627 $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,";
2628 } else {
2629 $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,";
2630 }
2631 if ($separatedEntityPMP) {
2632 $sql .= " ppe.pmp,";
2633 } else {
2634 $sql .= " p.pmp,";
2635 }
2636 $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,";
2637 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf";
2638 $sql .= " ,p.price_label";
2639 if (!$separatedStock) {
2640 $sql .= ", p.stock";
2641 }
2642 }
2643
2644 $resql = $this->db->query($sql);
2645 if ($resql) {
2646 unset($this->oldcopy);
2647
2648 if ($this->db->num_rows($resql) > 0) {
2649 $obj = $this->db->fetch_object($resql);
2650
2651 $this->id = $obj->rowid;
2652 $this->ref = $obj->ref;
2653 $this->ref_ext = $obj->ref_ext;
2654 $this->label = $obj->label;
2655 $this->description = $obj->description;
2656 $this->url = $obj->url;
2657 $this->note_public = $obj->note_public;
2658 $this->note_private = $obj->note_private;
2659 $this->note = $obj->note_private; // deprecated
2660
2661 $this->type = $obj->fk_product_type;
2662 $this->price_label = $obj->price_label;
2663 $this->status = $obj->tosell;
2664 $this->status_buy = $obj->tobuy;
2665 $this->status_batch = $obj->tobatch;
2666 $this->sell_or_eat_by_mandatory = $obj->sell_or_eat_by_mandatory;
2667 $this->batch_mask = $obj->batch_mask;
2668
2669 $this->customcode = $obj->customcode;
2670 $this->country_id = $obj->fk_country;
2671 $this->country_code = getCountry($this->country_id, 2, $this->db);
2672 $this->state_id = $obj->fk_state;
2673 $this->lifetime = $obj->lifetime;
2674 $this->qc_frequency = $obj->qc_frequency;
2675 $this->price = $obj->price;
2676 $this->price_ttc = $obj->price_ttc;
2677 $this->price_min = $obj->price_min;
2678 $this->price_min_ttc = $obj->price_min_ttc;
2679 $this->price_base_type = $obj->price_base_type;
2680 $this->cost_price = $obj->cost_price;
2681 $this->default_vat_code = $obj->default_vat_code;
2682 $this->tva_tx = $obj->tva_tx;
2684 $this->tva_npr = $obj->tva_npr;
2686 $this->localtax1_tx = $obj->localtax1_tx;
2687 $this->localtax2_tx = $obj->localtax2_tx;
2688 $this->localtax1_type = $obj->localtax1_type;
2689 $this->localtax2_type = $obj->localtax2_type;
2690
2691 $this->finished = $obj->finished;
2692 $this->fk_default_bom = $obj->fk_default_bom;
2693
2694 $this->duration = $obj->duration;
2695 $this->duration_value = $obj->duration ? (int) (substr($obj->duration, 0, dol_strlen($obj->duration) - 1)) : 0;
2696 $this->duration_unit = $obj->duration ? substr($obj->duration, -1) : null;
2697 $this->canvas = $obj->canvas;
2698 $this->net_measure = $obj->net_measure;
2699 $this->net_measure_units = $obj->net_measure_units;
2700 $this->weight = $obj->weight;
2701 $this->weight_units = $obj->weight_units;
2702 $this->length = $obj->length;
2703 $this->length_units = $obj->length_units;
2704 $this->width = $obj->width;
2705 $this->width_units = $obj->width_units;
2706 $this->height = $obj->height;
2707 $this->height_units = $obj->height_units;
2708
2709 $this->surface = $obj->surface;
2710 $this->surface_units = $obj->surface_units;
2711 $this->volume = $obj->volume;
2712 $this->volume_units = $obj->volume_units;
2713 $this->barcode = $obj->barcode;
2714 $this->barcode_type = $obj->fk_barcode_type;
2715
2716 $this->accountancy_code_buy = $obj->accountancy_code_buy;
2717 $this->accountancy_code_buy_intra = $obj->accountancy_code_buy_intra;
2718 $this->accountancy_code_buy_export = $obj->accountancy_code_buy_export;
2719 $this->accountancy_code_sell = $obj->accountancy_code_sell;
2720 $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
2721 $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
2722
2723 $this->fk_default_warehouse = $obj->fk_default_warehouse;
2724 $this->fk_default_workstation = $obj->fk_default_workstation;
2725 $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
2726 $this->desiredstock = $obj->desiredstock;
2727 $this->stock_reel = $obj->stock;
2728 $this->pmp = $obj->pmp;
2729
2730 $this->date_creation = $obj->datec;
2731 $this->date_modification = $obj->tms;
2732 $this->import_key = $obj->import_key;
2733 $this->entity = $obj->entity;
2734
2735 $this->ref_ext = $obj->ref_ext;
2736 $this->fk_price_expression = $obj->fk_price_expression;
2737 $this->fk_unit = $obj->fk_unit;
2738 $this->price_autogen = $obj->price_autogen;
2739 $this->model_pdf = $obj->model_pdf;
2740 $this->last_main_doc = $obj->last_main_doc;
2741
2742 $this->mandatory_period = $obj->mandatory_period;
2743
2744 $this->db->free($resql);
2745
2746 // fetch optionals attributes and labels
2747 $this->fetch_optionals();
2748
2749 // Multilangs
2750 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($ignore_lang_load)) {
2751 $this->getMultiLangs();
2752 }
2753
2754 // Load multiprices array
2755 if (getDolGlobalString('PRODUIT_MULTIPRICES') && empty($ignore_price_load)) { // prices per segment
2756 for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
2757 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2758 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2759 $sql .= " ,price_label";
2760 $sql .= " FROM ".$this->db->prefix()."product_price";
2761 $sql .= " WHERE entity IN (".getEntity('productprice').")";
2762 $sql .= " AND price_level=".((int) $i);
2763 $sql .= " AND fk_product = ".((int) $this->id);
2764 $sql .= " ORDER BY date_price DESC, rowid DESC"; // Get the most recent line
2765 $sql .= " LIMIT 1"; // Only the first one
2766 $resql = $this->db->query($sql);
2767 if ($resql) {
2768 $result = $this->db->fetch_array($resql);
2769
2770 $this->multiprices[$i] = $result ? $result["price"] : null;
2771 $this->multiprices_ttc[$i] = $result ? $result["price_ttc"] : null;
2772 $this->multiprices_min[$i] = $result ? $result["price_min"] : null;
2773 $this->multiprices_min_ttc[$i] = $result ? $result["price_min_ttc"] : null;
2774 $this->multiprices_base_type[$i] = $result ? $result["price_base_type"] : null;
2775 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2776 $this->multiprices_tva_tx[$i] = $result ? $result["tva_tx"].($result ? ' ('.$result['default_vat_code'].')' : '') : null;
2777 $this->multiprices_recuperableonly[$i] = $result ? $result["recuperableonly"] : null;
2778
2779 // Price by quantity
2780 /*
2781 $this->prices_by_qty[$i]=$result["price_by_qty"];
2782 $this->prices_by_qty_id[$i]=$result["rowid"];
2783 // Récuperation de la liste des prix selon qty si flag positionné
2784 if ($this->prices_by_qty[$i] == 1)
2785 {
2786 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2787 $sql.= " FROM ".$this->db->prefix()."product_price_by_qty";
2788 $sql.= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2789 $sql.= " ORDER BY quantity ASC";
2790
2791 $resql = $this->db->query($sql);
2792 if ($resql)
2793 {
2794 $resultat=array();
2795 $ii=0;
2796 while ($result= $this->db->fetch_array($resql)) {
2797 $resultat[$ii]=array();
2798 $resultat[$ii]["rowid"]=$result["rowid"];
2799 $resultat[$ii]["price"]= $result["price"];
2800 $resultat[$ii]["unitprice"]= $result["unitprice"];
2801 $resultat[$ii]["quantity"]= $result["quantity"];
2802 $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2803 $resultat[$ii]["remise"]= $result["remise"]; // deprecated
2804 $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2805 $ii++;
2806 }
2807 $this->prices_by_qty_list[$i]=$resultat;
2808 }
2809 else
2810 {
2811 dol_print_error($this->db);
2812 return -1;
2813 }
2814 }*/
2815 } else {
2816 $this->error = $this->db->lasterror;
2817 return -1;
2818 }
2819 }
2820 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') && empty($ignore_price_load)) { // prices per customers
2821 // Nothing loaded by default. List may be very long.
2822 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') && empty($ignore_price_load)) { // prices per quantity
2823 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2824 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
2825 $sql .= " FROM ".$this->db->prefix()."product_price";
2826 $sql .= " WHERE fk_product = ".((int) $this->id);
2827 $sql .= " ORDER BY date_price DESC, rowid DESC";
2828 $sql .= " LIMIT 1";
2829
2830 $resql = $this->db->query($sql);
2831 if ($resql) {
2832 $result = $this->db->fetch_array($resql);
2833
2834 if ($result) {
2835 // Price by quantity
2836 $this->prices_by_qty[0] = $result["price_by_qty"];
2837 $this->prices_by_qty_id[0] = $result["rowid"];
2838 // Récuperation de la liste des prix selon qty si flag positionné
2839 if ($this->prices_by_qty[0] == 1) {
2840 $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
2841 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2842 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[0]);
2843 $sql .= " ORDER BY quantity ASC";
2844
2845 $resql = $this->db->query($sql);
2846 if ($resql) {
2847 $resultat = array();
2848 $ii = 0;
2849 while ($result = $this->db->fetch_array($resql)) {
2850 $resultat[$ii] = array();
2851 $resultat[$ii]["rowid"] = $result["rowid"];
2852 $resultat[$ii]["price"] = $result["price"];
2853 $resultat[$ii]["unitprice"] = $result["unitprice"];
2854 $resultat[$ii]["quantity"] = $result["quantity"];
2855 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2856 //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
2857 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2858 $ii++;
2859 }
2860 $this->prices_by_qty_list[0] = $resultat;
2861 } else {
2862 $this->error = $this->db->lasterror;
2863 return -1;
2864 }
2865 }
2866 }
2867 } else {
2868 $this->error = $this->db->lasterror;
2869 return -1;
2870 }
2871 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') && empty($ignore_price_load)) { // prices per customer and quantity
2872 for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
2873 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2874 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2875 $sql .= " FROM ".$this->db->prefix()."product_price";
2876 $sql .= " WHERE entity IN (".getEntity('productprice').")";
2877 $sql .= " AND price_level=".((int) $i);
2878 $sql .= " AND fk_product = ".((int) $this->id);
2879 $sql .= " ORDER BY date_price DESC, rowid DESC";
2880 $sql .= " LIMIT 1";
2881 $resql = $this->db->query($sql);
2882 if (!$resql) {
2883 $this->error = $this->db->lasterror;
2884 return -1;
2885 } elseif ($result = $this->db->fetch_array($resql)) {
2886 $this->multiprices[$i] = (!empty($result["price"]) ? $result["price"] : 0);
2887 $this->multiprices_ttc[$i] = (!empty($result["price_ttc"]) ? $result["price_ttc"] : 0);
2888 $this->multiprices_min[$i] = (!empty($result["price_min"]) ? $result["price_min"] : 0);
2889 $this->multiprices_min_ttc[$i] = (!empty($result["price_min_ttc"]) ? $result["price_min_ttc"] : 0);
2890 $this->multiprices_base_type[$i] = (!empty($result["price_base_type"]) ? $result["price_base_type"] : '');
2891 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2892 $this->multiprices_tva_tx[$i] = (!empty($result["tva_tx"]) ? $result["tva_tx"] : 0); // TODO Add ' ('.$result['default_vat_code'].')'
2893 $this->multiprices_recuperableonly[$i] = (!empty($result["recuperableonly"]) ? $result["recuperableonly"] : 0);
2894
2895 // Price by quantity
2896 $this->prices_by_qty[$i] = (!empty($result["price_by_qty"]) ? $result["price_by_qty"] : 0);
2897 $this->prices_by_qty_id[$i] = (!empty($result["rowid"]) ? $result["rowid"] : 0);
2898 // Récuperation de la liste des prix selon qty si flag positionné
2899 if ($this->prices_by_qty[$i] == 1) {
2900 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2901 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2902 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2903 $sql .= " ORDER BY quantity ASC";
2904
2905 $resql = $this->db->query($sql);
2906 if ($resql) {
2907 $resultat = array();
2908 $ii = 0;
2909 while ($result = $this->db->fetch_array($resql)) {
2910 $resultat[$ii] = array();
2911 $resultat[$ii]["rowid"] = $result["rowid"];
2912 $resultat[$ii]["price"] = $result["price"];
2913 $resultat[$ii]["unitprice"] = $result["unitprice"];
2914 $resultat[$ii]["quantity"] = $result["quantity"];
2915 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2916 $resultat[$ii]["remise"] = $result["remise"]; // deprecated
2917 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2918 $ii++;
2919 }
2920 $this->prices_by_qty_list[$i] = $resultat;
2921 } else {
2922 $this->error = $this->db->lasterror;
2923 return -1;
2924 }
2925 }
2926 }
2927 }
2928 }
2929
2930 if (isModEnabled('dynamicprices') && !empty($this->fk_price_expression) && empty($ignore_expression)) {
2931 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2932 $priceparser = new PriceParser($this->db);
2933 $price_result = $priceparser->parseProduct($this);
2934 if ($price_result >= 0) {
2935 $this->price = $price_result;
2936 // Calculate the VAT
2937 $this->price_ttc = (float) price2num($this->price) * (1 + ($this->tva_tx / 100));
2938 $this->price_ttc = (float) price2num($this->price_ttc, 'MU');
2939 }
2940 }
2941
2942 // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
2943 // Instead we just init the stock_warehouse array
2944 $this->stock_warehouse = array();
2945
2946 return 1;
2947 } else {
2948 return 0;
2949 }
2950 } else {
2951 $this->error = $this->db->lasterror();
2952 return -1;
2953 }
2954 }
2955
2956 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2963 public function load_stats_mo($socid = 0)
2964 {
2965 // phpcs:enable
2966 global $user, $hookmanager, $action;
2967
2968 $error = 0;
2969
2970 foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
2971 $this->stats_mo['customers_'.$role] = 0;
2972 $this->stats_mo['nb_'.$role] = 0;
2973 $this->stats_mo['qty_'.$role] = 0;
2974
2975 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2976 $sql .= " SUM(mp.qty) as qty";
2977 $sql .= " FROM ".$this->db->prefix()."mrp_mo as c";
2978 $sql .= " INNER JOIN ".$this->db->prefix()."mrp_production as mp ON mp.fk_mo=c.rowid";
2979 if (!$user->hasRight('societe', 'client', 'voir')) {
2980 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc=c.fk_soc AND sc.fk_user = ".((int) $user->id);
2981 }
2982 $sql .= " WHERE ";
2983 $sql .= " c.entity IN (".getEntity('mo').")";
2984
2985 $sql .= " AND mp.fk_product = ".((int) $this->id);
2986 $sql .= " AND mp.role ='".$this->db->escape($role)."'";
2987 if ($socid > 0) {
2988 $sql .= " AND c.fk_soc = ".((int) $socid);
2989 }
2990
2991 $result = $this->db->query($sql);
2992 if ($result) {
2993 $obj = $this->db->fetch_object($result);
2994 $this->stats_mo['customers_'.$role] = $obj->nb_customers ? $obj->nb_customers : 0;
2995 $this->stats_mo['nb_'.$role] = $obj->nb ? $obj->nb : 0;
2996 $this->stats_mo['qty_'.$role] = $obj->qty ? price2num($obj->qty, 'MS') : 0; // qty may be a float due to the SUM()
2997 } else {
2998 $this->error = $this->db->error();
2999 $error++;
3000 }
3001 }
3002
3003 if (!empty($error)) {
3004 return -1;
3005 }
3006
3007 $parameters = array('socid' => $socid);
3008 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3009 if ($reshook > 0) {
3010 $this->stats_mo = $hookmanager->resArray['stats_mo'];
3011 }
3012
3013 return 1;
3014 }
3015
3016 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3023 public function load_stats_bom($socid = 0)
3024 {
3025 // phpcs:enable
3026 global $user, $hookmanager, $action;
3027
3028 $error = 0;
3029
3030 $this->stats_bom['nb_toproduce'] = 0;
3031 $this->stats_bom['nb_toconsume'] = 0;
3032 $this->stats_bom['qty_toproduce'] = 0;
3033 $this->stats_bom['qty_toconsume'] = 0;
3034
3035 $sql = "SELECT COUNT(DISTINCT b.rowid) as nb_toproduce,";
3036 $sql .= " SUM(b.qty) as qty_toproduce";
3037 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3038 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
3039 $sql .= " WHERE ";
3040 $sql .= " b.entity IN (".getEntity('bom').")";
3041 $sql .= " AND b.fk_product =".((int) $this->id);
3042 $sql .= " GROUP BY b.rowid";
3043
3044 $result = $this->db->query($sql);
3045 if ($result) {
3046 $obj = $this->db->fetch_object($result);
3047 $this->stats_bom['nb_toproduce'] = !empty($obj->nb_toproduce) ? $obj->nb_toproduce : 0;
3048 $this->stats_bom['qty_toproduce'] = !empty($obj->qty_toproduce) ? price2num($obj->qty_toproduce) : 0;
3049 } else {
3050 $this->error = $this->db->error();
3051 $error++;
3052 }
3053
3054 $sql = "SELECT COUNT(DISTINCT bl.rowid) as nb_toconsume,";
3055 $sql .= " SUM(bl.qty) as qty_toconsume";
3056 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3057 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
3058 $sql .= " WHERE ";
3059 $sql .= " b.entity IN (".getEntity('bom').")";
3060 $sql .= " AND bl.fk_product =".((int) $this->id);
3061
3062 $result = $this->db->query($sql);
3063 if ($result) {
3064 $obj = $this->db->fetch_object($result);
3065 $this->stats_bom['nb_toconsume'] = !empty($obj->nb_toconsume) ? $obj->nb_toconsume : 0;
3066 $this->stats_bom['qty_toconsume'] = !empty($obj->qty_toconsume) ? price2num($obj->qty_toconsume) : 0;
3067 } else {
3068 $this->error = $this->db->error();
3069 $error++;
3070 }
3071
3072 if (!empty($error)) {
3073 return -1;
3074 }
3075
3076 $parameters = array('socid' => $socid);
3077 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3078 if ($reshook > 0) {
3079 $this->stats_bom = $hookmanager->resArray['stats_bom'];
3080 }
3081
3082 return 1;
3083 }
3084
3085 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3092 public function load_stats_propale($socid = 0)
3093 {
3094 // phpcs:enable
3095 global $conf, $user, $hookmanager, $action;
3096
3097 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
3098 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3099 $sql .= " FROM ".$this->db->prefix()."propaldet as pd";
3100 $sql .= ", ".$this->db->prefix()."propal as p";
3101 $sql .= ", ".$this->db->prefix()."societe as s";
3102 if (!$user->hasRight('societe', 'client', 'voir')) {
3103 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3104 }
3105 $sql .= " WHERE p.rowid = pd.fk_propal";
3106 $sql .= " AND p.fk_soc = s.rowid";
3107 $sql .= " AND p.entity IN (".getEntity('propal').")";
3108 $sql .= " AND pd.fk_product = ".((int) $this->id);
3109 if (!$user->hasRight('societe', 'client', 'voir')) {
3110 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3111 }
3112 //$sql.= " AND pr.fk_statut != 0";
3113 if ($socid > 0) {
3114 $sql .= " AND p.fk_soc = ".((int) $socid);
3115 }
3116
3117 $result = $this->db->query($sql);
3118 if ($result) {
3119 $obj = $this->db->fetch_object($result);
3120 $this->stats_propale['customers'] = $obj->nb_customers;
3121 $this->stats_propale['nb'] = $obj->nb;
3122 $this->stats_propale['rows'] = $obj->nb_rows;
3123 $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
3124
3125 // if it's a virtual product, maybe it is in proposal by extension
3126 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3127 $TFather = $this->getFather();
3128 if (is_array($TFather) && !empty($TFather)) {
3129 foreach ($TFather as &$fatherData) {
3130 $pFather = new Product($this->db);
3131 $pFather->id = $fatherData['id'];
3132 $qtyCoef = $fatherData['qty'];
3133
3134 if ($fatherData['incdec']) {
3135 $pFather->load_stats_propale($socid);
3136
3137 $this->stats_propale['customers'] += $pFather->stats_propale['customers'];
3138 $this->stats_propale['nb'] += $pFather->stats_propale['nb'];
3139 $this->stats_propale['rows'] += $pFather->stats_propale['rows'];
3140 $this->stats_propale['qty'] += $pFather->stats_propale['qty'] * $qtyCoef;
3141 }
3142 }
3143 }
3144 }
3145
3146 $parameters = array('socid' => $socid);
3147 $reshook = $hookmanager->executeHooks('loadStatsCustomerProposal', $parameters, $this, $action);
3148 if ($reshook > 0) {
3149 $this->stats_propale = $hookmanager->resArray['stats_propale'];
3150 }
3151
3152 return 1;
3153 } else {
3154 $this->error = $this->db->error();
3155 return -1;
3156 }
3157 }
3158
3159
3160 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3167 public function load_stats_proposal_supplier($socid = 0)
3168 {
3169 // phpcs:enable
3170 global $conf, $user, $hookmanager, $action;
3171
3172 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
3173 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3174 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as pd";
3175 $sql .= ", ".$this->db->prefix()."supplier_proposal as p";
3176 $sql .= ", ".$this->db->prefix()."societe as s";
3177 if (!$user->hasRight('societe', 'client', 'voir')) {
3178 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3179 }
3180 $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
3181 $sql .= " AND p.fk_soc = s.rowid";
3182 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
3183 $sql .= " AND pd.fk_product = ".((int) $this->id);
3184 if (!$user->hasRight('societe', 'client', 'voir')) {
3185 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3186 }
3187 //$sql.= " AND pr.fk_statut != 0";
3188 if ($socid > 0) {
3189 $sql .= " AND p.fk_soc = ".((int) $socid);
3190 }
3191
3192 $result = $this->db->query($sql);
3193 if ($result) {
3194 $obj = $this->db->fetch_object($result);
3195 $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
3196 $this->stats_proposal_supplier['nb'] = $obj->nb;
3197 $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
3198 $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
3199
3200 $parameters = array('socid' => $socid);
3201 $reshook = $hookmanager->executeHooks('loadStatsSupplierProposal', $parameters, $this, $action);
3202 if ($reshook > 0) {
3203 $this->stats_proposal_supplier = $hookmanager->resArray['stats_proposal_supplier'];
3204 }
3205
3206 return 1;
3207 } else {
3208 $this->error = $this->db->error();
3209 return -1;
3210 }
3211 }
3212
3213
3214 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3223 public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
3224 {
3225 // phpcs:enable
3226 global $conf, $user, $hookmanager, $action;
3227
3228 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3229 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3230 $sql .= " FROM ".$this->db->prefix()."commandedet as cd";
3231 $sql .= ", ".$this->db->prefix()."commande as c";
3232 $sql .= ", ".$this->db->prefix()."societe as s";
3233 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3234 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3235 }
3236 $sql .= " WHERE c.rowid = cd.fk_commande";
3237 $sql .= " AND c.fk_soc = s.rowid";
3238 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'commande').")";
3239 $sql .= " AND cd.fk_product = ".((int) $this->id);
3240 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3241 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3242 }
3243 if ($socid > 0) {
3244 $sql .= " AND c.fk_soc = ".((int) $socid);
3245 }
3246 if ($filtrestatut != '') {
3247 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")";
3248 }
3249
3250 $result = $this->db->query($sql);
3251 if ($result) {
3252 $obj = $this->db->fetch_object($result);
3253 $this->stats_commande['customers'] = $obj->nb_customers;
3254 $this->stats_commande['nb'] = $obj->nb;
3255 $this->stats_commande['rows'] = $obj->nb_rows;
3256 $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
3257
3258 // if it's a virtual product, maybe it is in order by extension
3259 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3260 $TFather = $this->getFather();
3261 if (is_array($TFather) && !empty($TFather)) {
3262 foreach ($TFather as &$fatherData) {
3263 $pFather = new Product($this->db);
3264 $pFather->id = $fatherData['id'];
3265 $qtyCoef = $fatherData['qty'];
3266
3267 if ($fatherData['incdec']) {
3268 $pFather->load_stats_commande($socid, $filtrestatut);
3269
3270 $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
3271 $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
3272 $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
3273 $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
3274 }
3275 }
3276 }
3277 }
3278
3279 // If stock decrease is on invoice validation, the theoretical stock continue to
3280 // count the orders to ship in theoretical stock when some are already removed by invoice validation.
3281 if ($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
3282 if (getDolGlobalString('DECREASE_ONLY_UNINVOICEDPRODUCTS')) {
3283 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation but only if order not yet invoice.
3284 $adeduire = 0;
3285 $sql = "SELECT SUM(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3286 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3287 $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'))";
3288 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_source = c.rowid";
3289 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND c.facture = 0 AND fd.fk_product = ".((int) $this->id);
3290
3291 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3292 $resql = $this->db->query($sql);
3293 if ($resql) {
3294 if ($this->db->num_rows($resql) > 0) {
3295 $obj = $this->db->fetch_object($resql);
3296 $adeduire += $obj->count;
3297 }
3298 }
3299
3300 $this->stats_commande['qty'] -= $adeduire;
3301 } else {
3302 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is off, we make a compensation with lines of invoices linked to the order
3303 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3304
3305 // For every order having invoice already validated we need to decrease stock cause it's in physical stock
3306 $adeduire = 0;
3307 $sql = "SELECT sum(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet as fd ";
3308 $sql .= " JOIN ".MAIN_DB_PREFIX."facture as f ON fd.fk_facture = f.rowid";
3309 $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'))";
3310 $sql .= " JOIN ".MAIN_DB_PREFIX."commande as c ON el.fk_source = c.rowid";
3311 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product = ".((int) $this->id);
3312
3313 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3314 $resql = $this->db->query($sql);
3315 if ($resql) {
3316 if ($this->db->num_rows($resql) > 0) {
3317 $obj = $this->db->fetch_object($resql);
3318 $adeduire += $obj->count;
3319 }
3320 } else {
3321 $this->error = $this->db->error();
3322 return -1;
3323 }
3324
3325 $this->stats_commande['qty'] -= $adeduire;
3326 }
3327 }
3328
3329 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3330 $reshook = $hookmanager->executeHooks('loadStatsCustomerOrder', $parameters, $this, $action);
3331 if ($reshook > 0) {
3332 $this->stats_commande = $hookmanager->resArray['stats_commande'];
3333 }
3334 return 1;
3335 } else {
3336 $this->error = $this->db->error();
3337 return -1;
3338 }
3339 }
3340
3341 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3351 public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3352 {
3353 // phpcs:enable
3354 global $conf, $user, $hookmanager, $action;
3355
3356 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
3357 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3358 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as cd";
3359 $sql .= ", ".$this->db->prefix()."commande_fournisseur as c";
3360 $sql .= ", ".$this->db->prefix()."societe as s";
3361 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3362 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3363 }
3364 $sql .= " WHERE c.rowid = cd.fk_commande";
3365 $sql .= " AND c.fk_soc = s.rowid";
3366 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3367 $sql .= " AND cd.fk_product = ".((int) $this->id);
3368 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3369 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3370 }
3371 if ($socid > 0) {
3372 $sql .= " AND c.fk_soc = ".((int) $socid);
3373 }
3374 if ($filtrestatut != '') {
3375 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
3376 }
3377 if (!empty($dateofvirtualstock)) {
3378 $sql .= " AND c.date_livraison <= '".$this->db->idate($dateofvirtualstock)."'";
3379 }
3380
3381 $result = $this->db->query($sql);
3382 if ($result) {
3383 $obj = $this->db->fetch_object($result);
3384 $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
3385 $this->stats_commande_fournisseur['nb'] = $obj->nb;
3386 $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
3387 $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3388
3389 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3390 $reshook = $hookmanager->executeHooks('loadStatsSupplierOrder', $parameters, $this, $action);
3391 if ($reshook > 0) {
3392 $this->stats_commande_fournisseur = $hookmanager->resArray['stats_commande_fournisseur'];
3393 }
3394
3395 return 1;
3396 } else {
3397 $this->error = $this->db->error().' sql='.$sql;
3398 return -1;
3399 }
3400 }
3401
3402 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3412 public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $filterShipmentStatus = '')
3413 {
3414 // phpcs:enable
3415 global $conf, $user, $hookmanager, $action;
3416
3417 $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
3418 $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
3419 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
3420 $sql .= ", ".$this->db->prefix()."commandedet as cd";
3421 $sql .= ", ".$this->db->prefix()."commande as c";
3422 $sql .= ", ".$this->db->prefix()."expedition as e";
3423 $sql .= ", ".$this->db->prefix()."societe as s";
3424 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3425 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3426 }
3427 $sql .= " WHERE e.rowid = ed.fk_expedition";
3428 $sql .= " AND c.rowid = cd.fk_commande";
3429 $sql .= " AND e.fk_soc = s.rowid";
3430 $sql .= " AND e.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'expedition').")";
3431 $sql .= " AND ed.fk_elementdet = cd.rowid";
3432 $sql .= " AND cd.fk_product = ".((int) $this->id);
3433 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3434 $sql .= " AND e.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3435 }
3436 if ($socid > 0) {
3437 $sql .= " AND e.fk_soc = ".((int) $socid);
3438 }
3439 if ($filtrestatut != '') {
3440 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3441 }
3442 if (!empty($filterShipmentStatus)) {
3443 $sql .= " AND e.fk_statut IN (".$this->db->sanitize($filterShipmentStatus).")";
3444 }
3445
3446 $result = $this->db->query($sql);
3447 if ($result) {
3448 $obj = $this->db->fetch_object($result);
3449 $this->stats_expedition['customers'] = $obj->nb_customers;
3450 $this->stats_expedition['nb'] = $obj->nb;
3451 $this->stats_expedition['rows'] = $obj->nb_rows;
3452 $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
3453
3454 // if it's a virtual product, maybe it is in sending by extension
3455 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3456 $TFather = $this->getFather();
3457 if (is_array($TFather) && !empty($TFather)) {
3458 foreach ($TFather as &$fatherData) {
3459 $pFather = new Product($this->db);
3460 $pFather->id = $fatherData['id'];
3461 $qtyCoef = $fatherData['qty'];
3462
3463 if ($fatherData['incdec']) {
3464 $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
3465
3466 $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
3467 $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
3468 $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
3469 $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
3470 }
3471 }
3472 }
3473 }
3474
3475 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock, 'filterShipmentStatus' => $filterShipmentStatus);
3476 $reshook = $hookmanager->executeHooks('loadStatsSending', $parameters, $this, $action);
3477 if ($reshook > 0) {
3478 $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
3479 }
3480
3481 return 1;
3482 } else {
3483 $this->error = $this->db->error();
3484 return -1;
3485 }
3486 }
3487
3488 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3498 public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3499 {
3500 // phpcs:enable
3501 global $conf, $user, $hookmanager, $action;
3502
3503 $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_suppliers, COUNT(DISTINCT cf.rowid) as nb,";
3504 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3505 $sql .= " FROM ".$this->db->prefix()."receptiondet_batch as fd";
3506 $sql .= ", ".$this->db->prefix()."commande_fournisseur as cf";
3507 $sql .= ", ".$this->db->prefix()."societe as s";
3508 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3509 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3510 }
3511 $sql .= " WHERE cf.rowid = fd.fk_element";
3512 $sql .= " AND cf.fk_soc = s.rowid";
3513 $sql .= " AND cf.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3514 $sql .= " AND fd.fk_product = ".((int) $this->id);
3515 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3516 $sql .= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3517 }
3518 if ($socid > 0) {
3519 $sql .= " AND cf.fk_soc = ".((int) $socid);
3520 }
3521 if ($filtrestatut != '') {
3522 $sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3523 }
3524 if (!empty($dateofvirtualstock)) {
3525 $sql .= " AND fd.datec <= '".$this->db->idate($dateofvirtualstock)."'";
3526 }
3527
3528 $result = $this->db->query($sql);
3529 if ($result) {
3530 $obj = $this->db->fetch_object($result);
3531 $this->stats_reception['suppliers'] = $obj->nb_suppliers;
3532 $this->stats_reception['nb'] = $obj->nb;
3533 $this->stats_reception['rows'] = $obj->nb_rows;
3534 $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
3535
3536 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3537 $reshook = $hookmanager->executeHooks('loadStatsReception', $parameters, $this, $action);
3538 if ($reshook > 0) {
3539 $this->stats_reception = $hookmanager->resArray['stats_reception'];
3540 }
3541
3542 return 1;
3543 } else {
3544 $this->error = $this->db->error();
3545 return -1;
3546 }
3547 }
3548
3549 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3560 public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null, $warehouseid = 0)
3561 {
3562 // phpcs:enable
3563 global $user, $hookmanager, $action;
3564
3565 $serviceStockIsEnabled = isModEnabled("service") && getDolGlobalString('STOCK_SUPPORTS_SERVICES');
3566
3567 $sql = "SELECT COUNT(DISTINCT m.fk_soc) as nb_customers, COUNT(DISTINCT m.rowid) as nb,";
3568 $sql .= " COUNT(mp.rowid) as nb_rows, SUM(mp.qty) as qty, role";
3569 $sql .= " FROM ".$this->db->prefix()."mrp_production as mp";
3570 $sql .= ", ".$this->db->prefix()."mrp_mo as m";
3571 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON s.rowid = m.fk_soc";
3572 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3573 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3574 }
3575 $sql .= " WHERE m.rowid = mp.fk_mo";
3576 $sql .= " AND m.entity IN (".getEntity(($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE')) ? 'stock' : 'mrp').")";
3577 $sql .= " AND mp.fk_product = ".((int) $this->id);
3578 $sql .= " AND (mp.disable_stock_change IN (0) OR mp.disable_stock_change IS NULL)";
3579 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3580 $sql .= " AND m.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3581 }
3582 if ($socid > 0) {
3583 $sql .= " AND m.fk_soc = ".((int) $socid);
3584 }
3585 if ($filtrestatut != '') {
3586 $sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
3587 }
3588 if (!empty($dateofvirtualstock)) {
3589 $sql .= " AND m.date_valid <= '".$this->db->idate($dateofvirtualstock)."'"; // better date to code ? end of production ?
3590 }
3591 if (!$serviceStockIsEnabled) {
3592 $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))";
3593 }
3594 if (!empty($warehouseid)) {
3595 $sql .= " AND m.fk_warehouse = ".((int) $warehouseid);
3596 }
3597 $sql .= " GROUP BY role";
3598
3599 if ($warehouseid) {
3600 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
3601 } else {
3602 $this->stats_mrptoconsume['customers'] = 0;
3603 $this->stats_mrptoconsume['nb'] = 0;
3604 $this->stats_mrptoconsume['rows'] = 0;
3605 $this->stats_mrptoconsume['qty'] = 0;
3606 $this->stats_mrptoproduce['customers'] = 0;
3607 $this->stats_mrptoproduce['nb'] = 0;
3608 $this->stats_mrptoproduce['rows'] = 0;
3609 $this->stats_mrptoproduce['qty'] = 0;
3610 }
3611
3612 $result = $this->db->query($sql);
3613 if ($result) {
3614 while ($obj = $this->db->fetch_object($result)) {
3615 if ($obj->role == 'toconsume' && empty($warehouseid)) {
3616 $this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3617 $this->stats_mrptoconsume['nb'] += $obj->nb;
3618 $this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3619 $this->stats_mrptoconsume['qty'] += ($obj->qty ? $obj->qty : 0);
3620 }
3621 if ($obj->role == 'consumed' && empty($warehouseid)) {
3622 //$this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3623 //$this->stats_mrptoconsume['nb'] += $obj->nb;
3624 //$this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3625 $this->stats_mrptoconsume['qty'] -= ($obj->qty ? $obj->qty : 0);
3626 }
3627 if ($obj->role == 'toproduce') {
3628 if ($warehouseid) {
3629 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] += ($obj->qty ? $obj->qty : 0);
3630 } else {
3631 $this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3632 $this->stats_mrptoproduce['nb'] += $obj->nb;
3633 $this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3634 $this->stats_mrptoproduce['qty'] += ($obj->qty ? $obj->qty : 0);
3635 }
3636 }
3637 if ($obj->role == 'produced') {
3638 //$this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3639 //$this->stats_mrptoproduce['nb'] += $obj->nb;
3640 //$this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3641 if ($warehouseid) {
3642 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3643 } else {
3644 $this->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3645 }
3646 }
3647 }
3648
3649 // Clean data
3650 if ($warehouseid) {
3651 if ($this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] < 0) {
3652 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
3653 }
3654 } else {
3655 if ($this->stats_mrptoconsume['qty'] < 0) {
3656 $this->stats_mrptoconsume['qty'] = 0;
3657 }
3658 if ($this->stats_mrptoproduce['qty'] < 0) {
3659 $this->stats_mrptoproduce['qty'] = 0;
3660 }
3661 }
3662
3663 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3664 $reshook = $hookmanager->executeHooks('loadStatsInProduction', $parameters, $this, $action);
3665 if ($reshook > 0) {
3666 $this->stats_mrptoproduce = $hookmanager->resArray['stats_mrptoproduce'];
3667 }
3668
3669 return 1;
3670 } else {
3671 $this->error = $this->db->error();
3672 return -1;
3673 }
3674 }
3675
3676 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3683 public function load_stats_contrat($socid = 0)
3684 {
3685 // phpcs:enable
3686 global $conf, $user, $hookmanager, $action;
3687
3688 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3689 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3690 $sql .= " FROM ".$this->db->prefix()."contratdet as cd";
3691 $sql .= ", ".$this->db->prefix()."contrat as c";
3692 $sql .= ", ".$this->db->prefix()."societe as s";
3693 if (!$user->hasRight('societe', 'client', 'voir')) {
3694 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3695 }
3696 $sql .= " WHERE c.rowid = cd.fk_contrat";
3697 $sql .= " AND c.fk_soc = s.rowid";
3698 $sql .= " AND c.entity IN (".getEntity('contract').")";
3699 $sql .= " AND cd.fk_product = ".((int) $this->id);
3700 if (!$user->hasRight('societe', 'client', 'voir')) {
3701 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3702 }
3703 //$sql.= " AND c.statut != 0";
3704 if ($socid > 0) {
3705 $sql .= " AND c.fk_soc = ".((int) $socid);
3706 }
3707
3708 $result = $this->db->query($sql);
3709 if ($result) {
3710 $obj = $this->db->fetch_object($result);
3711 $this->stats_contrat['customers'] = $obj->nb_customers;
3712 $this->stats_contrat['nb'] = $obj->nb;
3713 $this->stats_contrat['rows'] = $obj->nb_rows;
3714 $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
3715
3716 // if it's a virtual product, maybe it is in contract by extension
3717 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3718 $TFather = $this->getFather();
3719 if (is_array($TFather) && !empty($TFather)) {
3720 foreach ($TFather as &$fatherData) {
3721 $pFather = new Product($this->db);
3722 $pFather->id = $fatherData['id'];
3723 $qtyCoef = $fatherData['qty'];
3724
3725 if ($fatherData['incdec']) {
3726 $pFather->load_stats_contrat($socid);
3727
3728 $this->stats_contrat['customers'] += $pFather->stats_contrat['customers'];
3729 $this->stats_contrat['nb'] += $pFather->stats_contrat['nb'];
3730 $this->stats_contrat['rows'] += $pFather->stats_contrat['rows'];
3731 $this->stats_contrat['qty'] += $pFather->stats_contrat['qty'] * $qtyCoef;
3732 }
3733 }
3734 }
3735 }
3736
3737 $parameters = array('socid' => $socid);
3738 $reshook = $hookmanager->executeHooks('loadStatsContract', $parameters, $this, $action);
3739 if ($reshook > 0) {
3740 $this->stats_contrat = $hookmanager->resArray['stats_contrat'];
3741 }
3742
3743 return 1;
3744 } else {
3745 $this->error = $this->db->error().' sql='.$sql;
3746 return -1;
3747 }
3748 }
3749
3750 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3757 public function load_stats_facture($socid = 0)
3758 {
3759 // phpcs:enable
3760 global $conf, $user, $hookmanager, $action;
3761
3762 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3763 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(".$this->db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
3764 $sql .= " FROM ".$this->db->prefix()."facturedet as fd";
3765 $sql .= ", ".$this->db->prefix()."facture as f";
3766 $sql .= ", ".$this->db->prefix()."societe as s";
3767 if (!$user->hasRight('societe', 'client', 'voir')) {
3768 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3769 }
3770 $sql .= " WHERE f.rowid = fd.fk_facture";
3771 $sql .= " AND f.fk_soc = s.rowid";
3772 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3773 $sql .= " AND fd.fk_product = ".((int) $this->id);
3774 if (!$user->hasRight('societe', 'client', 'voir')) {
3775 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3776 }
3777 //$sql.= " AND f.fk_statut != 0";
3778 if ($socid > 0) {
3779 $sql .= " AND f.fk_soc = ".((int) $socid);
3780 }
3781
3782 $result = $this->db->query($sql);
3783 if ($result) {
3784 $obj = $this->db->fetch_object($result);
3785 $this->stats_facture['customers'] = $obj->nb_customers;
3786 $this->stats_facture['nb'] = $obj->nb;
3787 $this->stats_facture['rows'] = $obj->nb_rows;
3788 $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
3789
3790 // if it's a virtual product, maybe it is in invoice by extension
3791 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3792 $TFather = $this->getFather();
3793 if (is_array($TFather) && !empty($TFather)) {
3794 foreach ($TFather as &$fatherData) {
3795 $pFather = new Product($this->db);
3796 $pFather->id = $fatherData['id'];
3797 $qtyCoef = $fatherData['qty'];
3798
3799 if ($fatherData['incdec']) {
3800 $pFather->load_stats_facture($socid);
3801
3802 $this->stats_facture['customers'] += $pFather->stats_facture['customers'];
3803 $this->stats_facture['nb'] += $pFather->stats_facture['nb'];
3804 $this->stats_facture['rows'] += $pFather->stats_facture['rows'];
3805 $this->stats_facture['qty'] += $pFather->stats_facture['qty'] * $qtyCoef;
3806 }
3807 }
3808 }
3809 }
3810
3811 $parameters = array('socid' => $socid);
3812 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoice', $parameters, $this, $action);
3813 if ($reshook > 0) {
3814 $this->stats_facture = $hookmanager->resArray['stats_facture'];
3815 }
3816
3817 return 1;
3818 } else {
3819 $this->error = $this->db->error();
3820 return -1;
3821 }
3822 }
3823
3824
3825 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3832 public function load_stats_facturerec($socid = 0)
3833 {
3834 // phpcs:enable
3835 global $conf, $user, $hookmanager, $action;
3836
3837 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3838 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3839 $sql .= " FROM ".MAIN_DB_PREFIX."facturedet_rec as fd";
3840 $sql .= ", ".MAIN_DB_PREFIX."facture_rec as f";
3841 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
3842 if (!$user->hasRight('societe', 'client', 'voir')) {
3843 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3844 }
3845 $sql .= " WHERE f.rowid = fd.fk_facture";
3846 $sql .= " AND f.fk_soc = s.rowid";
3847 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3848 $sql .= " AND fd.fk_product = ".((int) $this->id);
3849 if (!$user->hasRight('societe', 'client', 'voir')) {
3850 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3851 }
3852 //$sql.= " AND f.fk_statut != 0";
3853 if ($socid > 0) {
3854 $sql .= " AND f.fk_soc = ".((int) $socid);
3855 }
3856
3857 $result = $this->db->query($sql);
3858 if ($result) {
3859 $obj = $this->db->fetch_object($result);
3860 $this->stats_facturerec['customers'] = $obj->nb_customers;
3861 $this->stats_facturerec['nb'] = $obj->nb;
3862 $this->stats_facturerec['rows'] = $obj->nb_rows;
3863 $this->stats_facturerec['qty'] = $obj->qty ? $obj->qty : 0;
3864
3865 // if it's a virtual product, maybe it is in invoice by extension
3866 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3867 $TFather = $this->getFather();
3868 if (is_array($TFather) && !empty($TFather)) {
3869 foreach ($TFather as &$fatherData) {
3870 $pFather = new Product($this->db);
3871 $pFather->id = $fatherData['id'];
3872 $qtyCoef = $fatherData['qty'];
3873
3874 if ($fatherData['incdec']) {
3875 $pFather->load_stats_facture($socid);
3876
3877 $this->stats_facturerec['customers'] += $pFather->stats_facturerec['customers'];
3878 $this->stats_facturerec['nb'] += $pFather->stats_facturerec['nb'];
3879 $this->stats_facturerec['rows'] += $pFather->stats_facturerec['rows'];
3880 $this->stats_facturerec['qty'] += $pFather->stats_facturerec['qty'] * $qtyCoef;
3881 }
3882 }
3883 }
3884 }
3885
3886 $parameters = array('socid' => $socid);
3887 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoiceRec', $parameters, $this, $action);
3888 if ($reshook > 0) {
3889 $this->stats_facturerec = $hookmanager->resArray['stats_facturerec'];
3890 }
3891
3892 return 1;
3893 } else {
3894 $this->error = $this->db->error();
3895 return -1;
3896 }
3897 }
3898
3899 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3906 public function load_stats_facture_fournisseur($socid = 0)
3907 {
3908 // phpcs:enable
3909 global $conf, $user, $hookmanager, $action;
3910
3911 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
3912 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3913 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as fd";
3914 $sql .= ", ".$this->db->prefix()."facture_fourn as f";
3915 $sql .= ", ".$this->db->prefix()."societe as s";
3916 if (!$user->hasRight('societe', 'client', 'voir')) {
3917 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3918 }
3919 $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
3920 $sql .= " AND f.fk_soc = s.rowid";
3921 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
3922 $sql .= " AND fd.fk_product = ".((int) $this->id);
3923 if (!$user->hasRight('societe', 'client', 'voir')) {
3924 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3925 }
3926 //$sql.= " AND f.fk_statut != 0";
3927 if ($socid > 0) {
3928 $sql .= " AND f.fk_soc = ".((int) $socid);
3929 }
3930
3931 $result = $this->db->query($sql);
3932 if ($result) {
3933 $obj = $this->db->fetch_object($result);
3934 $this->stats_facture_fournisseur['suppliers'] = $obj->nb_suppliers;
3935 $this->stats_facture_fournisseur['nb'] = $obj->nb;
3936 $this->stats_facture_fournisseur['rows'] = $obj->nb_rows;
3937 $this->stats_facture_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3938
3939 $parameters = array('socid' => $socid);
3940 $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoice', $parameters, $this, $action);
3941 if ($reshook > 0) {
3942 $this->stats_facture_fournisseur = $hookmanager->resArray['stats_facture_fournisseur'];
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
3961 private function _get_stats($sql, $mode, $year = 0)
3962 {
3963 // phpcs:enable
3964 $tab = array();
3965
3966 $resql = $this->db->query($sql);
3967 if ($resql) {
3968 $num = $this->db->num_rows($resql);
3969 $i = 0;
3970 while ($i < $num) {
3971 $arr = $this->db->fetch_array($resql);
3972 if (is_array($arr)) {
3973 $keyfortab = (string) $arr[1];
3974 if ($year == -1) {
3975 $keyfortab = substr($keyfortab, -2);
3976 }
3977
3978 if ($mode == 'byunit') {
3979 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[0]; // 1st field
3980 } elseif ($mode == 'bynumber') {
3981 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3982 } elseif ($mode == 'byamount') {
3983 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3984 } else {
3985 // Bad value for $mode
3986 return -1;
3987 }
3988 }
3989 $i++;
3990 }
3991 } else {
3992 $this->error = $this->db->error().' sql='.$sql;
3993 return -1;
3994 }
3995
3996 if (empty($year)) {
3997 $year = dol_print_date(time(), '%Y');
3998 $month = dol_print_date(time(), '%m');
3999 } elseif ($year == -1) {
4000 $year = '';
4001 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
4002 } else {
4003 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
4004 }
4005
4006 $result = array();
4007
4008 for ($j = 0; $j < 12; $j++) {
4009 // $ids is 'D', 'N', 'O', 'S', ... (First letter of month in user language)
4010 $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, 1970), "%b"), 1, 'right', 'UTF-8', 1));
4011
4012 //print $idx.'-'.$year.'-'.$month.'<br>';
4013 $result[$j] = array($idx, isset($tab[$year.$month]) ? $tab[$year.$month] : 0);
4014 // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
4015
4016 $month = "0".($month - 1);
4017 if (dol_strlen($month) == 3) {
4018 $month = substr($month, 1);
4019 }
4020 if ($month == 0) {
4021 $month = 12;
4022 $year = $year - 1;
4023 }
4024 }
4025
4026 return array_reverse($result);
4027 }
4028
4029
4030 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4041 public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4042 {
4043 // phpcs:enable
4044 global $conf;
4045 global $user;
4046
4047 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4048 if ($mode == 'bynumber') {
4049 $sql .= ", count(DISTINCT f.rowid)";
4050 }
4051 $sql .= ", sum(d.total_ht) as total_ht";
4052 $sql .= " FROM ".$this->db->prefix()."facturedet as d, ".$this->db->prefix()."facture as f, ".$this->db->prefix()."societe as s";
4053 if ($filteronproducttype >= 0) {
4054 $sql .= ", ".$this->db->prefix()."product as p";
4055 }
4056 if (!$user->hasRight('societe', 'client', 'voir')) {
4057 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4058 }
4059 $sql .= " WHERE f.rowid = d.fk_facture";
4060 if ($this->id > 0) {
4061 $sql .= " AND d.fk_product = ".((int) $this->id);
4062 } else {
4063 $sql .= " AND d.fk_product > 0";
4064 }
4065 if ($filteronproducttype >= 0) {
4066 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4067 }
4068 $sql .= " AND f.fk_soc = s.rowid";
4069 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4070 if (!$user->hasRight('societe', 'client', 'voir')) {
4071 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4072 }
4073 if ($socid > 0) {
4074 $sql .= " AND f.fk_soc = $socid";
4075 }
4076 $sql .= $morefilter;
4077 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4078 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4079
4080 return $this->_get_stats($sql, $mode, $year);
4081 }
4082
4083
4084 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4095 public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4096 {
4097 // phpcs:enable
4098 global $conf;
4099 global $user;
4100
4101 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4102 if ($mode == 'bynumber') {
4103 $sql .= ", count(DISTINCT f.rowid)";
4104 }
4105 $sql .= ", sum(d.total_ht) as total_ht";
4106 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as d, ".$this->db->prefix()."facture_fourn as f, ".$this->db->prefix()."societe as s";
4107 if ($filteronproducttype >= 0) {
4108 $sql .= ", ".$this->db->prefix()."product as p";
4109 }
4110 if (!$user->hasRight('societe', 'client', 'voir')) {
4111 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4112 }
4113 $sql .= " WHERE f.rowid = d.fk_facture_fourn";
4114 if ($this->id > 0) {
4115 $sql .= " AND d.fk_product = ".((int) $this->id);
4116 } else {
4117 $sql .= " AND d.fk_product > 0";
4118 }
4119 if ($filteronproducttype >= 0) {
4120 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4121 }
4122 $sql .= " AND f.fk_soc = s.rowid";
4123 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4124 if (!$user->hasRight('societe', 'client', 'voir')) {
4125 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4126 }
4127 if ($socid > 0) {
4128 $sql .= " AND f.fk_soc = $socid";
4129 }
4130 $sql .= $morefilter;
4131 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4132 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4133
4134 return $this->_get_stats($sql, $mode, $year);
4135 }
4136
4137 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4148 public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4149 {
4150 // phpcs:enable
4151 global $conf, $user;
4152
4153 $sql = "SELECT sum(d.qty) as qty, date_format(p.datep, '%Y%m')";
4154 if ($mode == 'bynumber') {
4155 $sql .= ", count(DISTINCT p.rowid)";
4156 }
4157 $sql .= ", sum(d.total_ht) as total_ht";
4158 $sql .= " FROM ".$this->db->prefix()."propaldet as d, ".$this->db->prefix()."propal as p, ".$this->db->prefix()."societe as s";
4159 if ($filteronproducttype >= 0) {
4160 $sql .= ", ".$this->db->prefix()."product as prod";
4161 }
4162 if (!$user->hasRight('societe', 'client', 'voir')) {
4163 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4164 }
4165 $sql .= " WHERE p.rowid = d.fk_propal";
4166 if ($this->id > 0) {
4167 $sql .= " AND d.fk_product = ".((int) $this->id);
4168 } else {
4169 $sql .= " AND d.fk_product > 0";
4170 }
4171 if ($filteronproducttype >= 0) {
4172 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4173 }
4174 $sql .= " AND p.fk_soc = s.rowid";
4175 $sql .= " AND p.entity IN (".getEntity('propal').")";
4176 if (!$user->hasRight('societe', 'client', 'voir')) {
4177 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4178 }
4179 if ($socid > 0) {
4180 $sql .= " AND p.fk_soc = ".((int) $socid);
4181 }
4182 $sql .= $morefilter;
4183 $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
4184 $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
4185
4186 return $this->_get_stats($sql, $mode, $year);
4187 }
4188
4189 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4200 public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4201 {
4202 // phpcs:enable
4203 global $conf;
4204 global $user;
4205
4206 $sql = "SELECT sum(d.qty) as qty, date_format(p.date_valid, '%Y%m')";
4207 if ($mode == 'bynumber') {
4208 $sql .= ", count(DISTINCT p.rowid)";
4209 }
4210 $sql .= ", sum(d.total_ht) as total_ht";
4211 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as d, ".$this->db->prefix()."supplier_proposal as p, ".$this->db->prefix()."societe as s";
4212 if ($filteronproducttype >= 0) {
4213 $sql .= ", ".$this->db->prefix()."product as prod";
4214 }
4215 if (!$user->hasRight('societe', 'client', 'voir')) {
4216 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4217 }
4218 $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
4219 if ($this->id > 0) {
4220 $sql .= " AND d.fk_product = ".((int) $this->id);
4221 } else {
4222 $sql .= " AND d.fk_product > 0";
4223 }
4224 if ($filteronproducttype >= 0) {
4225 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4226 }
4227 $sql .= " AND p.fk_soc = s.rowid";
4228 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
4229 if (!$user->hasRight('societe', 'client', 'voir')) {
4230 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4231 }
4232 if ($socid > 0) {
4233 $sql .= " AND p.fk_soc = ".((int) $socid);
4234 }
4235 $sql .= $morefilter;
4236 $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
4237 $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
4238
4239 return $this->_get_stats($sql, $mode, $year);
4240 }
4241
4242 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4253 public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4254 {
4255 // phpcs:enable
4256 global $conf, $user;
4257
4258 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4259 if ($mode == 'bynumber') {
4260 $sql .= ", count(DISTINCT c.rowid)";
4261 }
4262 $sql .= ", sum(d.total_ht) as total_ht";
4263 $sql .= " FROM ".$this->db->prefix()."commandedet as d, ".$this->db->prefix()."commande as c, ".$this->db->prefix()."societe as s";
4264 if ($filteronproducttype >= 0) {
4265 $sql .= ", ".$this->db->prefix()."product as p";
4266 }
4267 if (!$user->hasRight('societe', 'client', 'voir')) {
4268 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4269 }
4270 $sql .= " WHERE c.rowid = d.fk_commande";
4271 if ($this->id > 0) {
4272 $sql .= " AND d.fk_product = ".((int) $this->id);
4273 } else {
4274 $sql .= " AND d.fk_product > 0";
4275 }
4276 if ($filteronproducttype >= 0) {
4277 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4278 }
4279 $sql .= " AND c.fk_soc = s.rowid";
4280 $sql .= " AND c.entity IN (".getEntity('commande').")";
4281 if (!$user->hasRight('societe', 'client', 'voir')) {
4282 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4283 }
4284 if ($socid > 0) {
4285 $sql .= " AND c.fk_soc = ".((int) $socid);
4286 }
4287 $sql .= $morefilter;
4288 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4289 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4290
4291 return $this->_get_stats($sql, $mode, $year);
4292 }
4293
4294 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4305 public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4306 {
4307 // phpcs:enable
4308 global $conf, $user;
4309
4310 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4311 if ($mode == 'bynumber') {
4312 $sql .= ", count(DISTINCT c.rowid)";
4313 }
4314 $sql .= ", sum(d.total_ht) as total_ht";
4315 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as d, ".$this->db->prefix()."commande_fournisseur as c, ".$this->db->prefix()."societe as s";
4316 if ($filteronproducttype >= 0) {
4317 $sql .= ", ".$this->db->prefix()."product as p";
4318 }
4319 if (!$user->hasRight('societe', 'client', 'voir')) {
4320 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4321 }
4322 $sql .= " WHERE c.rowid = d.fk_commande";
4323 if ($this->id > 0) {
4324 $sql .= " AND d.fk_product = ".((int) $this->id);
4325 } else {
4326 $sql .= " AND d.fk_product > 0";
4327 }
4328 if ($filteronproducttype >= 0) {
4329 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4330 }
4331 $sql .= " AND c.fk_soc = s.rowid";
4332 $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
4333 if (!$user->hasRight('societe', 'client', 'voir')) {
4334 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4335 }
4336 if ($socid > 0) {
4337 $sql .= " AND c.fk_soc = ".((int) $socid);
4338 }
4339 $sql .= $morefilter;
4340 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4341 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4342
4343 return $this->_get_stats($sql, $mode, $year);
4344 }
4345
4346 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4357 public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4358 {
4359 // phpcs:enable
4360 global $conf, $user;
4361
4362 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_contrat, '%Y%m')";
4363 if ($mode == 'bynumber') {
4364 $sql .= ", count(DISTINCT c.rowid)";
4365 }
4366 $sql .= ", sum(d.total_ht) as total_ht";
4367 $sql .= " FROM ".$this->db->prefix()."contratdet as d, ".$this->db->prefix()."contrat as c, ".$this->db->prefix()."societe as s";
4368 if ($filteronproducttype >= 0) {
4369 $sql .= ", ".$this->db->prefix()."product as p";
4370 }
4371 if (!$user->hasRight('societe', 'client', 'voir')) {
4372 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4373 }
4374 $sql .= " WHERE c.entity IN (".getEntity('contract').")";
4375 $sql .= " AND c.rowid = d.fk_contrat";
4376
4377 if ($this->id > 0) {
4378 $sql .= " AND d.fk_product = ".((int) $this->id);
4379 } else {
4380 $sql .= " AND d.fk_product > 0";
4381 }
4382 if ($filteronproducttype >= 0) {
4383 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4384 }
4385 $sql .= " AND c.fk_soc = s.rowid";
4386
4387 if (!$user->hasRight('societe', 'client', 'voir')) {
4388 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4389 }
4390 if ($socid > 0) {
4391 $sql .= " AND c.fk_soc = ".((int) $socid);
4392 }
4393 $sql .= $morefilter;
4394 $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
4395 $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
4396
4397 return $this->_get_stats($sql, $mode, $year);
4398 }
4399
4400 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4411 public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4412 {
4413 // phpcs:enable
4414 global $conf, $user;
4415
4416 $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
4417 if ($mode == 'bynumber') {
4418 $sql .= ", count(DISTINCT d.rowid)";
4419 }
4420 $sql .= " FROM ".$this->db->prefix()."mrp_mo as d LEFT JOIN ".$this->db->prefix()."societe as s ON d.fk_soc = s.rowid";
4421 if ($filteronproducttype >= 0) {
4422 $sql .= ", ".$this->db->prefix()."product as p";
4423 }
4424 if (!$user->hasRight('societe', 'client', 'voir')) {
4425 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4426 }
4427
4428 $sql .= " WHERE d.entity IN (".getEntity('mo').")";
4429 $sql .= " AND d.status > 0";
4430
4431 if ($this->id > 0) {
4432 $sql .= " AND d.fk_product = ".((int) $this->id);
4433 } else {
4434 $sql .= " AND d.fk_product > 0";
4435 }
4436 if ($filteronproducttype >= 0) {
4437 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4438 }
4439
4440 if (!$user->hasRight('societe', 'client', 'voir')) {
4441 $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4442 }
4443 if ($socid > 0) {
4444 $sql .= " AND d.fk_soc = ".((int) $socid);
4445 }
4446 $sql .= $morefilter;
4447 $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
4448 $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
4449
4450 return $this->_get_stats($sql, $mode, $year);
4451 }
4452
4453 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4464 public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4465 {
4466 global $user;
4467
4468 // phpcs:enable
4469 // Clean parameters
4470 if (!is_numeric($id_pere)) {
4471 $id_pere = 0;
4472 }
4473 if (!is_numeric($id_fils)) {
4474 $id_fils = 0;
4475 }
4476 if (!is_numeric($incdec)) {
4477 $incdec = 0;
4478 }
4479
4480 $result = $this->del_sousproduit($id_pere, $id_fils);
4481 if ($result < 0) {
4482 return $result;
4483 }
4484
4485 // Check not already father of id_pere (to avoid father -> child -> father links)
4486 $sql = "SELECT fk_product_pere from ".$this->db->prefix()."product_association";
4487 $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere);
4488 if (!$this->db->query($sql)) {
4489 dol_print_error($this->db);
4490 return -1;
4491 } else {
4492 //Selection of the highest row
4493 $sql = "SELECT MAX(rang) as max_rank FROM ".$this->db->prefix()."product_association";
4494 $sql .= " WHERE fk_product_pere = ".((int) $id_pere);
4495 $resql = $this->db->query($sql);
4496 if ($resql) {
4497 $obj = $this->db->fetch_object($resql);
4498 $rank = $obj->max_rank + 1;
4499 //Addition of a product with the highest rank +1
4500 $sql = "INSERT INTO ".$this->db->prefix()."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)";
4501 $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".price2num($qty, 'MS').", ".price2num($incdec, 'MS').", ".((int) $rank).")";
4502 if (! $this->db->query($sql)) {
4503 dol_print_error($this->db);
4504 return -1;
4505 } else {
4506 if (!$notrigger) {
4507 // Call trigger
4508 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_ADD', $user);
4509 if ($result < 0) {
4510 $this->error = $this->db->lasterror();
4511 dol_syslog(get_class($this).'::addSubproduct error='.$this->error, LOG_ERR);
4512 return -1;
4513 }
4514 }
4515 // End call triggers
4516
4517 return 1;
4518 }
4519 } else {
4520 dol_print_error($this->db);
4521 return -1;
4522 }
4523 }
4524 }
4525
4526 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4537 public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4538 {
4539 global $user;
4540
4541 // phpcs:enable
4542 // Clean parameters
4543 if (!is_numeric($id_pere)) {
4544 $id_pere = 0;
4545 }
4546 if (!is_numeric($id_fils)) {
4547 $id_fils = 0;
4548 }
4549 if (!is_numeric($incdec)) {
4550 $incdec = 1;
4551 }
4552 if (!is_numeric($qty)) {
4553 $qty = 1;
4554 }
4555
4556 $sql = 'UPDATE '.$this->db->prefix().'product_association SET ';
4557 $sql .= 'qty = '.price2num($qty, 'MS');
4558 $sql .= ',incdec = '.price2num($incdec, 'MS');
4559 $sql .= ' WHERE fk_product_pere = '.((int) $id_pere).' AND fk_product_fils = '.((int) $id_fils);
4560
4561 if (!$this->db->query($sql)) {
4562 dol_print_error($this->db);
4563 return -1;
4564 } else {
4565 if (!$notrigger) {
4566 // Call trigger
4567 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_UPDATE', $user);
4568 if ($result < 0) {
4569 $this->error = $this->db->lasterror();
4570 dol_syslog(get_class($this).'::updateSubproduct error='.$this->error, LOG_ERR);
4571 return -1;
4572 }
4573 // End call triggers
4574 }
4575
4576 return 1;
4577 }
4578 }
4579
4580 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4589 public function del_sousproduit($fk_parent, $fk_child, $notrigger = 0)
4590 {
4591 global $user;
4592
4593 // phpcs:enable
4594 if (!is_numeric($fk_parent)) {
4595 $fk_parent = 0;
4596 }
4597 if (!is_numeric($fk_child)) {
4598 $fk_child = 0;
4599 }
4600
4601 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
4602 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4603 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4604
4605 dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
4606 if (!$this->db->query($sql)) {
4607 dol_print_error($this->db);
4608 return -1;
4609 }
4610
4611 // Updated ranks so that none are missing
4612 $sqlrank = "SELECT rowid, rang FROM ".$this->db->prefix()."product_association";
4613 $sqlrank .= " WHERE fk_product_pere = ".((int) $fk_parent);
4614 $sqlrank .= " ORDER BY rang";
4615 $resqlrank = $this->db->query($sqlrank);
4616 if ($resqlrank) {
4617 $cpt = 0;
4618 while ($objrank = $this->db->fetch_object($resqlrank)) {
4619 $cpt++;
4620 $sql = "UPDATE ".$this->db->prefix()."product_association";
4621 $sql .= " SET rang = ".((int) $cpt);
4622 $sql .= " WHERE rowid = ".((int) $objrank->rowid);
4623 if (! $this->db->query($sql)) {
4624 dol_print_error($this->db);
4625 return -1;
4626 }
4627 }
4628 }
4629
4630 if (!$notrigger) {
4631 // Call trigger
4632 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_DELETE', $user);
4633 if ($result < 0) {
4634 $this->error = $this->db->lasterror();
4635 dol_syslog(get_class($this).'::delSubproduct error='.$this->error, LOG_ERR);
4636 return -1;
4637 }
4638 // End call triggers
4639 }
4640
4641 return 1;
4642 }
4643
4644 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4652 public function is_sousproduit($fk_parent, $fk_child)
4653 {
4654 // phpcs:enable
4655 $sql = "SELECT fk_product_pere, qty, incdec";
4656 $sql .= " FROM ".$this->db->prefix()."product_association";
4657 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4658 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4659
4660 $result = $this->db->query($sql);
4661 if ($result) {
4662 $num = $this->db->num_rows($result);
4663
4664 if ($num > 0) {
4665 $obj = $this->db->fetch_object($result);
4666
4667 $this->is_sousproduit_qty = $obj->qty;
4668 $this->is_sousproduit_incdec = $obj->incdec;
4669
4670 return 1;
4671 } else {
4672 return 0;
4673 }
4674 } else {
4675 dol_print_error($this->db);
4676 return -1;
4677 }
4678 }
4679
4680
4681 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4692 public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
4693 {
4694 // phpcs:enable
4695 global $conf;
4696
4697 $now = dol_now();
4698
4699 dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
4700
4701 // Clean parameters
4702 $quantity = price2num($quantity, 'MS');
4703
4704 if ($ref_fourn) {
4705 // Check if ref is not already used
4706 $sql = "SELECT rowid, fk_product";
4707 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4708 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4709 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4710 $sql .= " AND fk_product <> ".((int) $this->id);
4711 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4712
4713 $resql = $this->db->query($sql);
4714 if ($resql) {
4715 $obj = $this->db->fetch_object($resql);
4716 if ($obj) {
4717 // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
4718 $this->product_id_already_linked = $obj->fk_product;
4719 return -3;
4720 }
4721 $this->db->free($resql);
4722 }
4723 }
4724
4725 $sql = "SELECT rowid";
4726 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4727 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4728 if ($ref_fourn) {
4729 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4730 } else {
4731 $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
4732 }
4733 $sql .= " AND quantity = ".((float) $quantity);
4734 $sql .= " AND fk_product = ".((int) $this->id);
4735 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4736
4737 $resql = $this->db->query($sql);
4738 if ($resql) {
4739 $obj = $this->db->fetch_object($resql);
4740
4741 // The reference supplier does not exist, we create it for this product.
4742 if (empty($obj)) {
4743 $sql = "INSERT INTO ".$this->db->prefix()."product_fournisseur_price(";
4744 $sql .= "datec";
4745 $sql .= ", entity";
4746 $sql .= ", fk_product";
4747 $sql .= ", fk_soc";
4748 $sql .= ", ref_fourn";
4749 $sql .= ", quantity";
4750 $sql .= ", fk_user";
4751 $sql .= ", tva_tx";
4752 $sql .= ") VALUES (";
4753 $sql .= "'".$this->db->idate($now)."'";
4754 $sql .= ", ".((int) $conf->entity);
4755 $sql .= ", ".((int) $this->id);
4756 $sql .= ", ".((int) $id_fourn);
4757 $sql .= ", '".$this->db->escape($ref_fourn)."'";
4758 $sql .= ", ".((float) $quantity);
4759 $sql .= ", ".((int) $user->id);
4760 $sql .= ", 0";
4761 $sql .= ")";
4762
4763 if ($this->db->query($sql)) {
4764 $this->product_fourn_price_id = $this->db->last_insert_id($this->db->prefix()."product_fournisseur_price");
4765 return 1;
4766 } else {
4767 $this->error = $this->db->lasterror();
4768 return -1;
4769 }
4770 } else {
4771 // If the supplier price already exists for this product and quantity
4772 $this->product_fourn_price_id = $obj->rowid;
4773 return 0;
4774 }
4775 } else {
4776 $this->error = $this->db->lasterror();
4777 return -2;
4778 }
4779 }
4780
4781
4782 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4788 public function list_suppliers()
4789 {
4790 // phpcs:enable
4791 global $conf;
4792
4793 $list = array();
4794
4795 $sql = "SELECT DISTINCT p.fk_soc";
4796 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as p";
4797 $sql .= " WHERE p.fk_product = ".((int) $this->id);
4798 $sql .= " AND p.entity = ".((int) $conf->entity);
4799
4800 $result = $this->db->query($sql);
4801 if ($result) {
4802 $num = $this->db->num_rows($result);
4803 $i = 0;
4804 while ($i < $num) {
4805 $obj = $this->db->fetch_object($result);
4806 $list[$i] = $obj->fk_soc;
4807 $i++;
4808 }
4809 }
4810
4811 return $list;
4812 }
4813
4814 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4822 public function clone_price($fromId, $toId)
4823 {
4824 global $conf, $user;
4825
4826 $now = dol_now();
4827
4828 $this->db->begin();
4829
4830 // prices
4831 $sql = "INSERT INTO ".$this->db->prefix()."product_price (";
4832 $sql .= " entity";
4833 $sql .= ", fk_product";
4834 $sql .= ", date_price";
4835 $sql .= ", price_level";
4836 $sql .= ", price";
4837 $sql .= ", price_ttc";
4838 $sql .= ", price_min";
4839 $sql .= ", price_min_ttc";
4840 $sql .= ", price_base_type";
4841 $sql .= ", price_label";
4842 $sql .= ", default_vat_code";
4843 $sql .= ", tva_tx";
4844 $sql .= ", recuperableonly";
4845 $sql .= ", localtax1_tx";
4846 $sql .= ", localtax1_type";
4847 $sql .= ", localtax2_tx";
4848 $sql .= ", localtax2_type";
4849 $sql .= ", fk_user_author";
4850 $sql .= ", tosell";
4851 $sql .= ", price_by_qty";
4852 $sql .= ", fk_price_expression";
4853 $sql .= ", fk_multicurrency";
4854 $sql .= ", multicurrency_code";
4855 $sql .= ", multicurrency_tx";
4856 $sql .= ", multicurrency_price";
4857 $sql .= ", multicurrency_price_ttc";
4858 $sql .= ")";
4859 $sql .= " SELECT";
4860 $sql .= " entity";
4861 $sql .= ", ".$toId;
4862 $sql .= ", '".$this->db->idate($now)."'";
4863 $sql .= ", price_level";
4864 $sql .= ", price";
4865 $sql .= ", price_ttc";
4866 $sql .= ", price_min";
4867 $sql .= ", price_min_ttc";
4868 $sql .= ", price_base_type";
4869 $sql .= ", price_label";
4870 $sql .= ", default_vat_code";
4871 $sql .= ", tva_tx";
4872 $sql .= ", recuperableonly";
4873 $sql .= ", localtax1_tx";
4874 $sql .= ", localtax1_type";
4875 $sql .= ", localtax2_tx";
4876 $sql .= ", localtax2_type";
4877 $sql .= ", ".$user->id;
4878 $sql .= ", tosell";
4879 $sql .= ", price_by_qty";
4880 $sql .= ", fk_price_expression";
4881 $sql .= ", fk_multicurrency";
4882 $sql .= ", multicurrency_code";
4883 $sql .= ", multicurrency_tx";
4884 $sql .= ", multicurrency_price";
4885 $sql .= ", multicurrency_price_ttc";
4886 $sql .= " FROM ".$this->db->prefix()."product_price ps";
4887 $sql .= " WHERE fk_product = ".((int) $fromId);
4888 $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)";
4889 $sql .= " ORDER BY date_price DESC";
4890
4891 dol_syslog(__METHOD__, LOG_DEBUG);
4892 $resql = $this->db->query($sql);
4893 if (!$resql) {
4894 $this->db->rollback();
4895 return -1;
4896 }
4897
4898 $this->db->commit();
4899 return 1;
4900 }
4901
4902 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4910 public function clone_associations($fromId, $toId)
4911 {
4912 // phpcs:enable
4913 $this->db->begin();
4914
4915 $sql = 'INSERT INTO '.$this->db->prefix().'product_association (fk_product_pere, fk_product_fils, qty, incdec)';
4916 $sql .= " SELECT ".$toId.", fk_product_fils, qty, incdec FROM ".$this->db->prefix()."product_association";
4917 $sql .= " WHERE fk_product_pere = ".((int) $fromId);
4918
4919 dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
4920 if (!$this->db->query($sql)) {
4921 $this->db->rollback();
4922 return -1;
4923 }
4924
4925 $this->db->commit();
4926 return 1;
4927 }
4928
4929 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4937 public function clone_fournisseurs($fromId, $toId)
4938 {
4939 // phpcs:enable
4940 $this->db->begin();
4941
4942 $now = dol_now();
4943
4944 // les fournisseurs
4945 /*$sql = "INSERT ".$this->db->prefix()."product_fournisseur ("
4946 . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
4947 . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
4948 . " FROM ".$this->db->prefix()."product_fournisseur"
4949 . " WHERE fk_product = ".((int) $fromId);
4950
4951 if ( ! $this->db->query($sql ) )
4952 {
4953 $this->db->rollback();
4954 return -1;
4955 }*/
4956
4957 // les prix de fournisseurs.
4958 $sql = "INSERT ".$this->db->prefix()."product_fournisseur_price (";
4959 $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user, tva_tx)";
4960 $sql .= " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, price, quantity, fk_user, tva_tx";
4961 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4962 $sql .= " WHERE fk_product = ".((int) $fromId);
4963
4964 dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
4965 $resql = $this->db->query($sql);
4966 if (!$resql) {
4967 $this->db->rollback();
4968 return -1;
4969 } else {
4970 $this->db->commit();
4971 return 1;
4972 }
4973 }
4974
4975 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4988 public function fetch_prod_arbo($prod, $compl_path = '', $multiply = 1, $level = 1, $id_parent = 0, $ignore_stock_load = 0)
4989 {
4990 // phpcs:enable
4991 global $conf, $langs;
4992
4993 $tmpproduct = null;
4994 //var_dump($prod);
4995 foreach ($prod as $id_product => $desc_pere) { // $id_product is 0 (first call starting with root top) or an id of a sub_product
4996 if (is_array($desc_pere)) { // If desc_pere is an array, this means it's a child
4997 $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
4998 $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
4999 $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
5000 $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
5001 $incdec = (!empty($desc_pere[4]) ? $desc_pere[4] : 0);
5002
5003 if ($multiply < 1) {
5004 $multiply = 1;
5005 }
5006
5007 //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
5008 if (is_null($tmpproduct)) {
5009 $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
5010 }
5011 $tmpproduct->fetch($id); // Load product to get ->ref
5012
5013 if (empty($ignore_stock_load) && ($tmpproduct->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
5014 $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
5015 }
5016
5017 $this->res[] = array(
5018 'id' => $id, // Id product
5019 'id_parent' => $id_parent,
5020 'ref' => $tmpproduct->ref, // Ref product
5021 'nb' => $nb, // Nb of units that compose parent product
5022 'nb_total' => $nb * $multiply, // Nb of units for all nb of product
5023 'stock' => $tmpproduct->stock_reel, // Stock
5024 'stock_alert' => $tmpproduct->seuil_stock_alerte, // Stock alert
5025 'label' => $label,
5026 'fullpath' => $compl_path.$label, // Label
5027 'type' => $type, // Nb of units that compose parent product
5028 'desiredstock' => $tmpproduct->desiredstock,
5029 'level' => $level,
5030 'incdec' => $incdec,
5031 'entity' => $tmpproduct->entity
5032 );
5033
5034 // Recursive call if there child has children of its own
5035 if (isset($desc_pere['childs']) && is_array($desc_pere['childs'])) {
5036 //print 'YYY We go down for '.$desc_pere[3]." -> \n";
5037 $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1] * $multiply, $level + 1, $id, $ignore_stock_load);
5038 }
5039 }
5040 }
5041 }
5042
5043 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5052 public function get_arbo_each_prod($multiply = 1, $ignore_stock_load = 0)
5053 {
5054 // phpcs:enable
5055 $this->res = array();
5056 if (isset($this->sousprods) && is_array($this->sousprods)) {
5057 foreach ($this->sousprods as $prod_name => $desc_product) {
5058 if (is_array($desc_product)) {
5059 $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id, $ignore_stock_load); // This set $this->res
5060 }
5061 }
5062 }
5063 //var_dump($res);
5064 return $this->res;
5065 }
5066
5074 public function hasFatherOrChild($mode = 0)
5075 {
5076 $nb = 0;
5077
5078 $sql = "SELECT COUNT(pa.rowid) as nb";
5079 $sql .= " FROM ".$this->db->prefix()."product_association as pa";
5080 if ($mode == 0) {
5081 $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id)." OR pa.fk_product_pere = ".((int) $this->id);
5082 } elseif ($mode == -1) {
5083 $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)
5084 } elseif ($mode == 1) {
5085 $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)
5086 }
5087
5088 $resql = $this->db->query($sql);
5089 if ($resql) {
5090 $obj = $this->db->fetch_object($resql);
5091 if ($obj) {
5092 $nb = $obj->nb;
5093 }
5094 } else {
5095 return -1;
5096 }
5097
5098 return $nb;
5099 }
5100
5106 public function hasVariants()
5107 {
5108 $nb = 0;
5109 $sql = "SELECT count(rowid) as nb FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_parent = ".((int) $this->id);
5110 $sql .= " AND entity IN (".getEntity('product').")";
5111
5112 $resql = $this->db->query($sql);
5113 if ($resql) {
5114 $obj = $this->db->fetch_object($resql);
5115 if ($obj) {
5116 $nb = $obj->nb;
5117 }
5118 }
5119
5120 return $nb;
5121 }
5122
5123
5129 public function isVariant()
5130 {
5131 global $conf;
5132 if (isModEnabled('variants')) {
5133 $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_child = ".((int) $this->id)." AND entity IN (".getEntity('product').")";
5134
5135 $query = $this->db->query($sql);
5136
5137 if ($query) {
5138 if (!$this->db->num_rows($query)) {
5139 return false;
5140 }
5141 return true;
5142 } else {
5143 dol_print_error($this->db);
5144 return -1;
5145 }
5146 } else {
5147 return false;
5148 }
5149 }
5150
5157 public function getFather()
5158 {
5159 $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";
5160 $sql .= ", p.tosell as status, p.tobuy as status_buy";
5161 $sql .= " FROM ".$this->db->prefix()."product_association as pa,";
5162 $sql .= " ".$this->db->prefix()."product as p";
5163 $sql .= " WHERE p.rowid = pa.fk_product_pere";
5164 $sql .= " AND pa.fk_product_fils = ".((int) $this->id);
5165
5166 $res = $this->db->query($sql);
5167 if ($res) {
5168 $prods = array();
5169 while ($record = $this->db->fetch_array($res)) {
5170 // $record['id'] = $record['rowid'] = id of father
5171 $prods[$record['id']]['id'] = $record['rowid'];
5172 $prods[$record['id']]['ref'] = $record['ref'];
5173 $prods[$record['id']]['label'] = $record['label'];
5174 $prods[$record['id']]['qty'] = $record['qty'];
5175 $prods[$record['id']]['incdec'] = $record['incdec'];
5176 $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
5177 $prods[$record['id']]['entity'] = $record['entity'];
5178 $prods[$record['id']]['status'] = $record['status'];
5179 $prods[$record['id']]['status_buy'] = $record['status_buy'];
5180 }
5181 return $prods;
5182 } else {
5183 dol_print_error($this->db);
5184 return -1;
5185 }
5186 }
5187
5188
5198 public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array())
5199 {
5200 global $alreadyfound;
5201
5202 if (empty($id)) {
5203 return array();
5204 }
5205
5206 $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
5207 $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
5208 $sql .= " pa.rowid as fk_association, pa.rang";
5209 $sql .= " FROM ".$this->db->prefix()."product as p,";
5210 $sql .= " ".$this->db->prefix()."product_association as pa";
5211 $sql .= " WHERE p.rowid = pa.fk_product_fils";
5212 $sql .= " AND pa.fk_product_pere = ".((int) $id);
5213 $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
5214 $sql .= " ORDER BY pa.rang";
5215
5216 dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG);
5217
5218 if ($level == 1) {
5219 $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
5220 }
5221 // Protection against infinite loop
5222 if ($level > 30) {
5223 return array();
5224 }
5225
5226 $res = $this->db->query($sql);
5227 if ($res) {
5228 $prods = array();
5229 while ($rec = $this->db->fetch_array($res)) {
5230 if (!empty($alreadyfound[$rec['rowid']])) {
5231 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);
5232 if (in_array($rec['id'], $parents)) {
5233 continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5234 }
5235 }
5236 $alreadyfound[$rec['rowid']] = 1;
5237 $prods[$rec['rowid']] = array(
5238 0 => $rec['rowid'],
5239 1 => $rec['qty'],
5240 2 => $rec['fk_product_type'],
5241 3 => $this->db->escape($rec['label']),
5242 4 => $rec['incdec'],
5243 5 => $rec['ref'],
5244 6 => $rec['fk_association'],
5245 7 => $rec['rang']
5246 );
5247 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5248 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5249 if (empty($firstlevelonly)) {
5250 $parents[] = $rec['rowid'];
5251 $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents);
5252 foreach ($listofchilds as $keyChild => $valueChild) {
5253 $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5254 }
5255 }
5256 }
5257
5258 return $prods;
5259 } else {
5260 dol_print_error($this->db);
5261 return -1;
5262 }
5263 }
5264
5265 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5272 public function get_sousproduits_arbo()
5273 {
5274 // phpcs:enable
5275 $parent = array();
5276
5277 foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5278 $parent[$this->label][$keyChild] = $valueChild;
5279 }
5280 foreach ($parent as $key => $value) { // key=label, value is array of children
5281 $this->sousprods[$key] = $value;
5282 }
5283 }
5284
5291 public function getTooltipContentArray($params)
5292 {
5293 global $conf, $langs, $user;
5294
5295 $langs->loadLangs(array('products', 'other'));
5296
5297 $datas = array();
5298 $nofetch = !empty($params['nofetch']);
5299
5300 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5301 return ['optimize' => $langs->trans("ShowProduct")];
5302 }
5303
5304 // Does user has permission to read product/service
5305 $permissiontoreadproduct = 0;
5306 if ($this->type == self::TYPE_PRODUCT && $user->hasRight('product', 'read')) {
5307 $permissiontoreadproduct = 1;
5308 }
5309 if ($this->type == self::TYPE_SERVICE && $user->hasRight('service', 'read')) {
5310 $permissiontoreadproduct = 1;
5311 }
5312
5313 if (!empty($this->entity) && $permissiontoreadproduct) {
5314 $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, 1);
5315 if ($this->nbphoto > 0) {
5316 $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5317 }
5318 }
5319
5320 if ($this->type == Product::TYPE_PRODUCT) {
5321 $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5322 } elseif ($this->type == Product::TYPE_SERVICE) {
5323 $datas['picto'] = img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5324 }
5325 if (isset($this->status) && isset($this->status_buy)) {
5326 $datas['status'] = ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5327 }
5328
5329 if (!empty($this->ref)) {
5330 $datas['ref'] = '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5331 }
5332 if (!empty($this->label)) {
5333 $datas['label'] = '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label;
5334 }
5335
5336 if ($permissiontoreadproduct) {
5337 if (!empty($this->description)) {
5338 $datas['description'] = '<br><b>'.$langs->trans('ProductDescription').':</b> '.dolGetFirstLineOfText($this->description, 5);
5339 }
5340 if ($this->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
5341 if (isModEnabled('productbatch')) {
5342 $langs->load("productbatch");
5343 $datas['batchstatus'] = "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5344 }
5345 }
5346 if (isModEnabled('barcode')) {
5347 $datas['barcode'] = '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5348 }
5349
5350 if ($this->type == Product::TYPE_PRODUCT) {
5351 if ($this->weight) {
5352 $datas['weight'] = "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5353 }
5354 $labelsize = "";
5355 if ($this->length) {
5356 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5357 }
5358 if ($this->width) {
5359 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5360 }
5361 if ($this->height) {
5362 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5363 }
5364 if ($labelsize) {
5365 $datas['size'] = "<br>".$labelsize;
5366 }
5367
5368 $labelsurfacevolume = "";
5369 if ($this->surface) {
5370 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5371 }
5372 if ($this->volume) {
5373 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5374 }
5375 if ($labelsurfacevolume) {
5376 $datas['surface'] = "<br>" . $labelsurfacevolume;
5377 }
5378 }
5379 if ($this->type == Product::TYPE_SERVICE && !empty($this->duration_value)) {
5380 // Duration
5381 $datas['duration'] = '<br><b>'.$langs->trans("Duration").':</b> '.$this->duration_value;
5382 if ($this->duration_value > 1) {
5383 $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"));
5384 } elseif ($this->duration_value > 0) {
5385 $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"));
5386 }
5387 $datas['duration'] .= (!empty($this->duration_unit) && isset($dur[$this->duration_unit]) ? "&nbsp;".$langs->trans($dur[$this->duration_unit]) : '');
5388 }
5389 if (empty($user->socid)) {
5390 if (!empty($this->pmp) && $this->pmp) {
5391 $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5392 }
5393
5394 if (isModEnabled('accounting')) {
5395 if ($this->status && isset($this->accountancy_code_sell)) {
5396 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5397 $selllabel = '<br>';
5398 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5399 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5400 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5401 $datas['accountancysell'] = $selllabel;
5402 }
5403 if ($this->status_buy && isset($this->accountancy_code_buy)) {
5404 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5405 $buylabel = '';
5406 if (empty($this->status)) {
5407 $buylabel .= '<br>';
5408 }
5409 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5410 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5411 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5412 $datas['accountancybuy'] = $buylabel;
5413 }
5414 }
5415 }
5416 // show categories for this record only in ajax to not overload lists
5417 if (isModEnabled('category') && !$nofetch) {
5418 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5419 $form = new Form($this->db);
5420 $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5421 }
5422 }
5423
5424 return $datas;
5425 }
5426
5440 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5441 {
5442 global $langs, $hookmanager;
5443 include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5444
5445 $result = '';
5446
5447 $newref = $this->ref;
5448 if ($maxlength) {
5449 $newref = dol_trunc($newref, $maxlength, 'middle');
5450 }
5451 $params = [
5452 'id' => $this->id,
5453 'objecttype' => (isset($this->type) ? ($this->type == 1 ? 'service' : 'product') : $this->element),
5454 'option' => $option,
5455 'nofetch' => 1,
5456 ];
5457 $classfortooltip = 'classfortooltip';
5458 $dataparams = '';
5459 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5460 $classfortooltip = 'classforajaxtooltip';
5461 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5462 $label = '';
5463 } else {
5464 $label = implode($this->getTooltipContentArray($params));
5465 }
5466
5467 $linkclose = '';
5468 if (empty($notooltip)) {
5469 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5470 $label = $langs->trans("ShowProduct");
5471 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5472 }
5473 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5474 $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5475 } else {
5476 $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5477 }
5478
5479 if ($option == 'supplier' || $option == 'category') {
5480 $url = DOL_URL_ROOT.'/product/price_suppliers.php?id='.$this->id;
5481 } elseif ($option == 'stock') {
5482 $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5483 } elseif ($option == 'composition') {
5484 $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5485 } else {
5486 $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5487 }
5488
5489 if ($option !== 'nolink') {
5490 // Add param to save lastsearch_values or not
5491 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
5492 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
5493 $add_save_lastsearch_values = 1;
5494 }
5495 if ($add_save_lastsearch_values) {
5496 $url .= '&save_lastsearch_values=1';
5497 }
5498 }
5499
5500 $linkstart = '<a href="'.$url.'"';
5501 $linkstart .= $linkclose.'>';
5502 $linkend = '</a>';
5503
5504 $result .= $linkstart;
5505 if ($withpicto) {
5506 if ($this->type == Product::TYPE_PRODUCT) {
5507 $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5508 }
5509 if ($this->type == Product::TYPE_SERVICE) {
5510 $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5511 }
5512 }
5513 $result .= '<span class="aaa">'.dol_escape_htmltag($newref).'</span>';
5514 $result .= $linkend;
5515 if ($withpicto != 2) {
5516 $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
5517 }
5518
5519 global $action;
5520 $hookmanager->initHooks(array('productdao'));
5521 $parameters = array('id' => $this->id, 'getnomurl' => &$result, 'label' => &$label);
5522 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5523 if ($reshook > 0) {
5524 $result = $hookmanager->resPrint;
5525 } else {
5526 $result .= $hookmanager->resPrint;
5527 }
5528
5529 return $result;
5530 }
5531
5532
5543 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
5544 {
5545 global $conf, $user, $langs;
5546
5547 $langs->load("products");
5548 $outputlangs->load("products");
5549
5550 // Positionne le modele sur le nom du modele a utiliser
5551 if (!dol_strlen($modele)) {
5552 $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
5553 }
5554
5555 $modelpath = "core/modules/product/doc/";
5556
5557 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
5558 }
5559
5567 public function getLibStatut($mode = 0, $type = 0)
5568 {
5569 switch ($type) {
5570 case 0:
5571 return $this->LibStatut($this->status, $mode, $type);
5572 case 1:
5573 return $this->LibStatut($this->status_buy, $mode, $type);
5574 case 2:
5575 return $this->LibStatut($this->status_batch, $mode, $type);
5576 default:
5577 //Simulate previous behavior but should return an error string
5578 return $this->LibStatut($this->status_buy, $mode, $type);
5579 }
5580 }
5581
5582 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5591 public function LibStatut($status, $mode = 0, $type = 0)
5592 {
5593 // phpcs:enable
5594 global $conf, $langs;
5595
5596 $labelStatus = $labelStatusShort = '';
5597
5598 $langs->load('products');
5599 if (isModEnabled('productbatch')) {
5600 $langs->load("productbatch");
5601 }
5602
5603 if ($type == 2) {
5604 switch ($mode) {
5605 case 0:
5606 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
5607 return dolGetStatus($label);
5608 case 1:
5609 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
5610 return dolGetStatus($label);
5611 case 2:
5612 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
5613 case 3:
5614 return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
5615 case 4:
5616 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
5617 case 5:
5618 return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
5619 default:
5620 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
5621 }
5622 }
5623
5624 $statuttrans = empty($status) ? 'status5' : 'status4';
5625
5626 if ($status == 0) {
5627 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5628 if ($type == 0) {
5629 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
5630 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
5631 } elseif ($type == 1) {
5632 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
5633 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
5634 } elseif ($type == 2) {
5635 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
5636 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
5637 }
5638 } elseif ($status == 1) {
5639 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5640 if ($type == 0) {
5641 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
5642 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
5643 } elseif ($type == 1) {
5644 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
5645 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
5646 } elseif ($type == 2) {
5647 $labelStatus = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial'));
5648 $labelStatusShort = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort'));
5649 }
5650 } elseif ($type == 2 && $status == 2) {
5651 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
5652 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
5653 }
5654
5655 if ($mode > 6) {
5656 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
5657 } else {
5658 return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
5659 }
5660 }
5661
5662
5668 public function getLibFinished()
5669 {
5670 global $langs;
5671 $langs->load('products');
5672 $label = '';
5673
5674 if (isset($this->finished) && $this->finished >= 0) {
5675 $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
5676 $resql = $this->db->query($sql);
5677 if (!$resql) {
5678 $this->error = $this->db->error().' sql='.$sql;
5679 dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
5680 return -1;
5681 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
5682 $label = $langs->trans($res['label']);
5683 }
5684 $this->db->free($resql);
5685 }
5686
5687 return $label;
5688 }
5689
5690
5691 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5708 public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
5709 {
5710 // phpcs:enable
5711 if ($id_entrepot) {
5712 $this->db->begin();
5713
5714 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5715
5716 if ($nbpiece < 0) {
5717 if (!$movement) {
5718 $movement = 1;
5719 }
5720 $nbpiece = abs($nbpiece);
5721 }
5722 $op = array();
5723 $op[0] = "+".trim((string) $nbpiece);
5724 $op[1] = "-".trim((string) $nbpiece);
5725
5726 $movementstock = new MouvementStock($this->db);
5727 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->origin_id
5728 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
5729
5730 if ($result >= 0) {
5731 if ($extrafields) {
5732 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5733 $movementstock->array_options = $array_options;
5734 $movementstock->insertExtraFields();
5735 }
5736 $this->db->commit();
5737 return 1;
5738 } else {
5739 $this->error = $movementstock->error;
5740 $this->errors = $movementstock->errors;
5741
5742 $this->db->rollback();
5743 return -1;
5744 }
5745 }
5746
5747 return -1;
5748 }
5749
5750 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5771 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)
5772 {
5773 // phpcs:enable
5774 if ($id_entrepot) {
5775 $this->db->begin();
5776
5777 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5778
5779 if ($nbpiece < 0) {
5780 if (!$movement) {
5781 $movement = 1;
5782 }
5783 $nbpiece = abs($nbpiece);
5784 }
5785
5786 $op = array();
5787 $op[0] = "+".trim((string) $nbpiece);
5788 $op[1] = "-".trim((string) $nbpiece);
5789
5790 $movementstock = new MouvementStock($this->db);
5791 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->fk_origin
5792 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct, 0, $force_update_batch);
5793
5794 if ($result >= 0) {
5795 if ($extrafields) {
5796 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5797 $movementstock->array_options = $array_options;
5798 $movementstock->insertExtraFields();
5799 }
5800 $this->db->commit();
5801 return 1;
5802 } else {
5803 $this->error = $movementstock->error;
5804 $this->errors = $movementstock->errors;
5805
5806 $this->db->rollback();
5807 return -1;
5808 }
5809 }
5810 return -1;
5811 }
5812
5813 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5826 public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
5827 {
5828 // phpcs:enable
5829 global $conf;
5830
5831 $this->stock_reel = 0;
5832 $this->stock_warehouse = array();
5833 $this->stock_theorique = 0;
5834
5835 // Set filter on warehouse status
5836 $warehouseStatus = array();
5837 if (preg_match('/warehouseclosed/', $option)) {
5839 }
5840 if (preg_match('/warehouseopen/', $option)) {
5842 }
5843 if (preg_match('/warehouseinternal/', $option)) {
5844 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
5846 } else {
5848 }
5849 }
5850
5851 $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
5852 $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
5853 $sql .= ", ".$this->db->prefix()."entrepot as w";
5854 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
5855 $sql .= " AND w.rowid = ps.fk_entrepot";
5856 $sql .= " AND ps.fk_product = ".((int) $this->id);
5857 if (count($warehouseStatus)) {
5858 $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
5859 }
5860
5861 $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;
5862
5863 dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
5864 $result = $this->db->query($sql);
5865 if ($result) {
5866 $num = $this->db->num_rows($result);
5867 $i = 0;
5868 if ($num > 0) {
5869 while ($i < $num) {
5870 $row = $this->db->fetch_object($result);
5871 $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
5872 $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
5873 $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
5874 if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
5875 $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
5876 }
5877 $this->stock_reel += $row->reel;
5878 $i++;
5879 }
5880 }
5881 $this->db->free($result);
5882
5883 if (!preg_match('/novirtual/', $option)) {
5884 $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
5885 }
5886
5887 return 1;
5888 } else {
5889 $this->error = $this->db->lasterror();
5890 return -1;
5891 }
5892 }
5893
5894
5895 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5905 public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
5906 {
5907 // phpcs:enable
5908 global $conf, $hookmanager, $action;
5909
5910 $stock_commande_client = 0;
5911 $stock_commande_fournisseur = 0;
5912 $stock_sending_client = 0;
5913 $stock_reception_fournisseur = 0;
5914 $stock_inproduction = 0;
5915
5916 //dol_syslog("load_virtual_stock");
5917
5918 if (isModEnabled('order')) {
5919 $result = $this->load_stats_commande(0, '1,2', 1);
5920 if ($result < 0) {
5921 dol_print_error($this->db, $this->error);
5922 }
5923 $stock_commande_client = $this->stats_commande['qty'];
5924 }
5925 if (isModEnabled("shipping")) {
5926 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
5927 $filterShipmentStatus = '';
5928 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
5929 $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
5930 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
5931 $filterShipmentStatus = Expedition::STATUS_CLOSED;
5932 }
5933 $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
5934 if ($result < 0) {
5935 dol_print_error($this->db, $this->error);
5936 }
5937 $stock_sending_client = $this->stats_expedition['qty'];
5938 }
5939 // Include supplier order lines
5940 if (isModEnabled("supplier_order")) {
5941 $filterStatus = getDolGlobalString('SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK', '3,4');
5942 if (isset($includedraftpoforvirtual)) {
5943 $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
5944 }
5945 $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
5946 if ($result < 0) {
5947 dol_print_error($this->db, $this->error);
5948 }
5949 $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
5950 }
5951 // Include reception lines (when module reception is NOT used)
5952 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && !isModEnabled('reception')) {
5953 // Case module reception is not used
5954 $filterStatus = '4';
5955 if (isset($includedraftpoforvirtual)) {
5956 $filterStatus = '0,'.$filterStatus;
5957 }
5958 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
5959 if ($result < 0) {
5960 dol_print_error($this->db, $this->error);
5961 }
5962 $stock_reception_fournisseur = $this->stats_reception['qty'];
5963 }
5964 // Include reception lines (when module reception is used)
5965 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && isModEnabled("reception")) {
5966 // Case module reception is used
5967 $filterStatus = '4';
5968 if (isset($includedraftpoforvirtual)) {
5969 $filterStatus = '0,'.$filterStatus;
5970 }
5971 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock); // Use same tables than when module reception is not used.
5972 if ($result < 0) {
5973 dol_print_error($this->db, $this->error);
5974 }
5975 $stock_reception_fournisseur = $this->stats_reception['qty'];
5976 }
5977 // Include manufacturing
5978 if (isModEnabled('mrp')) {
5979 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
5980 if ($result < 0) {
5981 dol_print_error($this->db, $this->error);
5982 }
5983 $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
5984 }
5985
5986 $this->stock_theorique = $this->stock_reel + $stock_inproduction;
5987
5988 // Stock decrease mode
5989 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
5990 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
5991 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER')) {
5992 $this->stock_theorique += 0;
5993 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
5994 $this->stock_theorique -= $stock_commande_client;
5995 }
5996 // Stock Increase mode
5997 if (getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION') || getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
5998 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5999 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
6000 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6001 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) {
6002 $this->stock_theorique -= $stock_reception_fournisseur;
6003 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
6004 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6005 }
6006
6007 $parameters = array('id' => $this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
6008 // Note that $action and $object may have been modified by some hooks
6009 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
6010 if ($reshook > 0) {
6011 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
6012 } elseif ($reshook == 0 && isset($hookmanager->resArray['stock_stats_hook'])) {
6013 $this->stock_theorique += $hookmanager->resArray['stock_stats_hook'];
6014 }
6015
6016 //Virtual Stock by Warehouse
6017 if (!empty($this->stock_warehouse) && getDolGlobalString('STOCK_ALLOW_VIRTUAL_STOCK_PER_WAREHOUSE')) {
6018 foreach ($this->stock_warehouse as $warehouseid => $stockwarehouse) {
6019 if (isModEnabled('mrp')) {
6020 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock, $warehouseid);
6021 if ($result < 0) {
6022 dol_print_error($this->db, $this->error);
6023 }
6024 }
6025
6026 if ($this->fk_default_warehouse == $warehouseid) {
6027 $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']);
6028 } else {
6029 $this->stock_warehouse[$warehouseid]->virtual = $this->stock_warehouse[$warehouseid]->real + $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'];
6030 }
6031 }
6032 }
6033
6034 return 1;
6035 }
6036
6037
6045 public function loadBatchInfo($batch)
6046 {
6047 $result = array();
6048
6049 $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";
6050 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
6051 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
6052 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
6053 $resql = $this->db->query($sql);
6054 if ($resql) {
6055 $num = $this->db->num_rows($resql);
6056 $i = 0;
6057 while ($i < $num) {
6058 $obj = $this->db->fetch_object($resql);
6059 $result[] = array('batch' => $batch, 'eatby' => $this->db->jdate($obj->eatby), 'sellby' => $this->db->jdate($obj->sellby), 'qty' => $obj->qty);
6060 $i++;
6061 }
6062 return $result;
6063 } else {
6064 dol_print_error($this->db);
6065 $this->db->rollback();
6066 return array();
6067 }
6068 }
6069
6070 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6078 public function add_photo($sdir, $file)
6079 {
6080 // phpcs:enable
6081 global $conf;
6082
6083 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6084
6085 $result = 0;
6086
6087 $dir = $sdir;
6088 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6089 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
6090 } else {
6091 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
6092 }
6093
6094 dol_mkdir($dir);
6095
6096 $dir_osencoded = $dir;
6097
6098 if (is_dir($dir_osencoded)) {
6099 $originImage = $dir.'/'.$file['name'];
6100
6101 // Cree fichier en taille origine
6102 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
6103
6104 if (file_exists(dol_osencode($originImage))) {
6105 // Create thumbs
6106 $this->addThumbs($originImage);
6107 }
6108 }
6109
6110 if (is_numeric($result) && $result > 0) {
6111 return 1;
6112 } else {
6113 return -1;
6114 }
6115 }
6116
6117 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6124 public function is_photo_available($sdir)
6125 {
6126 // phpcs:enable
6127 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6128 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6129
6130 global $conf;
6131
6132 $dir = $sdir;
6133 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6134 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
6135 } else {
6136 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
6137 }
6138
6139 $nbphoto = 0;
6140
6141 $dir_osencoded = dol_osencode($dir);
6142 if (file_exists($dir_osencoded)) {
6143 $handle = opendir($dir_osencoded);
6144 if (is_resource($handle)) {
6145 while (($file = readdir($handle)) !== false) {
6146 if (!utf8_check($file)) {
6147 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
6148 }
6149 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6150 return true;
6151 }
6152 }
6153 }
6154 }
6155 return false;
6156 }
6157
6158 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6166 public function liste_photos($dir, $nbmax = 0)
6167 {
6168 // phpcs:enable
6169 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6170 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6171
6172 $nbphoto = 0;
6173 $tabobj = array();
6174
6175 $dir_osencoded = dol_osencode($dir);
6176 $handle = @opendir($dir_osencoded);
6177 if (is_resource($handle)) {
6178 while (($file = readdir($handle)) !== false) {
6179 if (!utf8_check($file)) {
6180 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // readdir returns ISO
6181 }
6182 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6183 $nbphoto++;
6184
6185 // We forge name of thumb.
6186 $photo = $file;
6187 $photo_vignette = '';
6188 $regs = array();
6189 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
6190 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
6191 }
6192
6193 $dirthumb = $dir.'thumbs/';
6194
6195 // Object
6196 $obj = array();
6197 $obj['photo'] = $photo;
6198 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
6199 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
6200 } else {
6201 $obj['photo_vignette'] = "";
6202 }
6203
6204 $tabobj[$nbphoto - 1] = $obj;
6205
6206 // Do we have to continue with next photo ?
6207 if ($nbmax && $nbphoto >= $nbmax) {
6208 break;
6209 }
6210 }
6211 }
6212
6213 closedir($handle);
6214 }
6215
6216 return $tabobj;
6217 }
6218
6219 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6226 public function delete_photo($file)
6227 {
6228 // phpcs:enable
6229 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6230 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6231
6232 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
6233 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
6234 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
6235
6236 // On efface l'image d'origine
6237 dol_delete_file($file, 0, 0, 0, $this); // For triggers
6238
6239 // Si elle existe, on efface la vignette
6240 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
6241 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
6242 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6243 dol_delete_file($dirthumb.$photo_vignette);
6244 }
6245
6246 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
6247 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6248 dol_delete_file($dirthumb.$photo_vignette);
6249 }
6250 }
6251 }
6252
6253 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6260 public function get_image_size($file)
6261 {
6262 // phpcs:enable
6263 $file_osencoded = dol_osencode($file);
6264 $infoImg = getimagesize($file_osencoded); // Get information on image
6265 $this->imgWidth = $infoImg[0]; // Largeur de l'image
6266 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
6267 }
6268
6274 public function loadStateBoard()
6275 {
6276 global $hookmanager;
6277
6278 $this->nb = array();
6279
6280 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6281 $sql .= " FROM ".$this->db->prefix()."product as p";
6282 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6283 // Add where from hooks
6284 if (is_object($hookmanager)) {
6285 $parameters = array();
6286 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6287 $sql .= $hookmanager->resPrint;
6288 }
6289 $sql .= ' GROUP BY fk_product_type';
6290
6291 $resql = $this->db->query($sql);
6292 if ($resql) {
6293 while ($obj = $this->db->fetch_object($resql)) {
6294 if ($obj->fk_product_type == 1) {
6295 $this->nb["services"] = $obj->nb;
6296 } else {
6297 $this->nb["products"] = $obj->nb;
6298 }
6299 }
6300 $this->db->free($resql);
6301 return 1;
6302 } else {
6303 dol_print_error($this->db);
6304 $this->error = $this->db->error();
6305 return -1;
6306 }
6307 }
6308
6314 public function isProduct()
6315 {
6316 return ($this->type == Product::TYPE_PRODUCT ? true : false);
6317 }
6318
6324 public function isService()
6325 {
6326 return ($this->type == Product::TYPE_SERVICE ? true : false);
6327 }
6328
6334 public function isStockManaged()
6335 {
6336 return ($this->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'));
6337 }
6338
6344 public function isMandatoryPeriod()
6345 {
6346 return ($this->mandatory_period == 1 ? true : false);
6347 }
6348
6354 public function hasbatch()
6355 {
6356 return ($this->status_batch > 0 ? true : false);
6357 }
6358
6359
6360 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6369 public function get_barcode($object, $type = '')
6370 {
6371 // phpcs:enable
6372 global $conf;
6373
6374 $result = '';
6375 if (getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
6376 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6377 foreach ($dirsociete as $dirroot) {
6378 $res = dol_include_once($dirroot . getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM').'.php');
6379 if ($res) {
6380 break;
6381 }
6382 }
6383 $var = getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM');
6384 $mod = new $var();
6385 '@phan-var-force ModeleNumRefBarCode $module';
6386
6387 $result = $mod->getNextValue($object, $type);
6388
6389 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6390 }
6391 return $result;
6392 }
6393
6401 public function initAsSpecimen()
6402 {
6403 $now = dol_now();
6404
6405 // Initialize parameters
6406 $this->specimen = 1;
6407 $this->id = 0;
6408 $this->ref = 'PRODUCT_SPEC';
6409 $this->label = 'PRODUCT SPECIMEN';
6410 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
6411 $this->specimen = 1;
6412 $this->country_id = 1;
6413 $this->status = 1;
6414 $this->status_buy = 1;
6415 $this->tobatch = 0;
6416 $this->sell_or_eat_by_mandatory = 0;
6417 $this->note_private = 'This is a comment (private)';
6418 $this->note_public = 'This is a comment (public)';
6419 $this->date_creation = $now;
6420 $this->date_modification = $now;
6421
6422 $this->weight = 4;
6423 $this->weight_units = 3;
6424
6425 $this->length = 5;
6426 $this->length_units = 1;
6427 $this->width = 6;
6428 $this->width_units = 0;
6429 $this->height = null;
6430 $this->height_units = null;
6431
6432 $this->surface = 30;
6433 $this->surface_units = 0;
6434 $this->volume = 300;
6435 $this->volume_units = 0;
6436
6437 $this->barcode = -1; // Create barcode automatically
6438
6439 return 1;
6440 }
6441
6448 public function getLabelOfUnit($type = 'long')
6449 {
6450 global $langs;
6451
6452 if (!$this->fk_unit) {
6453 return '';
6454 }
6455
6456 $langs->load('products');
6457 $label = '';
6458 $label_type = 'label';
6459 if ($type == 'short') {
6460 $label_type = 'short_label';
6461 }
6462
6463 $sql = "SELECT ".$label_type.", code from ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
6464
6465 $resql = $this->db->query($sql);
6466 if (!$resql) {
6467 $this->error = $this->db->error();
6468 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
6469 return -1;
6470 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
6471 $label = ($label_type == 'short_label' ? $res[$label_type] : 'unit'.$res['code']);
6472 }
6473 $this->db->free($resql);
6474
6475 return $label;
6476 }
6477
6478 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6484 public function min_recommended_price()
6485 {
6486 // phpcs:enable
6487 global $conf;
6488
6489 $maxpricesupplier = 0;
6490
6491 if (getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE')) {
6492 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6493 $product_fourn = new ProductFournisseur($this->db);
6494 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
6495
6496 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
6497 foreach ($product_fourn_list as $productfourn) {
6498 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
6499 $maxpricesupplier = $productfourn->fourn_unitprice;
6500 }
6501 }
6502
6503 $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
6504 }
6505 }
6506
6507 return $maxpricesupplier;
6508 }
6509
6510
6521 public function setCategories($categories)
6522 {
6523 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
6524 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
6525 }
6526
6535 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
6536 {
6537 $tables = array(
6538 'product_customer_price',
6539 'product_customer_price_log'
6540 );
6541
6542 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
6543 }
6544
6556 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
6557 {
6558 global $conf;
6559
6560 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
6561 $query = $this->db->query($sql);
6562
6563 $rules = array();
6564
6565 while ($result = $this->db->fetch_object($query)) {
6566 $rules[$result->level] = $result;
6567 }
6568
6569 //Because prices can be based on other level's prices, we temporarily store them
6570 $prices = array(
6571 1 => $baseprice
6572 );
6573
6574 $nbofproducts = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
6575 for ($i = 1; $i <= $nbofproducts; $i++) {
6576 $price = $baseprice;
6577 $price_min = $baseprice;
6578
6579 //We have to make sure it does exist and it is > 0
6580 //First price level only allows changing min_price
6581 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
6582 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
6583 }
6584
6585 $prices[$i] = $price;
6586
6587 //We have to make sure it does exist and it is > 0
6588 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
6589 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
6590 }
6591
6592 //Little check to make sure the price is modified before triggering generation
6593 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
6594 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
6595
6596 if ($check_amount && $check_type) {
6597 continue;
6598 }
6599
6600 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
6601 return -1;
6602 }
6603 }
6604
6605 return 1;
6606 }
6607
6613 public function getRights()
6614 {
6615 global $user;
6616
6617 if ($this->isProduct()) {
6618 return $user->rights->produit;
6619 } else {
6620 return $user->rights->service;
6621 }
6622 }
6623
6630 public function info($id)
6631 {
6632 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
6633 $sql .= " p.fk_user_author, p.fk_user_modif";
6634 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
6635 $sql .= " WHERE p.rowid = ".((int) $id);
6636
6637 $result = $this->db->query($sql);
6638 if ($result) {
6639 if ($this->db->num_rows($result)) {
6640 $obj = $this->db->fetch_object($result);
6641
6642 $this->id = $obj->rowid;
6643 $this->ref = $obj->ref;
6644
6645 $this->user_creation_id = $obj->fk_user_author;
6646 $this->user_modification_id = $obj->fk_user_modif;
6647
6648 $this->date_creation = $this->db->jdate($obj->date_creation);
6649 $this->date_modification = $this->db->jdate($obj->date_modification);
6650 }
6651
6652 $this->db->free($result);
6653 } else {
6654 dol_print_error($this->db);
6655 }
6656 }
6657
6658
6664 public function getProductDurationHours()
6665 {
6666 global $langs;
6667
6668 if (empty($this->duration_value)) {
6669 $this->errors[] = 'ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
6670 return -1;
6671 }
6672
6673 if ($this->duration_unit == 'i') {
6674 $prodDurationHours = 1. / 60;
6675 }
6676 if ($this->duration_unit == 'h') {
6677 $prodDurationHours = 1.;
6678 }
6679 if ($this->duration_unit == 'd') {
6680 $prodDurationHours = 24.;
6681 }
6682 if ($this->duration_unit == 'w') {
6683 $prodDurationHours = 24. * 7;
6684 }
6685 if ($this->duration_unit == 'm') {
6686 $prodDurationHours = 24. * 30;
6687 }
6688 if ($this->duration_unit == 'y') {
6689 $prodDurationHours = 24. * 365;
6690 }
6691 $prodDurationHours *= $this->duration_value;
6692
6693 return $prodDurationHours;
6694 }
6695
6696
6704 public function getKanbanView($option = '', $arraydata = null)
6705 {
6706 global $langs,$conf;
6707
6708 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6709
6710 $return = '<div class="box-flex-item box-flex-grow-zero">';
6711 $return .= '<div class="info-box info-box-sm">';
6712 $return .= '<div class="info-box-img">';
6713 $label = '';
6714 if ($this->is_photo_available($conf->product->multidir_output[$this->entity])) {
6715 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
6716 $return .= $label;
6717 } else {
6718 if ($this->type == Product::TYPE_PRODUCT) {
6719 $label .= img_picto('', 'product');
6720 } elseif ($this->type == Product::TYPE_SERVICE) {
6721 $label .= img_picto('', 'service');
6722 }
6723 $return .= $label;
6724 }
6725 $return .= '</div>';
6726 $return .= '<div class="info-box-content">';
6727 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
6728 if ($selected >= 0) {
6729 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
6730 }
6731 if (property_exists($this, 'label')) {
6732 $return .= '<br><span class="info-box-label opacitymedium inline-block tdoverflowmax150 valignmiddle" title="'.dol_escape_htmltag($this->label).'">'.dol_escape_htmltag($this->label).'</span>';
6733 }
6734 if (property_exists($this, 'price') && property_exists($this, 'price_ttc')) {
6735 if ($this->price_base_type == 'TTC') {
6736 $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
6737 } else {
6738 if ($this->status) {
6739 $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
6740 }
6741 }
6742 }
6743 $br = 1;
6744 if (property_exists($this, 'stock_reel') && $this->isProduct()) {
6745 $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>';
6746 $br = 0;
6747 }
6748 if (method_exists($this, 'getLibStatut')) {
6749 if ($br) {
6750 $return .= '<br><div class="info-box-status inline-block valignmiddle">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6751 } else {
6752 $return .= '<div class="info-box-status inline-block valignmiddle marginleftonly paddingleft">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6753 }
6754 }
6755 $return .= '</div>';
6756 $return .= '</div>';
6757 $return .= '</div>';
6758 return $return;
6759 }
6760}
6761
6767{
6768 public $picto = 'service';
6769}
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:636
$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.
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.
$price_by_qty
Price by quantity arrays.
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 object need to have its stock managed.
$duration
Service expiration label (value + unit)
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.
$product_id_already_linked
Product ID already linked to a reference supplier.
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 object is a product.
getRights()
Returns the rights used for this class.
loadBatchInfo($batch)
Load existing information about a serial.
$pmp
Average price value for product entry into stock (PMP)
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)
$default_vat_code
Default VAT code for product (link to code into llx_c_tva but without foreign keys)
$duration_unit
Service expiration unit.
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.
$multiprices
Arrays for multiprices.
$localtax1_tx
Other local taxes.
getChildsArbo($id, $firstlevelonly=0, $level=1, $parents=array())
Return children of product $id.
load_virtual_stock($includedraftpoforvirtual=null, $dateofvirtualstock=null)
Load value ->stock_theorique of a product.
load_stats_propale($socid=0)
Charge tableau des stats propale pour le produit/service.
get_barcode($object, $type='')
Get a barcode from the module to generate barcode values.
setAccountancyCode($type, $value)
Sets an accountancy code for a product.
load_stats_facture($socid=0)
Charge tableau des stats facture pour le produit/service.
$remise_percent
Default discount percent.
$imgWidth
Size of image.
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.
$tva_npr
int French VAT NPR is used (0 or 1)
$tva_tx
Default VAT rate of product.
load_stats_bom($socid=0)
Charge tableau des stats OF pour le produit/service.
hasbatch()
Return if object has a sell-by date or eat-by date.
$weight
Metric of products.
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 clicable 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.
$duration_value
Service expiration.
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)
$product_fourn_id
Id du fournisseur.
getSellOrEatByMandatoryLabel()
Get sell or eat by mandatory label.
$desiredstock
Ask for replenishment when $desiredstock < $stock_reel.
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.
$multilangs
Array for multilangs.
load_stats_commande_fournisseur($socid=0, $filtrestatut='', $forVirtualStock=0, $dateofvirtualstock=null)
Charge tableau des stats commande fournisseur pour le produit/service.
isMandatoryPeriod()
Return if object have a constraint on mandatory_period.
isProduct()
Return if 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 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:139