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