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