dolibarr 19.0.4
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-2023 Frédéric France <frederic.france@netlogic.fr>
18 * Copyright (C) 2023 Benjamin Falière <benjamin.faliere@altairis.fr>
19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 3 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program. If not, see <https://www.gnu.org/licenses/>.
32 */
33
39require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
40require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
41require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
42require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
43require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
44
48class Product extends CommonObject
49{
53 public $element = 'product';
54
58 public $table_element = 'product';
59
63 public $fk_element = 'fk_product';
64
68 protected $childtables = array(
69 'supplier_proposaldet' => array('name' => 'SupplierProposal', 'parent' => 'supplier_proposal', 'parentkey' => 'fk_supplier_proposal'),
70 'propaldet' => array('name' => 'Proposal', 'parent' => 'propal', 'parentkey' => 'fk_propal'),
71 'commandedet' => array('name' => 'Order', 'parent' => 'commande', 'parentkey' => 'fk_commande'),
72 'facturedet' => array('name' => 'Invoice', 'parent' => 'facture', 'parentkey' => 'fk_facture'),
73 'contratdet' => array('name' => 'Contract', 'parent' => 'contrat', 'parentkey' => 'fk_contrat'),
74 'facture_fourn_det' => array('name' => 'SupplierInvoice', 'parent' => 'facture_fourn', 'parentkey' => 'fk_facture_fourn'),
75 'commande_fournisseurdet' => array('name' => 'SupplierOrder', 'parent' => 'commande_fournisseur', 'parentkey' => 'fk_commande'),
76 'mrp_production' => array('name' => 'Mo', 'parent' => 'mrp_mo', 'parentkey' => 'fk_mo' ),
77 'bom_bom' => array('name' => 'BOM'),
78 'bom_bomline' => array('name' => 'BOMLine', 'parent' => 'bom_bom', 'parentkey' => 'fk_bom'),
79 );
80
86 public $ismultientitymanaged = 1;
87
91 public $isextrafieldmanaged = 1;
92
96 public $picto = 'product';
97
101 protected $table_ref_field = 'ref';
102
103 public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.xpm|\.xbm'; // See also into images.lib.php
104
109 public $libelle;
110
116 public $label;
117
123 public $description;
124
130 public $other;
131
137 public $type = self::TYPE_PRODUCT;
138
144 public $price;
145
146 public $price_formated; // used by takepos/ajax/ajax.php
147
153 public $price_ttc;
154
155 public $price_ttc_formated; // used by takepos/ajax/ajax.php
156
162 public $price_min;
163
169 public $price_min_ttc;
170
175 public $price_base_type;
176
178 public $multiprices = array();
179 public $multiprices_ttc = array();
180 public $multiprices_base_type = array();
181 public $multiprices_default_vat_code = array();
182 public $multiprices_min = array();
183 public $multiprices_min_ttc = array();
184 public $multiprices_tva_tx = array();
185 public $multiprices_recuperableonly = array();
186
189 public $prices_by_qty = array();
190 public $prices_by_qty_id = array();
191 public $prices_by_qty_list = array();
192
196 public $level;
197
199 public $multilangs = array();
200
203
205 public $tva_tx;
206
210 public $tva_npr = 0;
211
214
217 public $localtax2_tx;
218 public $localtax1_type;
219 public $localtax2_type;
220
221 // Properties set by get_buyprice() for return
222
223 public $desc_supplier;
224 public $vatrate_supplier;
225 public $default_vat_code_supplier;
226 public $fourn_multicurrency_price;
227 public $fourn_multicurrency_unitprice;
228 public $fourn_multicurrency_tx;
229 public $fourn_multicurrency_id;
230 public $fourn_multicurrency_code;
231 public $packaging;
232
233
234 public $lifetime; // In seconds
235
236 public $qc_frequency;
237
243 public $stock_reel = 0;
244
250 public $stock_theorique;
251
257 public $cost_price;
258
260 public $pmp;
261
267 public $seuil_stock_alerte = 0;
268
272 public $desiredstock = 0;
273
285 public $duration;
286
291
297 public $status = 0;
298
305 public $tosell;
306
312 public $status_buy = 0;
313
320 public $tobuy;
321
327 public $finished;
328
334 public $fk_default_bom;
335
341 public $product_fourn_price_id;
342
348 public $buyprice;
349
355 public $tobatch;
356
357
363 public $status_batch = 0;
364
370 public $batch_mask = '';
371
377 public $customcode;
378
384 public $url;
385
387 public $weight;
388 public $weight_units; // scale -3, 0, 3, 6
389 public $length;
390 public $length_units; // scale -3, 0, 3, 6
391 public $width;
392 public $width_units; // scale -3, 0, 3, 6
393 public $height;
394 public $height_units; // scale -3, 0, 3, 6
395 public $surface;
396 public $surface_units; // scale -3, 0, 3, 6
397 public $volume;
398 public $volume_units; // scale -3, 0, 3, 6
399
400 public $net_measure;
401 public $net_measure_units; // scale -3, 0, 3, 6
402
403 public $accountancy_code_sell;
404 public $accountancy_code_sell_intra;
405 public $accountancy_code_sell_export;
406 public $accountancy_code_buy;
407 public $accountancy_code_buy_intra;
408 public $accountancy_code_buy_export;
409
413 public $barcode;
414
418 public $barcode_type;
419
423 public $barcode_type_code;
424
425 public $stats_propale = array();
426 public $stats_commande = array();
427 public $stats_contrat = array();
428 public $stats_facture = array();
429 public $stats_proposal_supplier = array();
430 public $stats_commande_fournisseur = array();
431 public $stats_expedition = array();
432 public $stats_reception = array();
433 public $stats_mo = array();
434 public $stats_bom = array();
435 public $stats_mrptoconsume = array();
436 public $stats_mrptoproduce = array();
437 public $stats_facturerec = array();
438 public $stats_facture_fournisseur = array();
439
441 public $imgWidth;
442 public $imgHeight;
443
447 public $date_creation;
448
452 public $date_modification;
453
456
459
460 public $nbphoto = 0;
461
463 public $stock_warehouse = array();
464
468 public $fk_default_warehouse;
472 public $fk_price_expression;
473
474 /* To store supplier price found */
475 public $fourn_qty;
476 public $fourn_pu;
477 public $fourn_price_base_type;
478 public $fourn_socid;
479
485
489 public $ref_supplier;
490
496 public $fk_unit;
497
503 public $price_autogen = 0;
504
510 public $supplierprices;
511
517 public $sousprods;
518
522 public $res;
523
524
530 public $is_object_used;
531
532 public $is_sousproduit_qty;
533 public $is_sousproduit_incdec;
534
535 public $mandatory_period;
536
537
566 public $fields = array(
567 'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'index'=>1, 'position'=>1, 'comment'=>'Id'),
568 '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'),
569 'entity' =>array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'default'=>1, 'notnull'=>1, 'index'=>1, 'position'=>5),
570 'label' =>array('type'=>'varchar(255)', 'label'=>'Label', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>2, 'position'=>15, 'csslist'=>'tdoverflowmax250'),
571 'barcode' =>array('type'=>'varchar(255)', 'label'=>'Barcode', 'enabled'=>'isModEnabled("barcode")', 'position'=>20, 'visible'=>-1, 'showoncombobox'=>3),
572 'fk_barcode_type' => array('type'=>'integer', 'label'=>'BarcodeType', 'enabled'=>'1', 'position'=>21, 'notnull'=>0, 'visible'=>-1,),
573 'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>61),
574 'note' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>62),
575 'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>500),
576 'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>501),
577 //'date_valid' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>502),
578 'fk_user_author'=>array('type'=>'integer', 'label'=>'UserAuthor', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>510, 'foreignkey'=>'llx_user.rowid'),
579 'fk_user_modif' =>array('type'=>'integer', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>511),
580 //'fk_user_valid' =>array('type'=>'integer', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>512),
581 'localtax1_tx' => array('type'=>'double(6,3)', 'label'=>'Localtax1tx', 'enabled'=>'1', 'position'=>150, 'notnull'=>0, 'visible'=>-1,),
582 'localtax1_type' => array('type'=>'varchar(10)', 'label'=>'Localtax1type', 'enabled'=>'1', 'position'=>155, 'notnull'=>1, 'visible'=>-1,),
583 'localtax2_tx' => array('type'=>'double(6,3)', 'label'=>'Localtax2tx', 'enabled'=>'1', 'position'=>160, 'notnull'=>0, 'visible'=>-1,),
584 'localtax2_type' => array('type'=>'varchar(10)', 'label'=>'Localtax2type', 'enabled'=>'1', 'position'=>165, 'notnull'=>1, 'visible'=>-1,),
585 'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'index'=>0, 'position'=>1000),
586 //'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')),
587 //'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')),
588 'mandatory_period' => array('type'=>'integer', 'label'=>'mandatoryperiod', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>0, 'index'=>1, 'position'=>1000),
589 );
590
594 const TYPE_PRODUCT = 0;
598 const TYPE_SERVICE = 1;
599
604 const SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY = 1;
605 const SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY = 2;
606 const SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT = 3;
607
608
614 public function __construct($db)
615 {
616 $this->db = $db;
617 $this->canvas = '';
618 }
619
625 public function check()
626 {
627 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
628 $this->ref = trim($this->ref);
629 } else {
630 $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
631 }
632
633 $err = 0;
634 if (dol_strlen(trim($this->ref)) == 0) {
635 $err++;
636 }
637
638 if (dol_strlen(trim($this->label)) == 0) {
639 $err++;
640 }
641
642 if ($err > 0) {
643 return 0;
644 } else {
645 return 1;
646 }
647 }
648
656 public function create($user, $notrigger = 0)
657 {
658 global $conf, $langs;
659
660 $error = 0;
661
662 // Clean parameters
663 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
664 $this->ref = trim($this->ref);
665 } else {
666 $this->ref = dol_sanitizeFileName(dol_string_nospecial(trim($this->ref)));
667 }
668 $this->label = trim($this->label);
669 $this->price_ttc = price2num($this->price_ttc);
670 $this->price = price2num($this->price);
671 $this->price_min_ttc = price2num($this->price_min_ttc);
672 $this->price_min = price2num($this->price_min);
673 if (empty($this->tva_tx)) {
674 $this->tva_tx = 0;
675 }
676 if (empty($this->tva_npr)) {
677 $this->tva_npr = 0;
678 }
679 //Local taxes
680 if (empty($this->localtax1_tx)) {
681 $this->localtax1_tx = 0;
682 }
683 if (empty($this->localtax2_tx)) {
684 $this->localtax2_tx = 0;
685 }
686 if (empty($this->localtax1_type)) {
687 $this->localtax1_type = '0';
688 }
689 if (empty($this->localtax2_type)) {
690 $this->localtax2_type = '0';
691 }
692 if (empty($this->price)) {
693 $this->price = 0;
694 }
695 if (empty($this->price_min)) {
696 $this->price_min = 0;
697 }
698 // Price by quantity
699 if (empty($this->price_by_qty)) {
700 $this->price_by_qty = 0;
701 }
702
703 if (empty($this->status)) {
704 $this->status = 0;
705 }
706 if (empty($this->status_buy)) {
707 $this->status_buy = 0;
708 }
709
710 $price_ht = 0;
711 $price_ttc = 0;
712 $price_min_ht = 0;
713 $price_min_ttc = 0;
714
715 //
716 if ($this->price_base_type == 'TTC' && $this->price_ttc > 0) {
717 $price_ttc = price2num($this->price_ttc, 'MU');
718 $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)), 'MU');
719 }
720
721 //
722 if ($this->price_base_type != 'TTC' && $this->price > 0) {
723 $price_ht = price2num($this->price, 'MU');
724 $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)), 'MU');
725 }
726
727 //
728 if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC')) {
729 $price_min_ttc = price2num($this->price_min_ttc, 'MU');
730 $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)), 'MU');
731 }
732
733 //
734 if (($this->price_min > 0) && ($this->price_base_type != 'TTC')) {
735 $price_min_ht = price2num($this->price_min, 'MU');
736 $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)), 'MU');
737 }
738
739 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
740 $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
741 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
742 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
743 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
744 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
745
746 // Barcode value
747 $this->barcode = trim($this->barcode);
748 $this->mandatory_period = empty($this->mandatory_period) ? 0 : $this->mandatory_period;
749 // Check parameters
750 if (empty($this->label)) {
751 $this->error = 'ErrorMandatoryParametersNotProvided';
752 return -1;
753 }
754
755 if (empty($this->ref) || $this->ref == 'auto') {
756 // Load object modCodeProduct
757 $module = getDolGlobalString('PRODUCT_CODEPRODUCT_ADDON', 'mod_codeproduct_leopard');
758 if ($module != 'mod_codeproduct_leopard') { // Do not load module file for leopard
759 if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
760 $module = substr($module, 0, dol_strlen($module) - 4);
761 }
762 dol_include_once('/core/modules/product/'.$module.'.php');
763 $modCodeProduct = new $module();
764 if (!empty($modCodeProduct->code_auto)) {
765 $this->ref = $modCodeProduct->getNextValue($this, $this->type);
766 }
767 unset($modCodeProduct);
768 }
769
770 if (empty($this->ref)) {
771 $this->error = 'ProductModuleNotSetupForAutoRef';
772 return -2;
773 }
774 }
775
776 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);
777
778 $now = dol_now();
779
780 $this->db->begin();
781
782 // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
783 if ($this->barcode == -1) {
784 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
785 }
786
787 // Check more parameters
788 // If error, this->errors[] is filled
789 $result = $this->verify();
790
791 if ($result >= 0) {
792 $sql = "SELECT count(*) as nb";
793 $sql .= " FROM ".$this->db->prefix()."product";
794 $sql .= " WHERE entity IN (".getEntity('product').")";
795 $sql .= " AND ref = '".$this->db->escape($this->ref)."'";
796
797 $result = $this->db->query($sql);
798 if ($result) {
799 $obj = $this->db->fetch_object($result);
800 if ($obj->nb == 0) {
801 // Produit non deja existant
802 $sql = "INSERT INTO ".$this->db->prefix()."product (";
803 $sql .= "datec";
804 $sql .= ", entity";
805 $sql .= ", ref";
806 $sql .= ", ref_ext";
807 $sql .= ", price_min";
808 $sql .= ", price_min_ttc";
809 $sql .= ", label";
810 $sql .= ", fk_user_author";
811 $sql .= ", fk_product_type";
812 $sql .= ", price";
813 $sql .= ", price_ttc";
814 $sql .= ", price_base_type";
815 $sql .= ", tobuy";
816 $sql .= ", tosell";
817 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
818 $sql .= ", accountancy_code_buy";
819 $sql .= ", accountancy_code_buy_intra";
820 $sql .= ", accountancy_code_buy_export";
821 $sql .= ", accountancy_code_sell";
822 $sql .= ", accountancy_code_sell_intra";
823 $sql .= ", accountancy_code_sell_export";
824 }
825 $sql .= ", canvas";
826 $sql .= ", finished";
827 $sql .= ", tobatch";
828 $sql .= ", batch_mask";
829 $sql .= ", fk_unit";
830 $sql .= ", mandatory_period";
831 $sql .= ") VALUES (";
832 $sql .= "'".$this->db->idate($now)."'";
833 $sql .= ", ".(!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
834 $sql .= ", '".$this->db->escape($this->ref)."'";
835 $sql .= ", ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
836 $sql .= ", ".price2num($price_min_ht);
837 $sql .= ", ".price2num($price_min_ttc);
838 $sql .= ", ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
839 $sql .= ", ".((int) $user->id);
840 $sql .= ", ".((int) $this->type);
841 $sql .= ", ".price2num($price_ht, 'MT');
842 $sql .= ", ".price2num($price_ttc, 'MT');
843 $sql .= ", '".$this->db->escape($this->price_base_type)."'";
844 $sql .= ", ".((int) $this->status);
845 $sql .= ", ".((int) $this->status_buy);
846 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
847 $sql .= ", '".$this->db->escape($this->accountancy_code_buy)."'";
848 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_intra)."'";
849 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_export)."'";
850 $sql .= ", '".$this->db->escape($this->accountancy_code_sell)."'";
851 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
852 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
853 }
854 $sql .= ", '".$this->db->escape($this->canvas)."'";
855 $sql .= ", ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'NULL' : (int) $this->finished);
856 $sql .= ", ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : ((int) $this->status_batch));
857 $sql .= ", '".$this->db->escape($this->batch_mask)."'";
858 $sql .= ", ".($this->fk_unit > 0 ? ((int) $this->fk_unit) : 'NULL');
859 $sql .= ", '".$this->db->escape($this->mandatory_period)."'";
860 $sql .= ")";
861
862 dol_syslog(get_class($this)."::Create", LOG_DEBUG);
863 $result = $this->db->query($sql);
864 if ($result) {
865 $id = $this->db->last_insert_id($this->db->prefix()."product");
866
867 if ($id > 0) {
868 $this->id = $id;
869 $this->price = $price_ht;
870 $this->price_ttc = $price_ttc;
871 $this->price_min = $price_min_ht;
872 $this->price_min_ttc = $price_min_ttc;
873
874 $result = $this->_log_price($user);
875 if ($result > 0) {
876 if ($this->update($id, $user, true, 'add') <= 0) {
877 $error++;
878 }
879 } else {
880 $error++;
881 $this->error = $this->db->lasterror();
882 }
883
884 // update accountancy for this entity
885 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
886 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " .((int) $this->id) . " AND entity = " . ((int) $conf->entity));
887
888 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
889 $sql .= " fk_product";
890 $sql .= ", entity";
891 $sql .= ", accountancy_code_buy";
892 $sql .= ", accountancy_code_buy_intra";
893 $sql .= ", accountancy_code_buy_export";
894 $sql .= ", accountancy_code_sell";
895 $sql .= ", accountancy_code_sell_intra";
896 $sql .= ", accountancy_code_sell_export";
897 $sql .= ") VALUES (";
898 $sql .= $this->id;
899 $sql .= ", " . $conf->entity;
900 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
901 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
902 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
903 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
904 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
905 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
906 $sql .= ")";
907 $result = $this->db->query($sql);
908 if (!$result) {
909 $error++;
910 $this->error = 'ErrorFailedToInsertAccountancyForEntity';
911 }
912 }
913 } else {
914 $error++;
915 $this->error = 'ErrorFailedToGetInsertedId';
916 }
917 } else {
918 $error++;
919 $this->error = $this->db->lasterror();
920 }
921 } else {
922 // Product already exists with this ref
923 $langs->load("products");
924 $error++;
925 $this->error = "ErrorProductAlreadyExists";
926 dol_syslog(get_class($this)."::Create fails, ref ".$this->ref." already exists");
927 }
928 } else {
929 $error++;
930 $this->error = $this->db->lasterror();
931 }
932
933 if (!$error && !$notrigger) {
934 // Call trigger
935 $result = $this->call_trigger('PRODUCT_CREATE', $user);
936 if ($result < 0) {
937 $error++;
938 }
939 // End call triggers
940 }
941
942 if (!$error) {
943 $this->db->commit();
944 return $this->id;
945 } else {
946 $this->db->rollback();
947 return -$error;
948 }
949 } else {
950 $this->db->rollback();
951 dol_syslog(get_class($this)."::Create fails verify ".join(',', $this->errors), LOG_WARNING);
952 return -3;
953 }
954 }
955
956
963 public function verify()
964 {
965 global $langs;
966
967 $this->errors = array();
968
969 $result = 0;
970 $this->ref = trim($this->ref);
971
972 if (!$this->ref) {
973 $this->errors[] = 'ErrorBadRef';
974 $result = -2;
975 }
976
977 $arrayofnonnegativevalue = array('weight'=>'Weight', 'width'=>'Width', 'height'=>'Height', 'length'=>'Length', 'surface'=>'Surface', 'volume'=>'Volume');
978 foreach ($arrayofnonnegativevalue as $key => $value) {
979 if (property_exists($this, $key) && !empty($this->$key) && ($this->$key < 0)) {
980 $langs->loadLangs(array("main", "other"));
981 $this->error = $langs->trans("FieldCannotBeNegative", $langs->transnoentitiesnoconv($value));
982 $this->errors[] = $this->error;
983 $result = -4;
984 }
985 }
986
987 $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
988 if ($rescode) {
989 if ($rescode == -1) {
990 $this->errors[] = 'ErrorBadBarCodeSyntax';
991 } elseif ($rescode == -2) {
992 $this->errors[] = 'ErrorBarCodeRequired';
993 } elseif ($rescode == -3) {
994 // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
995 $this->errors[] = 'ErrorBarCodeAlreadyUsed';
996 }
997
998 $result = -3;
999 }
1000
1001 return $result;
1002 }
1003
1004 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1015 public function check_barcode($valuetotest, $typefortest)
1016 {
1017 // phpcs:enable
1018 global $conf;
1019 if (isModEnabled('barcode') && getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
1020 $module = strtolower($conf->global->BARCODE_PRODUCT_ADDON_NUM);
1021
1022 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
1023 foreach ($dirsociete as $dirroot) {
1024 $res = dol_include_once($dirroot.$module.'.php');
1025 if ($res) {
1026 break;
1027 }
1028 }
1029
1030 $mod = new $module();
1031
1032 dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
1033 $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
1034 return $result;
1035 } else {
1036 return 0;
1037 }
1038 }
1039
1051 public function update($id, $user, $notrigger = false, $action = 'update', $updatetype = false)
1052 {
1053 global $langs, $conf, $hookmanager;
1054
1055 $error = 0;
1056
1057 // Check parameters
1058 if (!$this->label) {
1059 $this->label = 'MISSING LABEL';
1060 }
1061
1062 // Clean parameters
1063 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
1064 $this->ref = trim($this->ref);
1065 } else {
1066 $this->ref = dol_string_nospecial(trim($this->ref));
1067 }
1068 $this->label = trim($this->label);
1069 $this->description = trim($this->description);
1070 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : null);
1071 $this->note_public = (isset($this->note_public) ? trim($this->note_public) : null);
1072 $this->net_measure = price2num($this->net_measure);
1073 $this->net_measure_units = trim($this->net_measure_units);
1074 $this->weight = price2num($this->weight);
1075 $this->weight_units = trim($this->weight_units);
1076 $this->length = price2num($this->length);
1077 $this->length_units = trim($this->length_units);
1078 $this->width = price2num($this->width);
1079 $this->width_units = trim($this->width_units);
1080 $this->height = price2num($this->height);
1081 $this->height_units = trim($this->height_units);
1082 $this->surface = price2num($this->surface);
1083 $this->surface_units = trim($this->surface_units);
1084 $this->volume = price2num($this->volume);
1085 $this->volume_units = trim($this->volume_units);
1086
1087 // set unit not defined
1088 if (is_numeric($this->length_units)) {
1089 $this->width_units = $this->length_units; // Not used yet
1090 }
1091 if (is_numeric($this->length_units)) {
1092 $this->height_units = $this->length_units; // Not used yet
1093 }
1094
1095 // Automated compute surface and volume if not filled
1096 if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
1097 $this->surface = $this->length * $this->width;
1098 $this->surface_units = measuring_units_squared($this->length_units);
1099 }
1100 if (empty($this->volume) && !empty($this->surface) && !empty($this->height) && $this->length_units == $this->height_units) {
1101 $this->volume = $this->surface * $this->height;
1102 $this->volume_units = measuring_units_cubed($this->height_units);
1103 }
1104
1105 if (empty($this->tva_tx)) {
1106 $this->tva_tx = 0;
1107 }
1108 if (empty($this->tva_npr)) {
1109 $this->tva_npr = 0;
1110 }
1111 if (empty($this->localtax1_tx)) {
1112 $this->localtax1_tx = 0;
1113 }
1114 if (empty($this->localtax2_tx)) {
1115 $this->localtax2_tx = 0;
1116 }
1117 if (empty($this->localtax1_type)) {
1118 $this->localtax1_type = '0';
1119 }
1120 if (empty($this->localtax2_type)) {
1121 $this->localtax2_type = '0';
1122 }
1123 if (empty($this->status)) {
1124 $this->status = 0;
1125 }
1126 if (empty($this->status_buy)) {
1127 $this->status_buy = 0;
1128 }
1129
1130 if (empty($this->country_id)) {
1131 $this->country_id = 0;
1132 }
1133
1134 if (empty($this->state_id)) {
1135 $this->state_id = 0;
1136 }
1137
1138 // Barcode value
1139 $this->barcode = trim($this->barcode);
1140
1141 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1142 $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
1143 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1144 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1145 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1146 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1147
1148
1149 $this->db->begin();
1150
1151 $result = 0;
1152 // Check name is required and codes are ok or unique. If error, this->errors[] is filled
1153 if ($action != 'add') {
1154 $result = $this->verify(); // We don't check when update called during a create because verify was already done
1155 } else {
1156 // we can continue
1157 $result = 0;
1158 }
1159
1160 if ($result >= 0) {
1161 // $this->oldcopy should have been set by the caller of update (here properties were already modified)
1162 if (empty($this->oldcopy)) {
1163 $this->oldcopy = dol_clone($this, 1);
1164 }
1165 // Test if batch management is activated on existing product
1166 // If yes, we create missing entries into product_batch
1167 if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
1168 //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
1169 $valueforundefinedlot = '000000';
1170 if (getDolGlobalString('STOCK_DEFAULT_BATCH')) {
1171 $valueforundefinedlot = $conf->global->STOCK_DEFAULT_BATCH;
1172 }
1173
1174 dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
1175
1176 $this->load_stock();
1177 foreach ($this->stock_warehouse as $idW => $ObjW) { // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
1178 $qty_batch = 0;
1179 foreach ($ObjW->detail_batch as $detail) { // Each lines of detail in product_batch of the current $ObjW = product_stock
1180 if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
1181 // We discard this line, we will create it later
1182 $sqlclean = "DELETE FROM ".$this->db->prefix()."product_batch WHERE batch in('Undefined', '".$this->db->escape($valueforundefinedlot)."') AND fk_product_stock = ".((int) $ObjW->id);
1183 $result = $this->db->query($sqlclean);
1184 if (!$result) {
1185 dol_print_error($this->db);
1186 exit;
1187 }
1188 continue;
1189 }
1190
1191 $qty_batch += $detail->qty;
1192 }
1193 // Quantities in batch details are not same as stock quantity,
1194 // so we add a default batch record to complete and get same qty in parent and child table
1195 if ($ObjW->real != $qty_batch) {
1196 $ObjBatch = new Productbatch($this->db);
1197 $ObjBatch->batch = $valueforundefinedlot;
1198 $ObjBatch->qty = ($ObjW->real - $qty_batch);
1199 $ObjBatch->fk_product_stock = $ObjW->id;
1200
1201 if ($ObjBatch->create($user, 1) < 0) {
1202 $error++;
1203 $this->errors = $ObjBatch->errors;
1204 } else {
1205 // we also add lot record if not exist
1206 $ObjLot = new Productlot($this->db);
1207 if ($ObjLot->fetch(0, $this->id, $valueforundefinedlot) == 0) {
1208 $ObjLot->fk_product = $this->id;
1209 $ObjLot->entity = $this->entity;
1210 $ObjLot->fk_user_creat = $user->id;
1211 $ObjLot->batch = $valueforundefinedlot;
1212 if ($ObjLot->create($user, true) < 0) {
1213 $error++;
1214 $this->errors = $ObjLot->errors;
1215 }
1216 }
1217 }
1218 }
1219 }
1220 }
1221
1222 // For automatic creation
1223 if ($this->barcode == -1) {
1224 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1225 }
1226
1227 $sql = "UPDATE ".$this->db->prefix()."product";
1228 $sql .= " SET label = '".$this->db->escape($this->label)."'";
1229
1230 if ($updatetype && ($this->isProduct() || $this->isService())) {
1231 $sql .= ", fk_product_type = ".((int) $this->type);
1232 }
1233
1234 $sql .= ", ref = '".$this->db->escape($this->ref)."'";
1235 $sql .= ", ref_ext = ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1236 $sql .= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
1237 $sql .= ", tva_tx = ".((float) $this->tva_tx);
1238 $sql .= ", recuperableonly = ".((int) $this->tva_npr);
1239 $sql .= ", localtax1_tx = ".((float) $this->localtax1_tx);
1240 $sql .= ", localtax2_tx = ".((float) $this->localtax2_tx);
1241 $sql .= ", localtax1_type = ".($this->localtax1_type != '' ? "'".$this->db->escape($this->localtax1_type)."'" : "'0'");
1242 $sql .= ", localtax2_type = ".($this->localtax2_type != '' ? "'".$this->db->escape($this->localtax2_type)."'" : "'0'");
1243
1244 $sql .= ", barcode = ".(empty($this->barcode) ? "null" : "'".$this->db->escape($this->barcode)."'");
1245 $sql .= ", fk_barcode_type = ".(empty($this->barcode_type) ? "null" : $this->db->escape($this->barcode_type));
1246
1247 $sql .= ", tosell = ".(int) $this->status;
1248 $sql .= ", tobuy = ".(int) $this->status_buy;
1249 $sql .= ", tobatch = ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
1250 $sql .= ", batch_mask = '".$this->db->escape($this->batch_mask)."'";
1251
1252 $sql .= ", finished = ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? "null" : (int) $this->finished);
1253 $sql .= ", fk_default_bom = ".((!isset($this->fk_default_bom) || $this->fk_default_bom < 0 || $this->fk_default_bom == '') ? "null" : (int) $this->fk_default_bom);
1254 $sql .= ", net_measure = ".($this->net_measure != '' ? "'".$this->db->escape($this->net_measure)."'" : 'null');
1255 $sql .= ", net_measure_units = ".($this->net_measure_units != '' ? "'".$this->db->escape($this->net_measure_units)."'" : 'null');
1256 $sql .= ", weight = ".($this->weight != '' ? "'".$this->db->escape($this->weight)."'" : 'null');
1257 $sql .= ", weight_units = ".($this->weight_units != '' ? "'".$this->db->escape($this->weight_units)."'" : 'null');
1258 $sql .= ", length = ".($this->length != '' ? "'".$this->db->escape($this->length)."'" : 'null');
1259 $sql .= ", length_units = ".($this->length_units != '' ? "'".$this->db->escape($this->length_units)."'" : 'null');
1260 $sql .= ", width= ".($this->width != '' ? "'".$this->db->escape($this->width)."'" : 'null');
1261 $sql .= ", width_units = ".($this->width_units != '' ? "'".$this->db->escape($this->width_units)."'" : 'null');
1262 $sql .= ", height = ".($this->height != '' ? "'".$this->db->escape($this->height)."'" : 'null');
1263 $sql .= ", height_units = ".($this->height_units != '' ? "'".$this->db->escape($this->height_units)."'" : 'null');
1264 $sql .= ", surface = ".($this->surface != '' ? "'".$this->db->escape($this->surface)."'" : 'null');
1265 $sql .= ", surface_units = ".($this->surface_units != '' ? "'".$this->db->escape($this->surface_units)."'" : 'null');
1266 $sql .= ", volume = ".($this->volume != '' ? "'".$this->db->escape($this->volume)."'" : 'null');
1267 $sql .= ", volume_units = ".($this->volume_units != '' ? "'".$this->db->escape($this->volume_units)."'" : 'null');
1268 $sql .= ", fk_default_warehouse = ".($this->fk_default_warehouse > 0 ? ((int) $this->fk_default_warehouse) : 'null');
1269 $sql .= ", fk_default_workstation = ".($this->fk_default_workstation > 0 ? ((int) $this->fk_default_workstation) : 'null');
1270 $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (float) $this->seuil_stock_alerte : 'null');
1271 $sql .= ", description = '".$this->db->escape($this->description)."'";
1272 $sql .= ", url = ".($this->url ? "'".$this->db->escape($this->url)."'" : 'null');
1273 $sql .= ", customcode = '".$this->db->escape($this->customcode)."'";
1274 $sql .= ", fk_country = ".($this->country_id > 0 ? (int) $this->country_id : 'null');
1275 $sql .= ", fk_state = ".($this->state_id > 0 ? (int) $this->state_id : 'null');
1276 $sql .= ", lifetime = ".($this->lifetime > 0 ? (int) $this->lifetime : 'null');
1277 $sql .= ", qc_frequency = ".($this->qc_frequency > 0 ? (int) $this->qc_frequency : 'null');
1278 $sql .= ", note = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : 'null');
1279 $sql .= ", note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : 'null');
1280 $sql .= ", duration = '".$this->db->escape($this->duration_value.$this->duration_unit)."'";
1281 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1282 $sql .= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy) . "'";
1283 $sql .= ", accountancy_code_buy_intra = '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1284 $sql .= ", accountancy_code_buy_export = '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1285 $sql .= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell) . "'";
1286 $sql .= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1287 $sql .= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1288 }
1289 $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (float) $this->desiredstock : "null");
1290 $sql .= ", cost_price = ".($this->cost_price != '' ? $this->db->escape($this->cost_price) : 'null');
1291 $sql .= ", fk_unit= ".(!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
1292 $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1);
1293 $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
1294 $sql .= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL');
1295 $sql .= ", mandatory_period = ".($this->mandatory_period);
1296 // stock field is not here because it is a denormalized value from product_stock.
1297 $sql .= " WHERE rowid = ".((int) $id);
1298
1299 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1300
1301 $resql = $this->db->query($sql);
1302 if ($resql) {
1303 $this->id = $id;
1304
1305 // Multilangs
1306 if (getDolGlobalInt('MAIN_MULTILANGS')) {
1307 if ($this->setMultiLangs($user) < 0) {
1308 return -2;
1309 }
1310 }
1311
1312 $action = 'update';
1313
1314 // update accountancy for this entity
1315 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1316 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " . ((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1317
1318 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1319 $sql .= " fk_product";
1320 $sql .= ", entity";
1321 $sql .= ", accountancy_code_buy";
1322 $sql .= ", accountancy_code_buy_intra";
1323 $sql .= ", accountancy_code_buy_export";
1324 $sql .= ", accountancy_code_sell";
1325 $sql .= ", accountancy_code_sell_intra";
1326 $sql .= ", accountancy_code_sell_export";
1327 $sql .= ") VALUES (";
1328 $sql .= $this->id;
1329 $sql .= ", " . $conf->entity;
1330 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1331 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1332 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1333 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1334 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1335 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1336 $sql .= ")";
1337 $result = $this->db->query($sql);
1338 if (!$result) {
1339 $error++;
1340 $this->error = 'ErrorFailedToUpdateAccountancyForEntity';
1341 }
1342 }
1343
1344 if (!$this->hasbatch() && $this->oldcopy->hasbatch()) {
1345 // Selection of all product stock mouvements that contains batchs
1346 $sql = 'SELECT pb.qty, ps.fk_entrepot, pb.batch FROM '.MAIN_DB_PREFIX.'product_batch as pb';
1347 $sql.= ' INNER JOIN '.MAIN_DB_PREFIX.'product_stock as ps ON (ps.rowid = pb.fk_product_stock)';
1348 $sql.= ' WHERE ps.fk_product = '.(int) $this->id;
1349
1350 $resql = $this->db->query($sql);
1351 if ($resql) {
1352 $inventorycode = dol_print_date(dol_now(), '%Y%m%d%H%M%S');
1353
1354 while ($obj = $this->db->fetch_object($resql)) {
1355 $value = $obj->qty;
1356 $fk_entrepot = $obj->fk_entrepot;
1357 $price = 0;
1358 $dlc = '';
1359 $dluo = '';
1360 $batch = $obj->batch;
1361
1362 // To know how to revert stockMouvement (add or remove)
1363 $addOremove = $value > 0 ? 1 : 0; // 1 if remove, 0 if add
1364 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1365 $res = $this->correct_stock_batch($user, $fk_entrepot, abs($value), $addOremove, $label, $price, $dlc, $dluo, $batch, $inventorycode, '', null, 0, null, true);
1366
1367 if ($res > 0) {
1368 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1369 $res = $this->correct_stock($user, $fk_entrepot, abs($value), (int) empty($addOremove), $label, $price, $inventorycode, '', null, 0);
1370 if ($res < 0) {
1371 $error++;
1372 }
1373 } else {
1374 $error++;
1375 }
1376 }
1377 }
1378 }
1379
1380 // Actions on extra fields
1381 if (!$error) {
1382 $result = $this->insertExtraFields();
1383 if ($result < 0) {
1384 $error++;
1385 }
1386 }
1387
1388 if (!$error && !$notrigger) {
1389 // Call trigger
1390 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1391 if ($result < 0) {
1392 $error++;
1393 }
1394 // End call triggers
1395 }
1396
1397 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
1398 // We remove directory
1399 if ($conf->product->dir_output) {
1400 $olddir = $conf->product->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
1401 $newdir = $conf->product->dir_output."/".dol_sanitizeFileName($this->ref);
1402 if (file_exists($olddir)) {
1403 //include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1404 //$res = dol_move($olddir, $newdir);
1405 // do not use dol_move with directory
1406 $res = @rename($olddir, $newdir);
1407 if (!$res) {
1408 $langs->load("errors");
1409 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
1410 $error++;
1411 }
1412 }
1413 }
1414 }
1415
1416 if (!$error) {
1417 if (isModEnabled('variants')) {
1418 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1419
1420 $comb = new ProductCombination($this->db);
1421
1422 foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1423 $currcomb->updateProperties($this, $user);
1424 }
1425 }
1426
1427 $this->db->commit();
1428 return 1;
1429 } else {
1430 $this->db->rollback();
1431 return -$error;
1432 }
1433 } else {
1434 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1435 $langs->load("errors");
1436 if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1437 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1438 } else {
1439 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1440 }
1441 $this->errors[] = $this->error;
1442 $this->db->rollback();
1443 return -1;
1444 } else {
1445 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1446 $this->errors[] = $this->error;
1447 $this->db->rollback();
1448 return -2;
1449 }
1450 }
1451 } else {
1452 $this->db->rollback();
1453 dol_syslog(get_class($this)."::Update fails verify ".join(',', $this->errors), LOG_WARNING);
1454 return -3;
1455 }
1456 }
1457
1465 public function delete(User $user, $notrigger = 0)
1466 {
1467 global $conf, $langs;
1468 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1469
1470 $error = 0;
1471
1472 // Check parameters
1473 if (empty($this->id)) {
1474 $this->error = "Object must be fetched before calling delete";
1475 return -1;
1476 }
1477 if (($this->type == Product::TYPE_PRODUCT && !$user->hasRight('produit', 'supprimer')) || ($this->type == Product::TYPE_SERVICE && !$user->hasRight('service', 'supprimer'))) {
1478 $this->error = "ErrorForbidden";
1479 return 0;
1480 }
1481
1482 $objectisused = $this->isObjectUsed($this->id);
1483 if (empty($objectisused)) {
1484 $this->db->begin();
1485
1486 if (!$error && empty($notrigger)) {
1487 // Call trigger
1488 $result = $this->call_trigger('PRODUCT_DELETE', $user);
1489 if ($result < 0) {
1490 $error++;
1491 }
1492 // End call triggers
1493 }
1494
1495 // Delete from product_batch on product delete
1496 if (!$error) {
1497 $sql = "DELETE FROM ".$this->db->prefix().'product_batch';
1498 $sql .= " WHERE fk_product_stock IN (";
1499 $sql .= "SELECT rowid FROM ".$this->db->prefix().'product_stock';
1500 $sql .= " WHERE fk_product = ".((int) $this->id).")";
1501
1502 $result = $this->db->query($sql);
1503 if (!$result) {
1504 $error++;
1505 $this->errors[] = $this->db->lasterror();
1506 }
1507 }
1508
1509 // Delete all child tables
1510 if (!$error) {
1511 $elements = array('product_fournisseur_price', 'product_price', 'product_lang', 'categorie_product', 'product_stock', 'product_customer_price', 'product_lot'); // product_batch is done before
1512 foreach ($elements as $table) {
1513 if (!$error) {
1514 $sql = "DELETE FROM ".$this->db->prefix().$table;
1515 $sql .= " WHERE fk_product = ".(int) $this->id;
1516
1517 $result = $this->db->query($sql);
1518 if (!$result) {
1519 $error++;
1520 $this->errors[] = $this->db->lasterror();
1521 }
1522 }
1523 }
1524 }
1525
1526 if (!$error) {
1527 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1528 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1529
1530 //If it is a parent product, then we remove the association with child products
1531 $prodcomb = new ProductCombination($this->db);
1532
1533 if ($prodcomb->deleteByFkProductParent($user, $this->id) < 0) {
1534 $error++;
1535 $this->errors[] = 'Error deleting combinations';
1536 }
1537
1538 //We also check if it is a child product
1539 if (!$error && ($prodcomb->fetchByFkProductChild($this->id) > 0) && ($prodcomb->delete($user) < 0)) {
1540 $error++;
1541 $this->errors[] = 'Error deleting child combination';
1542 }
1543 }
1544
1545 // Delete from product_association
1546 if (!$error) {
1547 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
1548 $sql .= " WHERE fk_product_pere = ".(int) $this->id." OR fk_product_fils = ".(int) $this->id;
1549
1550 $result = $this->db->query($sql);
1551 if (!$result) {
1552 $error++;
1553 $this->errors[] = $this->db->lasterror();
1554 }
1555 }
1556
1557 // Remove extrafields
1558 if (!$error) {
1559 $result = $this->deleteExtraFields();
1560 if ($result < 0) {
1561 $error++;
1562 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1563 }
1564 }
1565
1566 // Delete product
1567 if (!$error) {
1568 $sqlz = "DELETE FROM ".$this->db->prefix()."product";
1569 $sqlz .= " WHERE rowid = ".(int) $this->id;
1570
1571 $resultz = $this->db->query($sqlz);
1572 if (!$resultz) {
1573 $error++;
1574 $this->errors[] = $this->db->lasterror();
1575 }
1576 }
1577
1578 // Delete record into ECM index and physically
1579 if (!$error) {
1580 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1581 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1582 if (!$res) {
1583 $error++;
1584 }
1585 }
1586
1587 if (!$error) {
1588 // We remove directory
1589 $ref = dol_sanitizeFileName($this->ref);
1590 if ($conf->product->dir_output) {
1591 $dir = $conf->product->dir_output."/".$ref;
1592 if (file_exists($dir)) {
1594 if (!$res) {
1595 $this->errors[] = 'ErrorFailToDeleteDir';
1596 $error++;
1597 }
1598 }
1599 }
1600 }
1601
1602 if (!$error) {
1603 $this->db->commit();
1604 return 1;
1605 } else {
1606 foreach ($this->errors as $errmsg) {
1607 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1608 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1609 }
1610 $this->db->rollback();
1611 return -$error;
1612 }
1613 } else {
1614 $this->error = "ErrorRecordIsUsedCantDelete";
1615 return 0;
1616 }
1617 }
1618
1625 public function setMultiLangs($user)
1626 {
1627 global $conf, $langs;
1628
1629 $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1630 $current_lang = $langs->getDefaultLang();
1631
1632 foreach ($langs_available as $key => $value) {
1633 if ($key == $current_lang) {
1634 $sql = "SELECT rowid";
1635 $sql .= " FROM ".$this->db->prefix()."product_lang";
1636 $sql .= " WHERE fk_product = ".((int) $this->id);
1637 $sql .= " AND lang = '".$this->db->escape($key)."'";
1638
1639 $result = $this->db->query($sql);
1640
1641 if ($this->db->num_rows($result)) { // if there is already a description line for this language
1642 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1643 $sql2 .= " SET ";
1644 $sql2 .= " label='".$this->db->escape($this->label)."',";
1645 $sql2 .= " description='".$this->db->escape($this->description)."'";
1646 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1647 $sql2 .= ", note='".$this->db->escape($this->other)."'";
1648 }
1649 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1650 } else {
1651 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1652 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1653 $sql2 .= ", note";
1654 }
1655 $sql2 .= ")";
1656 $sql2 .= " VALUES(".$this->id.",'".$this->db->escape($key)."','".$this->db->escape($this->label)."',";
1657 $sql2 .= " '".$this->db->escape($this->description)."'";
1658 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1659 $sql2 .= ", '".$this->db->escape($this->other)."'";
1660 }
1661 $sql2 .= ")";
1662 }
1663 dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
1664 if (!$this->db->query($sql2)) {
1665 $this->error = $this->db->lasterror();
1666 return -1;
1667 }
1668 } elseif (isset($this->multilangs[$key])) {
1669 if (empty($this->multilangs["$key"]["label"])) {
1670 $this->errors[] = $key . ' : ' . $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Label"));
1671 return -1;
1672 }
1673
1674 $sql = "SELECT rowid";
1675 $sql .= " FROM ".$this->db->prefix()."product_lang";
1676 $sql .= " WHERE fk_product = ".((int) $this->id);
1677 $sql .= " AND lang = '".$this->db->escape($key)."'";
1678
1679 $result = $this->db->query($sql);
1680
1681 if ($this->db->num_rows($result)) { // if there is already a description line for this language
1682 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1683 $sql2 .= " SET ";
1684 $sql2 .= " label = '".$this->db->escape($this->multilangs["$key"]["label"])."',";
1685 $sql2 .= " description = '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1686 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1687 $sql2 .= ", note = '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1688 }
1689 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1690 } else {
1691 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1692 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1693 $sql2 .= ", note";
1694 }
1695 $sql2 .= ")";
1696 $sql2 .= " VALUES(".$this->id.",'".$this->db->escape($key)."','".$this->db->escape($this->multilangs["$key"]["label"])."',";
1697 $sql2 .= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1698 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1699 $sql2 .= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1700 }
1701 $sql2 .= ")";
1702 }
1703
1704 // We do not save if main fields are empty
1705 if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
1706 if (!$this->db->query($sql2)) {
1707 $this->error = $this->db->lasterror();
1708 return -1;
1709 }
1710 }
1711 } else {
1712 // language is not current language and we didn't provide a multilang description for this language
1713 }
1714 }
1715
1716 // Call trigger
1717 $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
1718 if ($result < 0) {
1719 $this->error = $this->db->lasterror();
1720 return -1;
1721 }
1722 // End call triggers
1723
1724 return 1;
1725 }
1726
1735 public function delMultiLangs($langtodelete, $user)
1736 {
1737 $sql = "DELETE FROM ".$this->db->prefix()."product_lang";
1738 $sql .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
1739
1740 dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
1741 $result = $this->db->query($sql);
1742 if ($result) {
1743 // Call trigger
1744 $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
1745 if ($result < 0) {
1746 $this->error = $this->db->lasterror();
1747 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1748 return -1;
1749 }
1750 // End call triggers
1751 return 1;
1752 } else {
1753 $this->error = $this->db->lasterror();
1754 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1755 return -1;
1756 }
1757 }
1758
1767 public function setAccountancyCode($type, $value)
1768 {
1769 global $user, $langs, $conf;
1770
1771 $error = 0;
1772
1773 $this->db->begin();
1774
1775 if ($type == 'buy') {
1776 $field = 'accountancy_code_buy';
1777 } elseif ($type == 'buy_intra') {
1778 $field = 'accountancy_code_buy_intra';
1779 } elseif ($type == 'buy_export') {
1780 $field = 'accountancy_code_buy_export';
1781 } elseif ($type == 'sell') {
1782 $field = 'accountancy_code_sell';
1783 } elseif ($type == 'sell_intra') {
1784 $field = 'accountancy_code_sell_intra';
1785 } elseif ($type == 'sell_export') {
1786 $field = 'accountancy_code_sell_export';
1787 } else {
1788 return -1;
1789 }
1790
1791 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ";
1792 $sql .= "$field = '".$this->db->escape($value)."'";
1793 $sql .= " WHERE rowid = ".((int) $this->id);
1794
1795 dol_syslog(__METHOD__, LOG_DEBUG);
1796 $resql = $this->db->query($sql);
1797
1798 if ($resql) {
1799 // Call trigger
1800 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1801 if ($result < 0) {
1802 $error++;
1803 }
1804 // End call triggers
1805
1806 if ($error) {
1807 $this->db->rollback();
1808 return -1;
1809 }
1810
1811 $this->$field = $value;
1812
1813 $this->db->commit();
1814 return 1;
1815 } else {
1816 $this->error = $this->db->lasterror();
1817 $this->db->rollback();
1818 return -1;
1819 }
1820 }
1821
1827 public function getMultiLangs()
1828 {
1829 global $langs;
1830
1831 $current_lang = $langs->getDefaultLang();
1832
1833 $sql = "SELECT lang, label, description, note as other";
1834 $sql .= " FROM ".$this->db->prefix()."product_lang";
1835 $sql .= " WHERE fk_product = ".((int) $this->id);
1836
1837 $result = $this->db->query($sql);
1838 if ($result) {
1839 while ($obj = $this->db->fetch_object($result)) {
1840 //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
1841 if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
1842 $this->label = $obj->label;
1843 $this->description = $obj->description;
1844 $this->other = $obj->other;
1845 }
1846 $this->multilangs["$obj->lang"]["label"] = $obj->label;
1847 $this->multilangs["$obj->lang"]["description"] = $obj->description;
1848 $this->multilangs["$obj->lang"]["other"] = $obj->other;
1849 }
1850 return 1;
1851 } else {
1852 $this->error = "Error: ".$this->db->lasterror()." - ".$sql;
1853 return -1;
1854 }
1855 }
1856
1863 private function getArrayForPriceCompare($level = 0)
1864 {
1865 $testExit = array('multiprices','multiprices_ttc','multiprices_base_type','multiprices_min','multiprices_min_ttc','multiprices_tva_tx','multiprices_recuperableonly');
1866
1867 foreach ($testExit as $field) {
1868 if (!isset($this->$field)) {
1869 return array();
1870 }
1871 $tmparray = $this->$field;
1872 if (!isset($tmparray[$level])) {
1873 return array();
1874 }
1875 }
1876
1877 $lastPrice = array(
1878 'level' => $level ? $level : 1,
1879 'multiprices' => (float) $this->multiprices[$level],
1880 'multiprices_ttc' => (float) $this->multiprices_ttc[$level],
1881 'multiprices_base_type' => $this->multiprices_base_type[$level],
1882 'multiprices_min' => (float) $this->multiprices_min[$level],
1883 'multiprices_min_ttc' => (float) $this->multiprices_min_ttc[$level],
1884 'multiprices_tva_tx' => (float) $this->multiprices_tva_tx[$level],
1885 'multiprices_recuperableonly' => (float) $this->multiprices_recuperableonly[$level],
1886 );
1887
1888 return $lastPrice;
1889 }
1890
1891
1892 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1900 private function _log_price($user, $level = 0)
1901 {
1902 // phpcs:enable
1903 global $conf;
1904
1905 $now = dol_now();
1906
1907 // Clean parameters
1908 if (empty($this->price_by_qty)) {
1909 $this->price_by_qty = 0;
1910 }
1911
1912 // Add new price
1913 $sql = "INSERT INTO ".$this->db->prefix()."product_price(price_level,date_price, fk_product, fk_user_author, price, price_ttc, price_base_type,tosell, tva_tx, default_vat_code, recuperableonly,";
1914 $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
1915 $sql .= " VALUES(".($level ? ((int) $level) : 1).", '".$this->db->idate($now)."', ".((int) $this->id).", ".((int) $user->id).", ".((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).",";
1916 $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');
1917 $sql .= ")";
1918
1919 dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
1920 $resql = $this->db->query($sql);
1921 if (!$resql) {
1922 $this->error = $this->db->lasterror();
1923 dol_print_error($this->db);
1924 return -1;
1925 } else {
1926 return 1;
1927 }
1928 }
1929
1930
1931 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1939 public function log_price_delete($user, $rowid)
1940 {
1941 // phpcs:enable
1942 $sql = "DELETE FROM ".$this->db->prefix()."product_price_by_qty";
1943 $sql .= " WHERE fk_product_price = ".((int) $rowid);
1944 $resql = $this->db->query($sql);
1945
1946 $sql = "DELETE FROM ".$this->db->prefix()."product_price";
1947 $sql .= " WHERE rowid=".((int) $rowid);
1948 $resql = $this->db->query($sql);
1949 if ($resql) {
1950 return 1;
1951 } else {
1952 $this->error = $this->db->lasterror();
1953 return -1;
1954 }
1955 }
1956
1957
1967 public function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp = 0)
1968 {
1969 global $conf, $hookmanager, $action;
1970
1971 // Call hook if any
1972 if (is_object($hookmanager)) {
1973 $parameters = array('thirdparty_seller'=>$thirdparty_seller, 'thirdparty_buyer' => $thirdparty_buyer, 'pqp' => $pqp);
1974 // Note that $action and $object may have been modified by some hooks
1975 $reshook = $hookmanager->executeHooks('getSellPrice', $parameters, $this, $action);
1976 if ($reshook > 0) {
1977 return $hookmanager->resArray;
1978 }
1979 }
1980
1981 // Update if prices fields are defined
1982 $tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id);
1983 $tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id);
1984 if (empty($tva_tx)) {
1985 $tva_npr = 0;
1986 }
1987
1988 $pu_ht = $this->price;
1989 $pu_ttc = $this->price_ttc;
1990 $price_min = $this->price_min;
1991 $price_base_type = $this->price_base_type;
1992
1993 // If price per segment
1994 if (getDolGlobalString('PRODUIT_MULTIPRICES') && !empty($thirdparty_buyer->price_level)) {
1995 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
1996 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
1997 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
1998 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
1999 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
2000 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2001 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2002 }
2003 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2004 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2005 }
2006 if (empty($tva_tx)) {
2007 $tva_npr = 0;
2008 }
2009 }
2010 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
2011 // If price per customer
2012 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2013
2014 $prodcustprice = new ProductCustomerPrice($this->db);
2015
2016 $filter = array('t.fk_product' => $this->id, 't.fk_soc' => $thirdparty_buyer->id);
2017
2018 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2019 if ($result) {
2020 if (count($prodcustprice->lines) > 0) {
2021 $pu_ht = price($prodcustprice->lines[0]->price);
2022 $price_min = price($prodcustprice->lines[0]->price_min);
2023 $pu_ttc = price($prodcustprice->lines[0]->price_ttc);
2024 $price_base_type = $prodcustprice->lines[0]->price_base_type;
2025 $tva_tx = $prodcustprice->lines[0]->tva_tx;
2026 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2027 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
2028 }
2029 $tva_npr = $prodcustprice->lines[0]->recuperableonly;
2030 if (empty($tva_tx)) {
2031 $tva_npr = 0;
2032 }
2033 }
2034 }
2035 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY')) {
2036 // If price per quantity
2037 if ($this->prices_by_qty[0]) {
2038 // yes, this product has some prices per quantity
2039 // Search price into product_price_by_qty from $this->id
2040 foreach ($this->prices_by_qty_list[0] as $priceforthequantityarray) {
2041 if ($priceforthequantityarray['rowid'] != $pqp) {
2042 continue;
2043 }
2044 // We found the price
2045 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2046 $pu_ht = $priceforthequantityarray['unitprice'];
2047 } else {
2048 $pu_ttc = $priceforthequantityarray['unitprice'];
2049 }
2050 break;
2051 }
2052 }
2053 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
2054 // If price per quantity and customer
2055 if ($this->prices_by_qty[$thirdparty_buyer->price_level]) {
2056 // yes, this product has some prices per quantity
2057 // Search price into product_price_by_qty from $this->id
2058 foreach ($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray) {
2059 if ($priceforthequantityarray['rowid'] != $pqp) {
2060 continue;
2061 }
2062 // We found the price
2063 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2064 $pu_ht = $priceforthequantityarray['unitprice'];
2065 } else {
2066 $pu_ttc = $priceforthequantityarray['unitprice'];
2067 }
2068 break;
2069 }
2070 }
2071 }
2072
2073 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);
2074 }
2075
2076 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2090 public function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
2091 {
2092 // phpcs:enable
2093 global $conf;
2094 $result = 0;
2095
2096 // 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)
2097 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2098 $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,";
2099 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2100 $sql .= " pfp.packaging";
2101 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2102 $sql .= " WHERE pfp.rowid = ".((int) $prodfournprice);
2103 if ($qty > 0) {
2104 $sql .= " AND pfp.quantity <= ".((float) $qty);
2105 }
2106 $sql .= " ORDER BY pfp.quantity DESC";
2107
2108 dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
2109 $resql = $this->db->query($sql);
2110 if ($resql) {
2111 $obj = $this->db->fetch_object($resql);
2112 if ($obj && $obj->quantity > 0) { // If we found a supplier prices from the id of supplier price
2113 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2114 $prod_supplier = new ProductFournisseur($this->db);
2115 $prod_supplier->product_fourn_price_id = $obj->rowid;
2116 $prod_supplier->id = $obj->fk_product;
2117 $prod_supplier->fourn_qty = $obj->quantity;
2118 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2119 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2120
2121 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2122 $priceparser = new PriceParser($this->db);
2123 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2124 if ($price_result >= 0) {
2125 $obj->price = $price_result;
2126 }
2127 }
2128 $this->product_fourn_price_id = $obj->rowid;
2129 $this->buyprice = $obj->price; // deprecated
2130 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
2131 $this->fourn_price_base_type = 'HT'; // Price base type
2132 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2133 $this->ref_fourn = $obj->ref_supplier; // deprecated
2134 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2135 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2136 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2137 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2138 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2139 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2140 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2141 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2142 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2143 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2144 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2145 $this->packaging = $obj->packaging;
2146 }
2147 $result = $obj->fk_product;
2148 return $result;
2149 } else { // If not found
2150 // 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.
2151 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2152 $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,";
2153 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2154 $sql .= " pfp.packaging";
2155 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2156 $sql .= " WHERE 1 = 1";
2157 if ($product_id > 0) {
2158 $sql .= " AND pfp.fk_product = ".((int) $product_id);
2159 }
2160 if ($fourn_ref != 'none') {
2161 $sql .= " AND pfp.ref_fourn = '".$this->db->escape($fourn_ref)."'";
2162 }
2163 if ($fk_soc > 0) {
2164 $sql .= " AND pfp.fk_soc = ".((int) $fk_soc);
2165 }
2166 if ($qty > 0) {
2167 $sql .= " AND pfp.quantity <= ".((float) $qty);
2168 }
2169 $sql .= " ORDER BY pfp.quantity DESC";
2170 $sql .= " LIMIT 1";
2171
2172 dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
2173 $resql = $this->db->query($sql);
2174 if ($resql) {
2175 $obj = $this->db->fetch_object($resql);
2176 if ($obj && $obj->quantity > 0) { // If found
2177 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2178 $prod_supplier = new ProductFournisseur($this->db);
2179 $prod_supplier->product_fourn_price_id = $obj->rowid;
2180 $prod_supplier->id = $obj->fk_product;
2181 $prod_supplier->fourn_qty = $obj->quantity;
2182 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2183 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2184
2185 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2186 $priceparser = new PriceParser($this->db);
2187 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2188 if ($result >= 0) {
2189 $obj->price = $price_result;
2190 }
2191 }
2192 $this->product_fourn_price_id = $obj->rowid;
2193 $this->buyprice = $obj->price; // deprecated
2194 $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
2195 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
2196 $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
2197 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2198 $this->ref_fourn = $obj->ref_supplier; // deprecated
2199 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2200 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2201 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2202 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2203 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2204 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2205 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2206 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2207 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2208 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2209 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2210 $this->packaging = $obj->packaging;
2211 }
2212 $result = $obj->fk_product;
2213 return $result;
2214 } else {
2215 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é.
2216 }
2217 } else {
2218 $this->error = $this->db->lasterror();
2219 return -3;
2220 }
2221 }
2222 } else {
2223 $this->error = $this->db->lasterror();
2224 return -2;
2225 }
2226 }
2227
2228
2246 public function updatePrice($newprice, $newpricebase, $user, $newvat = '', $newminprice = 0, $level = 0, $newnpr = 0, $newpbq = 0, $ignore_autogen = 0, $localtaxes_array = array(), $newdefaultvatcode = '', $notrigger = 0)
2247 {
2248 global $conf, $langs;
2249
2250 $lastPriceData = $this->getArrayForPriceCompare($level); // temporary store current price before update
2251
2252 $id = $this->id;
2253
2254 dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
2255
2256 // Clean parameters
2257 if (empty($this->tva_tx)) {
2258 $this->tva_tx = 0;
2259 }
2260 if (empty($newnpr)) {
2261 $newnpr = 0;
2262 }
2263 if (empty($newminprice)) {
2264 $newminprice = 0;
2265 }
2266 if (empty($newminprice)) {
2267 $newminprice = 0;
2268 }
2269
2270 // Check parameters
2271 if ($newvat == '') {
2272 $newvat = $this->tva_tx;
2273 }
2274
2275 // If multiprices are enabled, then we check if the current product is subject to price autogeneration
2276 // Price will be modified ONLY when the first one is the one that is being modified
2277 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
2278 return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
2279 }
2280
2281 if (!empty($newminprice) && ($newminprice > $newprice)) {
2282 $this->error = 'ErrorPriceCantBeLowerThanMinPrice';
2283 return -1;
2284 }
2285
2286 if ($newprice !== '' || $newprice === 0) {
2287 if ($newpricebase == 'TTC') {
2288 $price_ttc = price2num($newprice, 'MU');
2289 $price = price2num($newprice) / (1 + ($newvat / 100));
2290 $price = price2num($price, 'MU');
2291
2292 if ($newminprice != '' || $newminprice == 0) {
2293 $price_min_ttc = price2num($newminprice, 'MU');
2294 $price_min = price2num($newminprice) / (1 + ($newvat / 100));
2295 $price_min = price2num($price_min, 'MU');
2296 } else {
2297 $price_min = 0;
2298 $price_min_ttc = 0;
2299 }
2300 } else {
2301 $price = price2num($newprice, 'MU');
2302 $price_ttc = ($newnpr != 1) ? (float) price2num($newprice) * (1 + ($newvat / 100)) : $price;
2303 $price_ttc = price2num($price_ttc, 'MU');
2304
2305 if ($newminprice !== '' || $newminprice === 0) {
2306 $price_min = price2num($newminprice, 'MU');
2307 $price_min_ttc = price2num($newminprice) * (1 + ($newvat / 100));
2308 $price_min_ttc = price2num($price_min_ttc, 'MU');
2309 //print 'X'.$newminprice.'-'.$price_min;
2310 } else {
2311 $price_min = 0;
2312 $price_min_ttc = 0;
2313 }
2314 }
2315 //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
2316
2317 if (count($localtaxes_array) > 0) {
2318 $localtaxtype1 = $localtaxes_array['0'];
2319 $localtax1 = $localtaxes_array['1'];
2320 $localtaxtype2 = $localtaxes_array['2'];
2321 $localtax2 = $localtaxes_array['3'];
2322 } else {
2323 // if array empty, we try to use the vat code
2324 if (!empty($newdefaultvatcode)) {
2325 global $mysoc;
2326 // Get record from code
2327 $sql = "SELECT t.rowid, t.code, t.recuperableonly as tva_npr, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
2328 $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
2329 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$this->db->escape($mysoc->country_code)."'";
2330 $sql .= " AND t.taux = ".((float) $newdefaultvatcode)." AND t.active = 1";
2331 $sql .= " AND t.code = '".$this->db->escape($newdefaultvatcode)."'";
2332 $resql = $this->db->query($sql);
2333 if ($resql) {
2334 $obj = $this->db->fetch_object($resql);
2335 if ($obj) {
2336 $npr = $obj->tva_npr;
2337 $localtax1 = $obj->localtax1;
2338 $localtax2 = $obj->localtax2;
2339 $localtaxtype1 = $obj->localtax1_type;
2340 $localtaxtype2 = $obj->localtax2_type;
2341 }
2342 }
2343 } else {
2344 // old method. deprecated because we can't retrieve type
2345 $localtaxtype1 = '0';
2346 $localtax1 = get_localtax($newvat, 1);
2347 $localtaxtype2 = '0';
2348 $localtax2 = get_localtax($newvat, 2);
2349 }
2350 }
2351 if (empty($localtax1)) {
2352 $localtax1 = 0; // If = '' then = 0
2353 }
2354 if (empty($localtax2)) {
2355 $localtax2 = 0; // If = '' then = 0
2356 }
2357
2358 $this->db->begin();
2359
2360 // Ne pas mettre de quote sur les numeriques decimaux.
2361 // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
2362 $sql = "UPDATE ".$this->db->prefix()."product SET";
2363 $sql .= " price_base_type = '".$this->db->escape($newpricebase)."',";
2364 $sql .= " price = ".(float) $price.",";
2365 $sql .= " price_ttc = ".(float) $price_ttc.",";
2366 $sql .= " price_min = ".(float) $price_min.",";
2367 $sql .= " price_min_ttc = ".(float) $price_min_ttc.",";
2368 $sql .= " localtax1_tx = ".($localtax1 >= 0 ? (float) $localtax1 : 'NULL').",";
2369 $sql .= " localtax2_tx = ".($localtax2 >= 0 ? (float) $localtax2 : 'NULL').",";
2370 $sql .= " localtax1_type = ".($localtaxtype1 != '' ? "'".$this->db->escape($localtaxtype1)."'" : "'0'").",";
2371 $sql .= " localtax2_type = ".($localtaxtype2 != '' ? "'".$this->db->escape($localtaxtype2)."'" : "'0'").",";
2372 $sql .= " default_vat_code = ".($newdefaultvatcode ? "'".$this->db->escape($newdefaultvatcode)."'" : "null").",";
2373 $sql .= " tva_tx = ".(float) price2num($newvat).",";
2374 $sql .= " recuperableonly = '".$this->db->escape($newnpr)."'";
2375 $sql .= " WHERE rowid = ".((int) $id);
2376
2377 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2378 $resql = $this->db->query($sql);
2379 if ($resql) {
2380 $this->multiprices[$level] = $price;
2381 $this->multiprices_ttc[$level] = $price_ttc;
2382 $this->multiprices_min[$level] = $price_min;
2383 $this->multiprices_min_ttc[$level] = $price_min_ttc;
2384 $this->multiprices_base_type[$level] = $newpricebase;
2385 $this->multiprices_default_vat_code[$level] = $newdefaultvatcode;
2386 $this->multiprices_tva_tx[$level] = $newvat;
2387 $this->multiprices_recuperableonly[$level] = $newnpr;
2388
2389 $this->price = $price;
2390 $this->price_ttc = $price_ttc;
2391 $this->price_min = $price_min;
2392 $this->price_min_ttc = $price_min_ttc;
2393 $this->price_base_type = $newpricebase;
2394 $this->default_vat_code = $newdefaultvatcode;
2395 $this->tva_tx = $newvat;
2396 $this->tva_npr = $newnpr;
2397 //Local taxes
2398 $this->localtax1_tx = $localtax1;
2399 $this->localtax2_tx = $localtax2;
2400 $this->localtax1_type = $localtaxtype1;
2401 $this->localtax2_type = $localtaxtype2;
2402
2403 // Price by quantity
2404 $this->price_by_qty = $newpbq;
2405
2406 // check if price have really change before log
2407 $newPriceData = $this->getArrayForPriceCompare($level);
2408 if (!empty(array_diff_assoc($newPriceData, $lastPriceData)) || !getDolGlobalString('PRODUIT_MULTIPRICES')) {
2409 $this->_log_price($user, $level); // Save price for level into table product_price
2410 }
2411
2412 $this->level = $level; // Store level of price edited for trigger
2413
2414 // Call trigger
2415 if (!$notrigger) {
2416 $result = $this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
2417 if ($result < 0) {
2418 $this->db->rollback();
2419 return -1;
2420 }
2421 }
2422 // End call triggers
2423
2424 $this->db->commit();
2425 } else {
2426 $this->db->rollback();
2427 $this->error = $this->db->lasterror();
2428 return -1;
2429 }
2430 }
2431
2432 return 1;
2433 }
2434
2442 public function setPriceExpression($expression_id)
2443 {
2444 global $user;
2445
2446 $this->fk_price_expression = $expression_id;
2447
2448 return $this->update($this->id, $user);
2449 }
2450
2463 public function fetch($id = '', $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0, $ignore_price_load = 0, $ignore_lang_load = 0)
2464 {
2465 include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2466
2467 global $langs, $conf;
2468
2469 dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
2470
2471 // Check parameters
2472 if (!$id && !$ref && !$ref_ext && !$barcode) {
2473 $this->error = 'ErrorWrongParameters';
2474 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2475 return -1;
2476 }
2477
2478 $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,";
2479 $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,";
2480 $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,";
2481 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
2482 $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,";
2483 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2484 $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,";
2485 } else {
2486 $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,";
2487 }
2488
2489 //For MultiCompany
2490 //PMP per entity & Stocks Sharings stock_reel includes only stocks shared with this entity
2491 $separatedEntityPMP = false; // Set to true to get the AWP from table llx_product_perentity instead of field 'pmp' into llx_product.
2492 $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.
2493 $visibleWarehousesEntities = $conf->entity;
2494 if (getDolGlobalString('MULTICOMPANY_PRODUCT_SHARING_ENABLED')) {
2495 if (getDolGlobalString('MULTICOMPANY_PMP_PER_ENTITY_ENABLED')) {
2496 $checkPMPPerEntity = $this->db->query("SELECT pmp FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = ".((int) $id)." AND entity = ".(int) $conf->entity);
2497 if ($this->db->num_rows($checkPMPPerEntity)>0) {
2498 $separatedEntityPMP = true;
2499 }
2500 }
2501 global $mc;
2502 $separatedStock = true;
2503 if (isset($mc->sharings['stock']) && !empty($mc->sharings['stock'])) {
2504 $visibleWarehousesEntities .= "," . implode(",", $mc->sharings['stock']);
2505 }
2506 }
2507 if ($separatedEntityPMP) {
2508 $sql .= " ppe.pmp,";
2509 } else {
2510 $sql .= " p.pmp,";
2511 }
2512 $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.batch_mask, p.fk_unit,";
2513 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf,";
2514 if ($separatedStock) {
2515 $sql .= " SUM(sp.reel) as stock";
2516 } else {
2517 $sql .= " p.stock";
2518 }
2519 $sql .= " FROM ".$this->db->prefix()."product as p";
2520 if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') || $separatedEntityPMP) {
2521 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
2522 }
2523 if ($separatedStock) {
2524 $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)."))";
2525 }
2526
2527 if ($id) {
2528 $sql .= " WHERE p.rowid = ".((int) $id);
2529 } else {
2530 $sql .= " WHERE p.entity IN (".getEntity($this->element).")";
2531 if ($ref) {
2532 $sql .= " AND p.ref = '".$this->db->escape($ref)."'";
2533 } elseif ($ref_ext) {
2534 $sql .= " AND p.ref_ext = '".$this->db->escape($ref_ext)."'";
2535 } elseif ($barcode) {
2536 $sql .= " AND p.barcode = '".$this->db->escape($barcode)."'";
2537 }
2538 }
2539 if ($separatedStock) {
2540 $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,";
2541 $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,";
2542 $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,";
2543 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
2544 $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,";
2545 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2546 $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,";
2547 } else {
2548 $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,";
2549 }
2550 if ($separatedEntityPMP) {
2551 $sql .= " ppe.pmp,";
2552 } else {
2553 $sql .= " p.pmp,";
2554 }
2555 $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.batch_mask, p.fk_unit,";
2556 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf";
2557 if (!$separatedStock) {
2558 $sql .= ", p.stock";
2559 }
2560 }
2561
2562 $resql = $this->db->query($sql);
2563 if ($resql) {
2564 unset($this->oldcopy);
2565
2566 if ($this->db->num_rows($resql) > 0) {
2567 $obj = $this->db->fetch_object($resql);
2568
2569 $this->id = $obj->rowid;
2570 $this->ref = $obj->ref;
2571 $this->ref_ext = $obj->ref_ext;
2572 $this->label = $obj->label;
2573 $this->description = $obj->description;
2574 $this->url = $obj->url;
2575 $this->note_public = $obj->note_public;
2576 $this->note_private = $obj->note_private;
2577 $this->note = $obj->note_private; // deprecated
2578
2579 $this->type = $obj->fk_product_type;
2580 $this->status = $obj->tosell;
2581 $this->status_buy = $obj->tobuy;
2582 $this->status_batch = $obj->tobatch;
2583 $this->batch_mask = $obj->batch_mask;
2584
2585 $this->customcode = $obj->customcode;
2586 $this->country_id = $obj->fk_country;
2587 $this->country_code = getCountry($this->country_id, 2, $this->db);
2588 $this->state_id = $obj->fk_state;
2589 $this->lifetime = $obj->lifetime;
2590 $this->qc_frequency = $obj->qc_frequency;
2591 $this->price = $obj->price;
2592 $this->price_ttc = $obj->price_ttc;
2593 $this->price_min = $obj->price_min;
2594 $this->price_min_ttc = $obj->price_min_ttc;
2595 $this->price_base_type = $obj->price_base_type;
2596 $this->cost_price = $obj->cost_price;
2597 $this->default_vat_code = $obj->default_vat_code;
2598 $this->tva_tx = $obj->tva_tx;
2600 $this->tva_npr = $obj->tva_npr;
2602 $this->localtax1_tx = $obj->localtax1_tx;
2603 $this->localtax2_tx = $obj->localtax2_tx;
2604 $this->localtax1_type = $obj->localtax1_type;
2605 $this->localtax2_type = $obj->localtax2_type;
2606
2607 $this->finished = $obj->finished;
2608 $this->fk_default_bom = $obj->fk_default_bom;
2609
2610 $this->duration = $obj->duration;
2611 $this->duration_value = $obj->duration ? (int) (substr($obj->duration, 0, dol_strlen($obj->duration) - 1)) : 0;
2612 $this->duration_unit = $obj->duration ? substr($obj->duration, -1) : null;
2613 $this->canvas = $obj->canvas;
2614 $this->net_measure = $obj->net_measure;
2615 $this->net_measure_units = $obj->net_measure_units;
2616 $this->weight = $obj->weight;
2617 $this->weight_units = $obj->weight_units;
2618 $this->length = $obj->length;
2619 $this->length_units = $obj->length_units;
2620 $this->width = $obj->width;
2621 $this->width_units = $obj->width_units;
2622 $this->height = $obj->height;
2623 $this->height_units = $obj->height_units;
2624
2625 $this->surface = $obj->surface;
2626 $this->surface_units = $obj->surface_units;
2627 $this->volume = $obj->volume;
2628 $this->volume_units = $obj->volume_units;
2629 $this->barcode = $obj->barcode;
2630 $this->barcode_type = $obj->fk_barcode_type;
2631
2632 $this->accountancy_code_buy = $obj->accountancy_code_buy;
2633 $this->accountancy_code_buy_intra = $obj->accountancy_code_buy_intra;
2634 $this->accountancy_code_buy_export = $obj->accountancy_code_buy_export;
2635 $this->accountancy_code_sell = $obj->accountancy_code_sell;
2636 $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
2637 $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
2638
2639 $this->fk_default_warehouse = $obj->fk_default_warehouse;
2640 $this->fk_default_workstation = $obj->fk_default_workstation;
2641 $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
2642 $this->desiredstock = $obj->desiredstock;
2643 $this->stock_reel = $obj->stock;
2644 $this->pmp = $obj->pmp;
2645
2646 $this->date_creation = $obj->datec;
2647 $this->date_modification = $obj->tms;
2648 $this->import_key = $obj->import_key;
2649 $this->entity = $obj->entity;
2650
2651 $this->ref_ext = $obj->ref_ext;
2652 $this->fk_price_expression = $obj->fk_price_expression;
2653 $this->fk_unit = $obj->fk_unit;
2654 $this->price_autogen = $obj->price_autogen;
2655 $this->model_pdf = $obj->model_pdf;
2656
2657 $this->mandatory_period = $obj->mandatory_period;
2658
2659 $this->db->free($resql);
2660
2661 // fetch optionals attributes and labels
2662 $this->fetch_optionals();
2663
2664 // Multilangs
2665 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($ignore_lang_load)) {
2666 $this->getMultiLangs();
2667 }
2668
2669 // Load multiprices array
2670 if (getDolGlobalString('PRODUIT_MULTIPRICES') && empty($ignore_price_load)) { // prices per segment
2671 for ($i = 1; $i <= getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT'); $i++) {
2672 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2673 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2674 $sql .= " FROM ".$this->db->prefix()."product_price";
2675 $sql .= " WHERE entity IN (".getEntity('productprice').")";
2676 $sql .= " AND price_level=".((int) $i);
2677 $sql .= " AND fk_product = ".((int) $this->id);
2678 $sql .= " ORDER BY date_price DESC, rowid DESC"; // Get the most recent line
2679 $sql .= " LIMIT 1"; // Only the first one
2680 $resql = $this->db->query($sql);
2681 if ($resql) {
2682 $result = $this->db->fetch_array($resql);
2683
2684 $this->multiprices[$i] = $result ? $result["price"] : null;
2685 $this->multiprices_ttc[$i] = $result ? $result["price_ttc"] : null;
2686 $this->multiprices_min[$i] = $result ? $result["price_min"] : null;
2687 $this->multiprices_min_ttc[$i] = $result ? $result["price_min_ttc"] : null;
2688 $this->multiprices_base_type[$i] = $result ? $result["price_base_type"] : null;
2689 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2690 $this->multiprices_tva_tx[$i] = $result ? $result["tva_tx"].($result ? ' ('.$result['default_vat_code'].')' : '') : null;
2691 $this->multiprices_recuperableonly[$i] = $result ? $result["recuperableonly"] : null;
2692
2693 // Price by quantity
2694 /*
2695 $this->prices_by_qty[$i]=$result["price_by_qty"];
2696 $this->prices_by_qty_id[$i]=$result["rowid"];
2697 // Récuperation de la liste des prix selon qty si flag positionné
2698 if ($this->prices_by_qty[$i] == 1)
2699 {
2700 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2701 $sql.= " FROM ".$this->db->prefix()."product_price_by_qty";
2702 $sql.= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2703 $sql.= " ORDER BY quantity ASC";
2704 $resultat=array();
2705 $resql = $this->db->query($sql);
2706 if ($resql)
2707 {
2708 $ii=0;
2709 while ($result= $this->db->fetch_array($resql)) {
2710 $resultat[$ii]=array();
2711 $resultat[$ii]["rowid"]=$result["rowid"];
2712 $resultat[$ii]["price"]= $result["price"];
2713 $resultat[$ii]["unitprice"]= $result["unitprice"];
2714 $resultat[$ii]["quantity"]= $result["quantity"];
2715 $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2716 $resultat[$ii]["remise"]= $result["remise"]; // deprecated
2717 $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2718 $ii++;
2719 }
2720 $this->prices_by_qty_list[$i]=$resultat;
2721 }
2722 else
2723 {
2724 dol_print_error($this->db);
2725 return -1;
2726 }
2727 }*/
2728 } else {
2729 $this->error = $this->db->lasterror;
2730 return -1;
2731 }
2732 }
2733 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') && empty($ignore_price_load)) { // prices per customers
2734 // Nothing loaded by default. List may be very long.
2735 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') && empty($ignore_price_load)) { // prices per quantity
2736 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2737 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
2738 $sql .= " FROM ".$this->db->prefix()."product_price";
2739 $sql .= " WHERE fk_product = ".((int) $this->id);
2740 $sql .= " ORDER BY date_price DESC, rowid DESC";
2741 $sql .= " LIMIT 1";
2742 $resql = $this->db->query($sql);
2743 if ($resql) {
2744 $result = $this->db->fetch_array($resql);
2745
2746 // Price by quantity
2747 $this->prices_by_qty[0] = $result["price_by_qty"];
2748 $this->prices_by_qty_id[0] = $result["rowid"];
2749 // Récuperation de la liste des prix selon qty si flag positionné
2750 if ($this->prices_by_qty[0] == 1) {
2751 $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
2752 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2753 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[0]);
2754 $sql .= " ORDER BY quantity ASC";
2755 $resultat = array();
2756 $resql = $this->db->query($sql);
2757 if ($resql) {
2758 $ii = 0;
2759 while ($result = $this->db->fetch_array($resql)) {
2760 $resultat[$ii] = array();
2761 $resultat[$ii]["rowid"] = $result["rowid"];
2762 $resultat[$ii]["price"] = $result["price"];
2763 $resultat[$ii]["unitprice"] = $result["unitprice"];
2764 $resultat[$ii]["quantity"] = $result["quantity"];
2765 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2766 //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
2767 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2768 $ii++;
2769 }
2770 $this->prices_by_qty_list[0] = $resultat;
2771 } else {
2772 $this->error = $this->db->lasterror;
2773 return -1;
2774 }
2775 }
2776 } else {
2777 $this->error = $this->db->lasterror;
2778 return -1;
2779 }
2780 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') && empty($ignore_price_load)) { // prices per customer and quantity
2781 for ($i = 1; $i <= getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT'); $i++) {
2782 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2783 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2784 $sql .= " FROM ".$this->db->prefix()."product_price";
2785 $sql .= " WHERE entity IN (".getEntity('productprice').")";
2786 $sql .= " AND price_level=".((int) $i);
2787 $sql .= " AND fk_product = ".((int) $this->id);
2788 $sql .= " ORDER BY date_price DESC, rowid DESC";
2789 $sql .= " LIMIT 1";
2790 $resql = $this->db->query($sql);
2791 if ($resql) {
2792 $result = $this->db->fetch_array($resql);
2793
2794 $this->multiprices[$i] = (!empty($result["price"]) ? $result["price"] : 0);
2795 $this->multiprices_ttc[$i] = (!empty($result["price_ttc"]) ? $result["price_ttc"] : 0);
2796 $this->multiprices_min[$i] = (!empty($result["price_min"]) ? $result["price_min"] : 0);
2797 $this->multiprices_min_ttc[$i] = (!empty($result["price_min_ttc"]) ? $result["price_min_ttc"] : 0);
2798 $this->multiprices_base_type[$i] = (!empty($result["price_base_type"]) ? $result["price_base_type"] : '');
2799 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2800 $this->multiprices_tva_tx[$i] = (!empty($result["tva_tx"]) ? $result["tva_tx"] : 0); // TODO Add ' ('.$result['default_vat_code'].')'
2801 $this->multiprices_recuperableonly[$i] = (!empty($result["recuperableonly"]) ? $result["recuperableonly"] : 0);
2802
2803 // Price by quantity
2804 $this->prices_by_qty[$i] = (!empty($result["price_by_qty"]) ? $result["price_by_qty"] : 0);
2805 $this->prices_by_qty_id[$i] = (!empty($result["rowid"]) ? $result["rowid"] : 0);
2806 // Récuperation de la liste des prix selon qty si flag positionné
2807 if ($this->prices_by_qty[$i] == 1) {
2808 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2809 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2810 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2811 $sql .= " ORDER BY quantity ASC";
2812 $resultat = array();
2813 $resql = $this->db->query($sql);
2814 if ($resql) {
2815 $ii = 0;
2816 while ($result = $this->db->fetch_array($resql)) {
2817 $resultat[$ii] = array();
2818 $resultat[$ii]["rowid"] = $result["rowid"];
2819 $resultat[$ii]["price"] = $result["price"];
2820 $resultat[$ii]["unitprice"] = $result["unitprice"];
2821 $resultat[$ii]["quantity"] = $result["quantity"];
2822 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2823 $resultat[$ii]["remise"] = $result["remise"]; // deprecated
2824 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2825 $ii++;
2826 }
2827 $this->prices_by_qty_list[$i] = $resultat;
2828 } else {
2829 $this->error = $this->db->lasterror;
2830 return -1;
2831 }
2832 }
2833 } else {
2834 $this->error = $this->db->lasterror;
2835 return -1;
2836 }
2837 }
2838 }
2839
2840 if (isModEnabled('dynamicprices') && !empty($this->fk_price_expression) && empty($ignore_expression)) {
2841 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2842 $priceparser = new PriceParser($this->db);
2843 $price_result = $priceparser->parseProduct($this);
2844 if ($price_result >= 0) {
2845 $this->price = $price_result;
2846 // Calculate the VAT
2847 $this->price_ttc = price2num($this->price) * (1 + ($this->tva_tx / 100));
2848 $this->price_ttc = price2num($this->price_ttc, 'MU');
2849 }
2850 }
2851
2852 // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
2853 // Instead we just init the stock_warehouse array
2854 $this->stock_warehouse = array();
2855
2856 return 1;
2857 } else {
2858 return 0;
2859 }
2860 } else {
2861 $this->error = $this->db->lasterror();
2862 return -1;
2863 }
2864 }
2865
2866 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2873 public function load_stats_mo($socid = 0)
2874 {
2875 // phpcs:enable
2876 global $user, $hookmanager, $action;
2877
2878 $error = 0;
2879
2880 foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
2881 $this->stats_mo['customers_'.$role] = 0;
2882 $this->stats_mo['nb_'.$role] = 0;
2883 $this->stats_mo['qty_'.$role] = 0;
2884
2885 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2886 $sql .= " SUM(mp.qty) as qty";
2887 $sql .= " FROM ".$this->db->prefix()."mrp_mo as c";
2888 $sql .= " INNER JOIN ".$this->db->prefix()."mrp_production as mp ON mp.fk_mo=c.rowid";
2889 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
2890 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc=c.fk_soc AND sc.fk_user = ".((int) $user->id);
2891 }
2892 $sql .= " WHERE ";
2893 $sql .= " c.entity IN (".getEntity('mo').")";
2894
2895 $sql .= " AND mp.fk_product = ".((int) $this->id);
2896 $sql .= " AND mp.role ='".$this->db->escape($role)."'";
2897 if ($socid > 0) {
2898 $sql .= " AND c.fk_soc = ".((int) $socid);
2899 }
2900
2901 $result = $this->db->query($sql);
2902 if ($result) {
2903 $obj = $this->db->fetch_object($result);
2904 $this->stats_mo['customers_'.$role] = $obj->nb_customers ? $obj->nb_customers : 0;
2905 $this->stats_mo['nb_'.$role] = $obj->nb ? $obj->nb : 0;
2906 $this->stats_mo['qty_'.$role] = $obj->qty ? price2num($obj->qty, 'MS') : 0; // qty may be a float due to the SUM()
2907 } else {
2908 $this->error = $this->db->error();
2909 $error++;
2910 }
2911 }
2912
2913 if (!empty($error)) {
2914 return -1;
2915 }
2916
2917 $parameters = array('socid' => $socid);
2918 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
2919 if ($reshook > 0) {
2920 $this->stats_mo = $hookmanager->resArray['stats_mo'];
2921 }
2922
2923 return 1;
2924 }
2925
2926 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2933 public function load_stats_bom($socid = 0)
2934 {
2935 // phpcs:enable
2936 global $user, $hookmanager, $action;
2937
2938 $error = 0;
2939
2940 $this->stats_bom['nb_toproduce'] = 0;
2941 $this->stats_bom['nb_toconsume'] = 0;
2942 $this->stats_bom['qty_toproduce'] = 0;
2943 $this->stats_bom['qty_toconsume'] = 0;
2944
2945 $sql = "SELECT COUNT(DISTINCT b.rowid) as nb_toproduce,";
2946 $sql .= " SUM(b.qty) as qty_toproduce";
2947 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
2948 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
2949 $sql .= " WHERE ";
2950 $sql .= " b.entity IN (".getEntity('bom').")";
2951 $sql .= " AND b.fk_product =".((int) $this->id);
2952 $sql .= " GROUP BY b.rowid";
2953
2954 $result = $this->db->query($sql);
2955 if ($result) {
2956 $obj = $this->db->fetch_object($result);
2957 $this->stats_bom['nb_toproduce'] = !empty($obj->nb_toproduce) ? $obj->nb_toproduce : 0;
2958 $this->stats_bom['qty_toproduce'] = !empty($obj->qty_toproduce) ? price2num($obj->qty_toproduce) : 0;
2959 } else {
2960 $this->error = $this->db->error();
2961 $error++;
2962 }
2963
2964 $sql = "SELECT COUNT(DISTINCT bl.rowid) as nb_toconsume,";
2965 $sql .= " SUM(bl.qty) as qty_toconsume";
2966 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
2967 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
2968 $sql .= " WHERE ";
2969 $sql .= " b.entity IN (".getEntity('bom').")";
2970 $sql .= " AND bl.fk_product =".((int) $this->id);
2971
2972 $result = $this->db->query($sql);
2973 if ($result) {
2974 $obj = $this->db->fetch_object($result);
2975 $this->stats_bom['nb_toconsume'] = !empty($obj->nb_toconsume) ? $obj->nb_toconsume : 0;
2976 $this->stats_bom['qty_toconsume'] = !empty($obj->qty_toconsume) ? price2num($obj->qty_toconsume) : 0;
2977 } else {
2978 $this->error = $this->db->error();
2979 $error++;
2980 }
2981
2982 if (!empty($error)) {
2983 return -1;
2984 }
2985
2986 $parameters = array('socid' => $socid);
2987 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
2988 if ($reshook > 0) {
2989 $this->stats_bom = $hookmanager->resArray['stats_bom'];
2990 }
2991
2992 return 1;
2993 }
2994
2995 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3002 public function load_stats_propale($socid = 0)
3003 {
3004 // phpcs:enable
3005 global $conf, $user, $hookmanager, $action;
3006
3007 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
3008 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3009 $sql .= " FROM ".$this->db->prefix()."propaldet as pd";
3010 $sql .= ", ".$this->db->prefix()."propal as p";
3011 $sql .= ", ".$this->db->prefix()."societe as s";
3012 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3013 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3014 }
3015 $sql .= " WHERE p.rowid = pd.fk_propal";
3016 $sql .= " AND p.fk_soc = s.rowid";
3017 $sql .= " AND p.entity IN (".getEntity('propal').")";
3018 $sql .= " AND pd.fk_product = ".((int) $this->id);
3019 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3020 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3021 }
3022 //$sql.= " AND pr.fk_statut != 0";
3023 if ($socid > 0) {
3024 $sql .= " AND p.fk_soc = ".((int) $socid);
3025 }
3026
3027 $result = $this->db->query($sql);
3028 if ($result) {
3029 $obj = $this->db->fetch_object($result);
3030 $this->stats_propale['customers'] = $obj->nb_customers;
3031 $this->stats_propale['nb'] = $obj->nb;
3032 $this->stats_propale['rows'] = $obj->nb_rows;
3033 $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
3034
3035 // if it's a virtual product, maybe it is in proposal by extension
3036 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3037 $TFather = $this->getFather();
3038 if (is_array($TFather) && !empty($TFather)) {
3039 foreach ($TFather as &$fatherData) {
3040 $pFather = new Product($this->db);
3041 $pFather->id = $fatherData['id'];
3042 $qtyCoef = $fatherData['qty'];
3043
3044 if ($fatherData['incdec']) {
3045 $pFather->load_stats_propale($socid);
3046
3047 $this->stats_propale['customers'] += $pFather->stats_propale['customers'];
3048 $this->stats_propale['nb'] += $pFather->stats_propale['nb'];
3049 $this->stats_propale['rows'] += $pFather->stats_propale['rows'];
3050 $this->stats_propale['qty'] += $pFather->stats_propale['qty'] * $qtyCoef;
3051 }
3052 }
3053 }
3054 }
3055
3056 $parameters = array('socid' => $socid);
3057 $reshook = $hookmanager->executeHooks('loadStatsCustomerProposal', $parameters, $this, $action);
3058 if ($reshook > 0) {
3059 $this->stats_propale = $hookmanager->resArray['stats_propale'];
3060 }
3061
3062 return 1;
3063 } else {
3064 $this->error = $this->db->error();
3065 return -1;
3066 }
3067 }
3068
3069
3070 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3077 public function load_stats_proposal_supplier($socid = 0)
3078 {
3079 // phpcs:enable
3080 global $conf, $user, $hookmanager, $action;
3081
3082 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
3083 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3084 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as pd";
3085 $sql .= ", ".$this->db->prefix()."supplier_proposal as p";
3086 $sql .= ", ".$this->db->prefix()."societe as s";
3087 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3088 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3089 }
3090 $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
3091 $sql .= " AND p.fk_soc = s.rowid";
3092 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
3093 $sql .= " AND pd.fk_product = ".((int) $this->id);
3094 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3095 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3096 }
3097 //$sql.= " AND pr.fk_statut != 0";
3098 if ($socid > 0) {
3099 $sql .= " AND p.fk_soc = ".((int) $socid);
3100 }
3101
3102 $result = $this->db->query($sql);
3103 if ($result) {
3104 $obj = $this->db->fetch_object($result);
3105 $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
3106 $this->stats_proposal_supplier['nb'] = $obj->nb;
3107 $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
3108 $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
3109
3110 $parameters = array('socid' => $socid);
3111 $reshook = $hookmanager->executeHooks('loadStatsSupplierProposal', $parameters, $this, $action);
3112 if ($reshook > 0) {
3113 $this->stats_proposal_supplier = $hookmanager->resArray['stats_proposal_supplier'];
3114 }
3115
3116 return 1;
3117 } else {
3118 $this->error = $this->db->error();
3119 return -1;
3120 }
3121 }
3122
3123
3124 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3133 public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
3134 {
3135 // phpcs:enable
3136 global $conf, $user, $hookmanager, $action;
3137
3138 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3139 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3140 $sql .= " FROM ".$this->db->prefix()."commandedet as cd";
3141 $sql .= ", ".$this->db->prefix()."commande as c";
3142 $sql .= ", ".$this->db->prefix()."societe as s";
3143 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3144 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3145 }
3146 $sql .= " WHERE c.rowid = cd.fk_commande";
3147 $sql .= " AND c.fk_soc = s.rowid";
3148 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'commande').")";
3149 $sql .= " AND cd.fk_product = ".((int) $this->id);
3150 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3151 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3152 }
3153 if ($socid > 0) {
3154 $sql .= " AND c.fk_soc = ".((int) $socid);
3155 }
3156 if ($filtrestatut != '') {
3157 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3158 }
3159
3160 $result = $this->db->query($sql);
3161 if ($result) {
3162 $obj = $this->db->fetch_object($result);
3163 $this->stats_commande['customers'] = $obj->nb_customers;
3164 $this->stats_commande['nb'] = $obj->nb;
3165 $this->stats_commande['rows'] = $obj->nb_rows;
3166 $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
3167
3168 // if it's a virtual product, maybe it is in order by extension
3169 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3170 $TFather = $this->getFather();
3171 if (is_array($TFather) && !empty($TFather)) {
3172 foreach ($TFather as &$fatherData) {
3173 $pFather = new Product($this->db);
3174 $pFather->id = $fatherData['id'];
3175 $qtyCoef = $fatherData['qty'];
3176
3177 if ($fatherData['incdec']) {
3178 $pFather->load_stats_commande($socid, $filtrestatut);
3179
3180 $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
3181 $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
3182 $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
3183 $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
3184 }
3185 }
3186 }
3187 }
3188
3189 // If stock decrease is on invoice validation, the theorical stock continue to
3190 // count the orders lines in theoretical stock when some are already removed by invoice validation.
3191 if ($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
3192 if (getDolGlobalString('DECREASE_ONLY_UNINVOICEDPRODUCTS')) {
3193 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation but only if order not yet invoice.
3194 $adeduire = 0;
3195 $sql = "SELECT SUM(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3196 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3197 $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'))";
3198 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_source = c.rowid";
3199 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND c.facture = 0 AND fd.fk_product = ".((int) $this->id);
3200
3201 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3202 $resql = $this->db->query($sql);
3203 if ($resql) {
3204 if ($this->db->num_rows($resql) > 0) {
3205 $obj = $this->db->fetch_object($resql);
3206 $adeduire += $obj->count;
3207 }
3208 }
3209
3210 $this->stats_commande['qty'] -= $adeduire;
3211 } else {
3212 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is off, we make a compensation with lines of invoices linked to the order
3213 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3214
3215 // For every order having invoice already validated we need to decrease stock cause it's in physical stock
3216 $adeduire = 0;
3217 $sql = "SELECT sum(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet as fd ";
3218 $sql .= " JOIN ".MAIN_DB_PREFIX."facture as f ON fd.fk_facture = f.rowid";
3219 $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'))";
3220 $sql .= " JOIN ".MAIN_DB_PREFIX."commande as c ON el.fk_source = c.rowid";
3221 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product = ".((int) $this->id);
3222
3223 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3224 $resql = $this->db->query($sql);
3225 if ($resql) {
3226 if ($this->db->num_rows($resql) > 0) {
3227 $obj = $this->db->fetch_object($resql);
3228 $adeduire += $obj->count;
3229 }
3230 } else {
3231 $this->error = $this->db->error();
3232 return -1;
3233 }
3234
3235 $this->stats_commande['qty'] -= $adeduire;
3236 }
3237 }
3238
3239 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3240 $reshook = $hookmanager->executeHooks('loadStatsCustomerOrder', $parameters, $this, $action);
3241 if ($reshook > 0) {
3242 $this->stats_commande = $hookmanager->resArray['stats_commande'];
3243 }
3244 return 1;
3245 } else {
3246 $this->error = $this->db->error();
3247 return -1;
3248 }
3249 }
3250
3251 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3261 public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3262 {
3263 // phpcs:enable
3264 global $conf, $user, $hookmanager, $action;
3265
3266 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
3267 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3268 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as cd";
3269 $sql .= ", ".$this->db->prefix()."commande_fournisseur as c";
3270 $sql .= ", ".$this->db->prefix()."societe as s";
3271 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3272 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3273 }
3274 $sql .= " WHERE c.rowid = cd.fk_commande";
3275 $sql .= " AND c.fk_soc = s.rowid";
3276 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3277 $sql .= " AND cd.fk_product = ".((int) $this->id);
3278 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3279 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3280 }
3281 if ($socid > 0) {
3282 $sql .= " AND c.fk_soc = ".((int) $socid);
3283 }
3284 if ($filtrestatut != '') {
3285 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
3286 }
3287 if (!empty($dateofvirtualstock)) {
3288 $sql .= " AND c.date_livraison <= '".$this->db->idate($dateofvirtualstock)."'";
3289 }
3290
3291 $result = $this->db->query($sql);
3292 if ($result) {
3293 $obj = $this->db->fetch_object($result);
3294 $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
3295 $this->stats_commande_fournisseur['nb'] = $obj->nb;
3296 $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
3297 $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3298
3299 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3300 $reshook = $hookmanager->executeHooks('loadStatsSupplierOrder', $parameters, $this, $action);
3301 if ($reshook > 0) {
3302 $this->stats_commande_fournisseur = $hookmanager->resArray['stats_commande_fournisseur'];
3303 }
3304
3305 return 1;
3306 } else {
3307 $this->error = $this->db->error().' sql='.$sql;
3308 return -1;
3309 }
3310 }
3311
3312 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3322 public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $filterShipmentStatus = '')
3323 {
3324 // phpcs:enable
3325 global $conf, $user, $hookmanager, $action;
3326
3327 $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
3328 $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
3329 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
3330 $sql .= ", ".$this->db->prefix()."commandedet as cd";
3331 $sql .= ", ".$this->db->prefix()."commande as c";
3332 $sql .= ", ".$this->db->prefix()."expedition as e";
3333 $sql .= ", ".$this->db->prefix()."societe as s";
3334 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3335 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3336 }
3337 $sql .= " WHERE e.rowid = ed.fk_expedition";
3338 $sql .= " AND c.rowid = cd.fk_commande";
3339 $sql .= " AND e.fk_soc = s.rowid";
3340 $sql .= " AND e.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'expedition').")";
3341 $sql .= " AND ed.fk_origin_line = cd.rowid";
3342 $sql .= " AND cd.fk_product = ".((int) $this->id);
3343 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3344 $sql .= " AND e.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3345 }
3346 if ($socid > 0) {
3347 $sql .= " AND e.fk_soc = ".((int) $socid);
3348 }
3349 if ($filtrestatut != '') {
3350 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3351 }
3352 if (!empty($filterShipmentStatus)) {
3353 $sql .= " AND e.fk_statut IN (".$this->db->sanitize($filterShipmentStatus).")";
3354 }
3355
3356 $result = $this->db->query($sql);
3357 if ($result) {
3358 $obj = $this->db->fetch_object($result);
3359 $this->stats_expedition['customers'] = $obj->nb_customers;
3360 $this->stats_expedition['nb'] = $obj->nb;
3361 $this->stats_expedition['rows'] = $obj->nb_rows;
3362 $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
3363
3364 // if it's a virtual product, maybe it is in sending by extension
3365 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3366 $TFather = $this->getFather();
3367 if (is_array($TFather) && !empty($TFather)) {
3368 foreach ($TFather as &$fatherData) {
3369 $pFather = new Product($this->db);
3370 $pFather->id = $fatherData['id'];
3371 $qtyCoef = $fatherData['qty'];
3372
3373 if ($fatherData['incdec']) {
3374 $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
3375
3376 $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
3377 $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
3378 $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
3379 $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
3380 }
3381 }
3382 }
3383 }
3384
3385 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock, 'filterShipmentStatus' => $filterShipmentStatus);
3386 $reshook = $hookmanager->executeHooks('loadStatsSending', $parameters, $this, $action);
3387 if ($reshook > 0) {
3388 $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
3389 }
3390
3391 return 1;
3392 } else {
3393 $this->error = $this->db->error();
3394 return -1;
3395 }
3396 }
3397
3398 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3408 public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3409 {
3410 // phpcs:enable
3411 global $conf, $user, $hookmanager, $action;
3412
3413 $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_suppliers, COUNT(DISTINCT cf.rowid) as nb,";
3414 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3415 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur_dispatch as fd";
3416 $sql .= ", ".$this->db->prefix()."commande_fournisseur as cf";
3417 $sql .= ", ".$this->db->prefix()."societe as s";
3418 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3419 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3420 }
3421 $sql .= " WHERE cf.rowid = fd.fk_commande";
3422 $sql .= " AND cf.fk_soc = s.rowid";
3423 $sql .= " AND cf.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3424 $sql .= " AND fd.fk_product = ".((int) $this->id);
3425 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3426 $sql .= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3427 }
3428 if ($socid > 0) {
3429 $sql .= " AND cf.fk_soc = ".((int) $socid);
3430 }
3431 if ($filtrestatut != '') {
3432 $sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3433 }
3434 if (!empty($dateofvirtualstock)) {
3435 $sql .= " AND fd.datec <= '".$this->db->idate($dateofvirtualstock)."'";
3436 }
3437
3438 $result = $this->db->query($sql);
3439 if ($result) {
3440 $obj = $this->db->fetch_object($result);
3441 $this->stats_reception['suppliers'] = $obj->nb_suppliers;
3442 $this->stats_reception['nb'] = $obj->nb;
3443 $this->stats_reception['rows'] = $obj->nb_rows;
3444 $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
3445
3446 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3447 $reshook = $hookmanager->executeHooks('loadStatsReception', $parameters, $this, $action);
3448 if ($reshook > 0) {
3449 $this->stats_reception = $hookmanager->resArray['stats_reception'];
3450 }
3451
3452 return 1;
3453 } else {
3454 $this->error = $this->db->error();
3455 return -1;
3456 }
3457 }
3458
3459 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3469 public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3470 {
3471 // phpcs:enable
3472 global $conf, $user, $hookmanager, $action;
3473
3474 $serviceStockIsEnabled = isModEnabled("service") && getDolGlobalString('STOCK_SUPPORTS_SERVICES');
3475
3476 $sql = "SELECT COUNT(DISTINCT m.fk_soc) as nb_customers, COUNT(DISTINCT m.rowid) as nb,";
3477 $sql .= " COUNT(mp.rowid) as nb_rows, SUM(mp.qty) as qty, role";
3478 $sql .= " FROM ".$this->db->prefix()."mrp_production as mp";
3479 $sql .= ", ".$this->db->prefix()."mrp_mo as m";
3480 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON s.rowid = m.fk_soc";
3481 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3482 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3483 }
3484 $sql .= " WHERE m.rowid = mp.fk_mo";
3485 $sql .= " AND m.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'mrp').")";
3486 $sql .= " AND mp.fk_product = ".((int) $this->id);
3487 $sql .= " AND (mp.disable_stock_change IN (0) OR mp.disable_stock_change IS NULL)";
3488 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3489 $sql .= " AND m.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3490 }
3491 if ($socid > 0) {
3492 $sql .= " AND m.fk_soc = ".((int) $socid);
3493 }
3494 if ($filtrestatut != '') {
3495 $sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
3496 }
3497 if (!empty($dateofvirtualstock)) {
3498 $sql .= " AND m.date_valid <= '".$this->db->idate($dateofvirtualstock)."'"; // better date to code ? end of production ?
3499 }
3500 if (!$serviceStockIsEnabled) {
3501 $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))";
3502 }
3503 $sql .= " GROUP BY role";
3504
3505 $this->stats_mrptoconsume['customers'] = 0;
3506 $this->stats_mrptoconsume['nb'] = 0;
3507 $this->stats_mrptoconsume['rows'] = 0;
3508 $this->stats_mrptoconsume['qty'] = 0;
3509 $this->stats_mrptoproduce['customers'] = 0;
3510 $this->stats_mrptoproduce['nb'] = 0;
3511 $this->stats_mrptoproduce['rows'] = 0;
3512 $this->stats_mrptoproduce['qty'] = 0;
3513
3514 $result = $this->db->query($sql);
3515 if ($result) {
3516 while ($obj = $this->db->fetch_object($result)) {
3517 if ($obj->role == 'toconsume') {
3518 $this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3519 $this->stats_mrptoconsume['nb'] += $obj->nb;
3520 $this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3521 $this->stats_mrptoconsume['qty'] += ($obj->qty ? $obj->qty : 0);
3522 }
3523 if ($obj->role == 'consumed') {
3524 //$this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3525 //$this->stats_mrptoconsume['nb'] += $obj->nb;
3526 //$this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3527 $this->stats_mrptoconsume['qty'] -= ($obj->qty ? $obj->qty : 0);
3528 }
3529 if ($obj->role == 'toproduce') {
3530 $this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3531 $this->stats_mrptoproduce['nb'] += $obj->nb;
3532 $this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3533 $this->stats_mrptoproduce['qty'] += ($obj->qty ? $obj->qty : 0);
3534 }
3535 if ($obj->role == 'produced') {
3536 //$this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3537 //$this->stats_mrptoproduce['nb'] += $obj->nb;
3538 //$this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3539 $this->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3540 }
3541 }
3542
3543 // Clean data
3544 if ($this->stats_mrptoconsume['qty'] < 0) {
3545 $this->stats_mrptoconsume['qty'] = 0;
3546 }
3547 if ($this->stats_mrptoproduce['qty'] < 0) {
3548 $this->stats_mrptoproduce['qty'] = 0;
3549 }
3550
3551 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3552 $reshook = $hookmanager->executeHooks('loadStatsInProduction', $parameters, $this, $action);
3553 if ($reshook > 0) {
3554 $this->stats_mrptoproduce = $hookmanager->resArray['stats_mrptoproduce'];
3555 }
3556
3557 return 1;
3558 } else {
3559 $this->error = $this->db->error();
3560 return -1;
3561 }
3562 }
3563
3564 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3571 public function load_stats_contrat($socid = 0)
3572 {
3573 // phpcs:enable
3574 global $conf, $user, $hookmanager, $action;
3575
3576 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3577 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3578 $sql .= " FROM ".$this->db->prefix()."contratdet as cd";
3579 $sql .= ", ".$this->db->prefix()."contrat as c";
3580 $sql .= ", ".$this->db->prefix()."societe as s";
3581 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3582 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3583 }
3584 $sql .= " WHERE c.rowid = cd.fk_contrat";
3585 $sql .= " AND c.fk_soc = s.rowid";
3586 $sql .= " AND c.entity IN (".getEntity('contract').")";
3587 $sql .= " AND cd.fk_product = ".((int) $this->id);
3588 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3589 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3590 }
3591 //$sql.= " AND c.statut != 0";
3592 if ($socid > 0) {
3593 $sql .= " AND c.fk_soc = ".((int) $socid);
3594 }
3595
3596 $result = $this->db->query($sql);
3597 if ($result) {
3598 $obj = $this->db->fetch_object($result);
3599 $this->stats_contrat['customers'] = $obj->nb_customers;
3600 $this->stats_contrat['nb'] = $obj->nb;
3601 $this->stats_contrat['rows'] = $obj->nb_rows;
3602 $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
3603
3604 // if it's a virtual product, maybe it is in contract by extension
3605 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3606 $TFather = $this->getFather();
3607 if (is_array($TFather) && !empty($TFather)) {
3608 foreach ($TFather as &$fatherData) {
3609 $pFather = new Product($this->db);
3610 $pFather->id = $fatherData['id'];
3611 $qtyCoef = $fatherData['qty'];
3612
3613 if ($fatherData['incdec']) {
3614 $pFather->load_stats_contrat($socid);
3615
3616 $this->stats_contrat['customers'] += $pFather->stats_contrat['customers'];
3617 $this->stats_contrat['nb'] += $pFather->stats_contrat['nb'];
3618 $this->stats_contrat['rows'] += $pFather->stats_contrat['rows'];
3619 $this->stats_contrat['qty'] += $pFather->stats_contrat['qty'] * $qtyCoef;
3620 }
3621 }
3622 }
3623 }
3624
3625 $parameters = array('socid' => $socid);
3626 $reshook = $hookmanager->executeHooks('loadStatsContract', $parameters, $this, $action);
3627 if ($reshook > 0) {
3628 $this->stats_contrat = $hookmanager->resArray['stats_contrat'];
3629 }
3630
3631 return 1;
3632 } else {
3633 $this->error = $this->db->error().' sql='.$sql;
3634 return -1;
3635 }
3636 }
3637
3638 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3645 public function load_stats_facture($socid = 0)
3646 {
3647 // phpcs:enable
3648 global $conf, $user, $hookmanager, $action;
3649
3650 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3651 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(".$this->db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
3652 $sql .= " FROM ".$this->db->prefix()."facturedet as fd";
3653 $sql .= ", ".$this->db->prefix()."facture as f";
3654 $sql .= ", ".$this->db->prefix()."societe as s";
3655 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3656 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3657 }
3658 $sql .= " WHERE f.rowid = fd.fk_facture";
3659 $sql .= " AND f.fk_soc = s.rowid";
3660 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3661 $sql .= " AND fd.fk_product = ".((int) $this->id);
3662 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3663 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3664 }
3665 //$sql.= " AND f.fk_statut != 0";
3666 if ($socid > 0) {
3667 $sql .= " AND f.fk_soc = ".((int) $socid);
3668 }
3669
3670 $result = $this->db->query($sql);
3671 if ($result) {
3672 $obj = $this->db->fetch_object($result);
3673 $this->stats_facture['customers'] = $obj->nb_customers;
3674 $this->stats_facture['nb'] = $obj->nb;
3675 $this->stats_facture['rows'] = $obj->nb_rows;
3676 $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
3677
3678 // if it's a virtual product, maybe it is in invoice by extension
3679 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3680 $TFather = $this->getFather();
3681 if (is_array($TFather) && !empty($TFather)) {
3682 foreach ($TFather as &$fatherData) {
3683 $pFather = new Product($this->db);
3684 $pFather->id = $fatherData['id'];
3685 $qtyCoef = $fatherData['qty'];
3686
3687 if ($fatherData['incdec']) {
3688 $pFather->load_stats_facture($socid);
3689
3690 $this->stats_facture['customers'] += $pFather->stats_facture['customers'];
3691 $this->stats_facture['nb'] += $pFather->stats_facture['nb'];
3692 $this->stats_facture['rows'] += $pFather->stats_facture['rows'];
3693 $this->stats_facture['qty'] += $pFather->stats_facture['qty'] * $qtyCoef;
3694 }
3695 }
3696 }
3697 }
3698
3699 $parameters = array('socid' => $socid);
3700 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoice', $parameters, $this, $action);
3701 if ($reshook > 0) {
3702 $this->stats_facture = $hookmanager->resArray['stats_facture'];
3703 }
3704
3705 return 1;
3706 } else {
3707 $this->error = $this->db->error();
3708 return -1;
3709 }
3710 }
3711
3712
3713 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3720 public function load_stats_facturerec($socid = 0)
3721 {
3722 // phpcs:enable
3723 global $conf, $user, $hookmanager, $action;
3724
3725 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3726 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3727 $sql .= " FROM ".MAIN_DB_PREFIX."facturedet_rec as fd";
3728 $sql .= ", ".MAIN_DB_PREFIX."facture_rec as f";
3729 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
3730 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3731 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3732 }
3733 $sql .= " WHERE f.rowid = fd.fk_facture";
3734 $sql .= " AND f.fk_soc = s.rowid";
3735 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3736 $sql .= " AND fd.fk_product = ".((int) $this->id);
3737 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3738 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3739 }
3740 //$sql.= " AND f.fk_statut != 0";
3741 if ($socid > 0) {
3742 $sql .= " AND f.fk_soc = ".((int) $socid);
3743 }
3744
3745 $result = $this->db->query($sql);
3746 if ($result) {
3747 $obj = $this->db->fetch_object($result);
3748 $this->stats_facturerec['customers'] = $obj->nb_customers;
3749 $this->stats_facturerec['nb'] = $obj->nb;
3750 $this->stats_facturerec['rows'] = $obj->nb_rows;
3751 $this->stats_facturerec['qty'] = $obj->qty ? $obj->qty : 0;
3752
3753 // if it's a virtual product, maybe it is in invoice by extension
3754 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3755 $TFather = $this->getFather();
3756 if (is_array($TFather) && !empty($TFather)) {
3757 foreach ($TFather as &$fatherData) {
3758 $pFather = new Product($this->db);
3759 $pFather->id = $fatherData['id'];
3760 $qtyCoef = $fatherData['qty'];
3761
3762 if ($fatherData['incdec']) {
3763 $pFather->load_stats_facture($socid);
3764
3765 $this->stats_facturerec['customers'] += $pFather->stats_facturerec['customers'];
3766 $this->stats_facturerec['nb'] += $pFather->stats_facturerec['nb'];
3767 $this->stats_facturerec['rows'] += $pFather->stats_facturerec['rows'];
3768 $this->stats_facturerec['qty'] += $pFather->stats_facturerec['qty'] * $qtyCoef;
3769 }
3770 }
3771 }
3772 }
3773
3774 $parameters = array('socid' => $socid);
3775 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoiceRec', $parameters, $this, $action);
3776 if ($reshook > 0) {
3777 $this->stats_facturerec = $hookmanager->resArray['stats_facturerec'];
3778 }
3779
3780 return 1;
3781 } else {
3782 $this->error = $this->db->error();
3783 return -1;
3784 }
3785 }
3786
3787 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3794 public function load_stats_facture_fournisseur($socid = 0)
3795 {
3796 // phpcs:enable
3797 global $conf, $user, $hookmanager, $action;
3798
3799 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
3800 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3801 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as fd";
3802 $sql .= ", ".$this->db->prefix()."facture_fourn as f";
3803 $sql .= ", ".$this->db->prefix()."societe as s";
3804 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3805 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3806 }
3807 $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
3808 $sql .= " AND f.fk_soc = s.rowid";
3809 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
3810 $sql .= " AND fd.fk_product = ".((int) $this->id);
3811 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3812 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3813 }
3814 //$sql.= " AND f.fk_statut != 0";
3815 if ($socid > 0) {
3816 $sql .= " AND f.fk_soc = ".((int) $socid);
3817 }
3818
3819 $result = $this->db->query($sql);
3820 if ($result) {
3821 $obj = $this->db->fetch_object($result);
3822 $this->stats_facture_fournisseur['suppliers'] = $obj->nb_suppliers;
3823 $this->stats_facture_fournisseur['nb'] = $obj->nb;
3824 $this->stats_facture_fournisseur['rows'] = $obj->nb_rows;
3825 $this->stats_facture_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3826
3827 $parameters = array('socid' => $socid);
3828 $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoice', $parameters, $this, $action);
3829 if ($reshook > 0) {
3830 $this->stats_facture_fournisseur = $hookmanager->resArray['stats_facture_fournisseur'];
3831 }
3832
3833 return 1;
3834 } else {
3835 $this->error = $this->db->error();
3836 return -1;
3837 }
3838 }
3839
3840 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3849 private function _get_stats($sql, $mode, $year = 0)
3850 {
3851 // phpcs:enable
3852 $tab = array();
3853
3854 $resql = $this->db->query($sql);
3855 if ($resql) {
3856 $num = $this->db->num_rows($resql);
3857 $i = 0;
3858 while ($i < $num) {
3859 $arr = $this->db->fetch_array($resql);
3860 $keyfortab = (string) $arr[1];
3861 if ($year == -1) {
3862 $keyfortab = substr($keyfortab, -2);
3863 }
3864
3865 if ($mode == 'byunit') {
3866 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[0]; // 1st field
3867 } elseif ($mode == 'bynumber') {
3868 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3869 } elseif ($mode == 'byamount') {
3870 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3871 } else {
3872 // Bad value for $mode
3873 return -1;
3874 }
3875 $i++;
3876 }
3877 } else {
3878 $this->error = $this->db->error().' sql='.$sql;
3879 return -1;
3880 }
3881
3882 if (empty($year)) {
3883 $year = strftime('%Y', time());
3884 $month = strftime('%m', time());
3885 } elseif ($year == -1) {
3886 $year = '';
3887 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3888 } else {
3889 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3890 }
3891
3892 $result = array();
3893
3894 for ($j = 0; $j < 12; $j++) {
3895 // $ids is 'D', 'N', 'O', 'S', ... (First letter of month in user language)
3896 $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, 1970), "%b"), 1, 'right', 'UTF-8', 1));
3897
3898 //print $idx.'-'.$year.'-'.$month.'<br>';
3899 $result[$j] = array($idx, isset($tab[$year.$month]) ? $tab[$year.$month] : 0);
3900 // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
3901
3902 $month = "0".($month - 1);
3903 if (dol_strlen($month) == 3) {
3904 $month = substr($month, 1);
3905 }
3906 if ($month == 0) {
3907 $month = 12;
3908 $year = $year - 1;
3909 }
3910 }
3911
3912 return array_reverse($result);
3913 }
3914
3915
3916 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3927 public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3928 {
3929 // phpcs:enable
3930 global $conf;
3931 global $user;
3932
3933 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
3934 if ($mode == 'bynumber') {
3935 $sql .= ", count(DISTINCT f.rowid)";
3936 }
3937 $sql .= ", sum(d.total_ht) as total_ht";
3938 $sql .= " FROM ".$this->db->prefix()."facturedet as d, ".$this->db->prefix()."facture as f, ".$this->db->prefix()."societe as s";
3939 if ($filteronproducttype >= 0) {
3940 $sql .= ", ".$this->db->prefix()."product as p";
3941 }
3942 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3943 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3944 }
3945 $sql .= " WHERE f.rowid = d.fk_facture";
3946 if ($this->id > 0) {
3947 $sql .= " AND d.fk_product = ".((int) $this->id);
3948 } else {
3949 $sql .= " AND d.fk_product > 0";
3950 }
3951 if ($filteronproducttype >= 0) {
3952 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
3953 }
3954 $sql .= " AND f.fk_soc = s.rowid";
3955 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3956 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3957 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3958 }
3959 if ($socid > 0) {
3960 $sql .= " AND f.fk_soc = $socid";
3961 }
3962 $sql .= $morefilter;
3963 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
3964 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
3965
3966 return $this->_get_stats($sql, $mode, $year);
3967 }
3968
3969
3970 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3981 public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3982 {
3983 // phpcs:enable
3984 global $conf;
3985 global $user;
3986
3987 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
3988 if ($mode == 'bynumber') {
3989 $sql .= ", count(DISTINCT f.rowid)";
3990 }
3991 $sql .= ", sum(d.total_ht) as total_ht";
3992 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as d, ".$this->db->prefix()."facture_fourn as f, ".$this->db->prefix()."societe as s";
3993 if ($filteronproducttype >= 0) {
3994 $sql .= ", ".$this->db->prefix()."product as p";
3995 }
3996 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3997 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3998 }
3999 $sql .= " WHERE f.rowid = d.fk_facture_fourn";
4000 if ($this->id > 0) {
4001 $sql .= " AND d.fk_product = ".((int) $this->id);
4002 } else {
4003 $sql .= " AND d.fk_product > 0";
4004 }
4005 if ($filteronproducttype >= 0) {
4006 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4007 }
4008 $sql .= " AND f.fk_soc = s.rowid";
4009 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4010 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4011 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4012 }
4013 if ($socid > 0) {
4014 $sql .= " AND f.fk_soc = $socid";
4015 }
4016 $sql .= $morefilter;
4017 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4018 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4019
4020 return $this->_get_stats($sql, $mode, $year);
4021 }
4022
4023 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4034 public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4035 {
4036 // phpcs:enable
4037 global $conf, $user;
4038
4039 $sql = "SELECT sum(d.qty) as qty, date_format(p.datep, '%Y%m')";
4040 if ($mode == 'bynumber') {
4041 $sql .= ", count(DISTINCT p.rowid)";
4042 }
4043 $sql .= ", sum(d.total_ht) as total_ht";
4044 $sql .= " FROM ".$this->db->prefix()."propaldet as d, ".$this->db->prefix()."propal as p, ".$this->db->prefix()."societe as s";
4045 if ($filteronproducttype >= 0) {
4046 $sql .= ", ".$this->db->prefix()."product as prod";
4047 }
4048 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4049 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4050 }
4051 $sql .= " WHERE p.rowid = d.fk_propal";
4052 if ($this->id > 0) {
4053 $sql .= " AND d.fk_product = ".((int) $this->id);
4054 } else {
4055 $sql .= " AND d.fk_product > 0";
4056 }
4057 if ($filteronproducttype >= 0) {
4058 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4059 }
4060 $sql .= " AND p.fk_soc = s.rowid";
4061 $sql .= " AND p.entity IN (".getEntity('propal').")";
4062 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4063 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4064 }
4065 if ($socid > 0) {
4066 $sql .= " AND p.fk_soc = ".((int) $socid);
4067 }
4068 $sql .= $morefilter;
4069 $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
4070 $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
4071
4072 return $this->_get_stats($sql, $mode, $year);
4073 }
4074
4075 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4086 public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4087 {
4088 // phpcs:enable
4089 global $conf;
4090 global $user;
4091
4092 $sql = "SELECT sum(d.qty) as qty, date_format(p.date_valid, '%Y%m')";
4093 if ($mode == 'bynumber') {
4094 $sql .= ", count(DISTINCT p.rowid)";
4095 }
4096 $sql .= ", sum(d.total_ht) as total_ht";
4097 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as d, ".$this->db->prefix()."supplier_proposal as p, ".$this->db->prefix()."societe as s";
4098 if ($filteronproducttype >= 0) {
4099 $sql .= ", ".$this->db->prefix()."product as prod";
4100 }
4101 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4102 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4103 }
4104 $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
4105 if ($this->id > 0) {
4106 $sql .= " AND d.fk_product = ".((int) $this->id);
4107 } else {
4108 $sql .= " AND d.fk_product > 0";
4109 }
4110 if ($filteronproducttype >= 0) {
4111 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4112 }
4113 $sql .= " AND p.fk_soc = s.rowid";
4114 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
4115 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4116 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4117 }
4118 if ($socid > 0) {
4119 $sql .= " AND p.fk_soc = ".((int) $socid);
4120 }
4121 $sql .= $morefilter;
4122 $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
4123 $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
4124
4125 return $this->_get_stats($sql, $mode, $year);
4126 }
4127
4128 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4139 public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4140 {
4141 // phpcs:enable
4142 global $conf, $user;
4143
4144 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4145 if ($mode == 'bynumber') {
4146 $sql .= ", count(DISTINCT c.rowid)";
4147 }
4148 $sql .= ", sum(d.total_ht) as total_ht";
4149 $sql .= " FROM ".$this->db->prefix()."commandedet as d, ".$this->db->prefix()."commande as c, ".$this->db->prefix()."societe as s";
4150 if ($filteronproducttype >= 0) {
4151 $sql .= ", ".$this->db->prefix()."product as p";
4152 }
4153 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4154 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4155 }
4156 $sql .= " WHERE c.rowid = d.fk_commande";
4157 if ($this->id > 0) {
4158 $sql .= " AND d.fk_product = ".((int) $this->id);
4159 } else {
4160 $sql .= " AND d.fk_product > 0";
4161 }
4162 if ($filteronproducttype >= 0) {
4163 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4164 }
4165 $sql .= " AND c.fk_soc = s.rowid";
4166 $sql .= " AND c.entity IN (".getEntity('commande').")";
4167 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4168 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4169 }
4170 if ($socid > 0) {
4171 $sql .= " AND c.fk_soc = ".((int) $socid);
4172 }
4173 $sql .= $morefilter;
4174 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4175 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4176
4177 return $this->_get_stats($sql, $mode, $year);
4178 }
4179
4180 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4191 public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4192 {
4193 // phpcs:enable
4194 global $conf, $user;
4195
4196 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4197 if ($mode == 'bynumber') {
4198 $sql .= ", count(DISTINCT c.rowid)";
4199 }
4200 $sql .= ", sum(d.total_ht) as total_ht";
4201 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as d, ".$this->db->prefix()."commande_fournisseur as c, ".$this->db->prefix()."societe as s";
4202 if ($filteronproducttype >= 0) {
4203 $sql .= ", ".$this->db->prefix()."product as p";
4204 }
4205 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4206 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4207 }
4208 $sql .= " WHERE c.rowid = d.fk_commande";
4209 if ($this->id > 0) {
4210 $sql .= " AND d.fk_product = ".((int) $this->id);
4211 } else {
4212 $sql .= " AND d.fk_product > 0";
4213 }
4214 if ($filteronproducttype >= 0) {
4215 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4216 }
4217 $sql .= " AND c.fk_soc = s.rowid";
4218 $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
4219 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4220 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4221 }
4222 if ($socid > 0) {
4223 $sql .= " AND c.fk_soc = ".((int) $socid);
4224 }
4225 $sql .= $morefilter;
4226 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4227 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4228
4229 return $this->_get_stats($sql, $mode, $year);
4230 }
4231
4232 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4243 public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4244 {
4245 // phpcs:enable
4246 global $conf, $user;
4247
4248 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_contrat, '%Y%m')";
4249 if ($mode == 'bynumber') {
4250 $sql .= ", count(DISTINCT c.rowid)";
4251 }
4252 $sql .= ", sum(d.total_ht) as total_ht";
4253 $sql .= " FROM ".$this->db->prefix()."contratdet as d, ".$this->db->prefix()."contrat as c, ".$this->db->prefix()."societe as s";
4254 if ($filteronproducttype >= 0) {
4255 $sql .= ", ".$this->db->prefix()."product as p";
4256 }
4257 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4258 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4259 }
4260
4261 $sql .= " WHERE c.entity IN (".getEntity('contract').")";
4262 $sql .= " AND c.rowid = d.fk_contrat";
4263
4264 if ($this->id > 0) {
4265 $sql .= " AND d.fk_product = ".((int) $this->id);
4266 } else {
4267 $sql .= " AND d.fk_product > 0";
4268 }
4269 if ($filteronproducttype >= 0) {
4270 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4271 }
4272 $sql .= " AND c.fk_soc = s.rowid";
4273
4274 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4275 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4276 }
4277 if ($socid > 0) {
4278 $sql .= " AND c.fk_soc = ".((int) $socid);
4279 }
4280 $sql .= $morefilter;
4281 $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
4282 $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
4283
4284 return $this->_get_stats($sql, $mode, $year);
4285 }
4286
4287 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4298 public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4299 {
4300 // phpcs:enable
4301 global $conf, $user;
4302
4303 $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
4304 if ($mode == 'bynumber') {
4305 $sql .= ", count(DISTINCT d.rowid)";
4306 }
4307 $sql .= " FROM ".$this->db->prefix()."mrp_mo as d LEFT JOIN ".$this->db->prefix()."societe as s ON d.fk_soc = s.rowid";
4308 if ($filteronproducttype >= 0) {
4309 $sql .= ", ".$this->db->prefix()."product as p";
4310 }
4311 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4312 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4313 }
4314
4315 $sql .= " WHERE d.entity IN (".getEntity('mo').")";
4316 $sql .= " AND d.status > 0";
4317
4318 if ($this->id > 0) {
4319 $sql .= " AND d.fk_product = ".((int) $this->id);
4320 } else {
4321 $sql .= " AND d.fk_product > 0";
4322 }
4323 if ($filteronproducttype >= 0) {
4324 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4325 }
4326
4327 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4328 $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4329 }
4330 if ($socid > 0) {
4331 $sql .= " AND d.fk_soc = ".((int) $socid);
4332 }
4333 $sql .= $morefilter;
4334 $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
4335 $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
4336
4337 return $this->_get_stats($sql, $mode, $year);
4338 }
4339
4340 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4351 public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4352 {
4353 global $user;
4354
4355 // phpcs:enable
4356 // Clean parameters
4357 if (!is_numeric($id_pere)) {
4358 $id_pere = 0;
4359 }
4360 if (!is_numeric($id_fils)) {
4361 $id_fils = 0;
4362 }
4363 if (!is_numeric($incdec)) {
4364 $incdec = 0;
4365 }
4366
4367 $result = $this->del_sousproduit($id_pere, $id_fils);
4368 if ($result < 0) {
4369 return $result;
4370 }
4371
4372 // Check not already father of id_pere (to avoid father -> child -> father links)
4373 $sql = "SELECT fk_product_pere from ".$this->db->prefix()."product_association";
4374 $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere);
4375 if (!$this->db->query($sql)) {
4376 dol_print_error($this->db);
4377 return -1;
4378 } else {
4379 //Selection of the highest row
4380 $sql = "SELECT MAX(rang) as max_rank FROM ".$this->db->prefix()."product_association";
4381 $sql .= " WHERE fk_product_pere = ".((int) $id_pere);
4382 $resql = $this->db->query($sql);
4383 if ($resql) {
4384 $obj = $this->db->fetch_object($resql);
4385 $rank = $obj->max_rank + 1;
4386 //Addition of a product with the highest rank +1
4387 $sql = "INSERT INTO ".$this->db->prefix()."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)";
4388 $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".price2num($qty, 'MS').", ".price2num($incdec, 'MS').", ".((int) $rank).")";
4389 if (! $this->db->query($sql)) {
4390 dol_print_error($this->db);
4391 return -1;
4392 } else {
4393 if (!$notrigger) {
4394 // Call trigger
4395 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_ADD', $user);
4396 if ($result < 0) {
4397 $this->error = $this->db->lasterror();
4398 dol_syslog(get_class($this).'::addSubproduct error='.$this->error, LOG_ERR);
4399 return -1;
4400 }
4401 }
4402 // End call triggers
4403
4404 return 1;
4405 }
4406 } else {
4407 dol_print_error($this->db);
4408 return -1;
4409 }
4410 }
4411 }
4412
4413 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4424 public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4425 {
4426 global $user;
4427
4428 // phpcs:enable
4429 // Clean parameters
4430 if (!is_numeric($id_pere)) {
4431 $id_pere = 0;
4432 }
4433 if (!is_numeric($id_fils)) {
4434 $id_fils = 0;
4435 }
4436 if (!is_numeric($incdec)) {
4437 $incdec = 1;
4438 }
4439 if (!is_numeric($qty)) {
4440 $qty = 1;
4441 }
4442
4443 $sql = 'UPDATE '.$this->db->prefix().'product_association SET ';
4444 $sql .= 'qty = '.price2num($qty, 'MS');
4445 $sql .= ',incdec = '.price2num($incdec, 'MS');
4446 $sql .= ' WHERE fk_product_pere = '.((int) $id_pere).' AND fk_product_fils = '.((int) $id_fils);
4447
4448 if (!$this->db->query($sql)) {
4449 dol_print_error($this->db);
4450 return -1;
4451 } else {
4452 if (!$notrigger) {
4453 // Call trigger
4454 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_UPDATE', $user);
4455 if ($result < 0) {
4456 $this->error = $this->db->lasterror();
4457 dol_syslog(get_class($this).'::updateSubproduct error='.$this->error, LOG_ERR);
4458 return -1;
4459 }
4460 // End call triggers
4461 }
4462
4463 return 1;
4464 }
4465 }
4466
4467 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4476 public function del_sousproduit($fk_parent, $fk_child, $notrigger = 0)
4477 {
4478 global $user;
4479
4480 // phpcs:enable
4481 if (!is_numeric($fk_parent)) {
4482 $fk_parent = 0;
4483 }
4484 if (!is_numeric($fk_child)) {
4485 $fk_child = 0;
4486 }
4487
4488 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
4489 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4490 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4491
4492 dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
4493 if (!$this->db->query($sql)) {
4494 dol_print_error($this->db);
4495 return -1;
4496 }
4497
4498 // Updated ranks so that none are missing
4499 $sqlrank = "SELECT rowid, rang FROM ".$this->db->prefix()."product_association";
4500 $sqlrank.= " WHERE fk_product_pere = ".((int) $fk_parent);
4501 $sqlrank.= " ORDER BY rang";
4502 $resqlrank = $this->db->query($sqlrank);
4503 if ($resqlrank) {
4504 $cpt = 0;
4505 while ($objrank = $this->db->fetch_object($resqlrank)) {
4506 $cpt++;
4507 $sql = "UPDATE ".$this->db->prefix()."product_association";
4508 $sql.= " SET rang = ".((int) $cpt);
4509 $sql.= " WHERE rowid = ".((int) $objrank->rowid);
4510 if (! $this->db->query($sql)) {
4511 dol_print_error($this->db);
4512 return -1;
4513 }
4514 }
4515 }
4516
4517 if (!$notrigger) {
4518 // Call trigger
4519 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_DELETE', $user);
4520 if ($result < 0) {
4521 $this->error = $this->db->lasterror();
4522 dol_syslog(get_class($this).'::delSubproduct error='.$this->error, LOG_ERR);
4523 return -1;
4524 }
4525 // End call triggers
4526 }
4527
4528 return 1;
4529 }
4530
4531 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4539 public function is_sousproduit($fk_parent, $fk_child)
4540 {
4541 // phpcs:enable
4542 $sql = "SELECT fk_product_pere, qty, incdec";
4543 $sql .= " FROM ".$this->db->prefix()."product_association";
4544 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4545 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4546
4547 $result = $this->db->query($sql);
4548 if ($result) {
4549 $num = $this->db->num_rows($result);
4550
4551 if ($num > 0) {
4552 $obj = $this->db->fetch_object($result);
4553
4554 $this->is_sousproduit_qty = $obj->qty;
4555 $this->is_sousproduit_incdec = $obj->incdec;
4556
4557 return true;
4558 } else {
4559 return false;
4560 }
4561 } else {
4562 dol_print_error($this->db);
4563 return -1;
4564 }
4565 }
4566
4567
4568 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4579 public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
4580 {
4581 // phpcs:enable
4582 global $conf;
4583
4584 $now = dol_now();
4585
4586 dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
4587
4588 // Clean parameters
4589 $quantity = price2num($quantity, 'MS');
4590
4591 if ($ref_fourn) {
4592 $sql = "SELECT rowid, fk_product";
4593 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4594 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4595 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4596 $sql .= " AND fk_product <> ".((int) $this->id);
4597 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4598
4599 $resql = $this->db->query($sql);
4600 if ($resql) {
4601 $obj = $this->db->fetch_object($resql);
4602 if ($obj) {
4603 // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
4604 $this->product_id_already_linked = $obj->fk_product;
4605 return -3;
4606 }
4607 $this->db->free($resql);
4608 }
4609 }
4610
4611 $sql = "SELECT rowid";
4612 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4613 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4614 if ($ref_fourn) {
4615 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4616 } else {
4617 $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
4618 }
4619 $sql .= " AND quantity = ".((float) $quantity);
4620 $sql .= " AND fk_product = ".((int) $this->id);
4621 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4622
4623 $resql = $this->db->query($sql);
4624 if ($resql) {
4625 $obj = $this->db->fetch_object($resql);
4626
4627 // The reference supplier does not exist, we create it for this product.
4628 if (empty($obj)) {
4629 $sql = "INSERT INTO ".$this->db->prefix()."product_fournisseur_price(";
4630 $sql .= "datec";
4631 $sql .= ", entity";
4632 $sql .= ", fk_product";
4633 $sql .= ", fk_soc";
4634 $sql .= ", ref_fourn";
4635 $sql .= ", quantity";
4636 $sql .= ", fk_user";
4637 $sql .= ", tva_tx";
4638 $sql .= ") VALUES (";
4639 $sql .= "'".$this->db->idate($now)."'";
4640 $sql .= ", ".$conf->entity;
4641 $sql .= ", ".$this->id;
4642 $sql .= ", ".$id_fourn;
4643 $sql .= ", '".$this->db->escape($ref_fourn)."'";
4644 $sql .= ", ".$quantity;
4645 $sql .= ", ".$user->id;
4646 $sql .= ", 0";
4647 $sql .= ")";
4648
4649 if ($this->db->query($sql)) {
4650 $this->product_fourn_price_id = $this->db->last_insert_id($this->db->prefix()."product_fournisseur_price");
4651 return 1;
4652 } else {
4653 $this->error = $this->db->lasterror();
4654 return -1;
4655 }
4656 } else {
4657 // If the supplier price already exists for this product and quantity
4658 $this->product_fourn_price_id = $obj->rowid;
4659 return 0;
4660 }
4661 } else {
4662 $this->error = $this->db->lasterror();
4663 return -2;
4664 }
4665 }
4666
4667
4668 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4674 public function list_suppliers()
4675 {
4676 // phpcs:enable
4677 global $conf;
4678
4679 $list = array();
4680
4681 $sql = "SELECT DISTINCT p.fk_soc";
4682 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as p";
4683 $sql .= " WHERE p.fk_product = ".((int) $this->id);
4684 $sql .= " AND p.entity = ".((int) $conf->entity);
4685
4686 $result = $this->db->query($sql);
4687 if ($result) {
4688 $num = $this->db->num_rows($result);
4689 $i = 0;
4690 while ($i < $num) {
4691 $obj = $this->db->fetch_object($result);
4692 $list[$i] = $obj->fk_soc;
4693 $i++;
4694 }
4695 }
4696
4697 return $list;
4698 }
4699
4700 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4708 public function clone_price($fromId, $toId)
4709 {
4710 global $conf, $user;
4711
4712 $now = dol_now();
4713
4714 $this->db->begin();
4715
4716 // prices
4717 $sql = "INSERT INTO ".$this->db->prefix()."product_price (";
4718 $sql .= " entity";
4719 $sql .= ", fk_product";
4720 $sql .= ", date_price";
4721 $sql .= ", price_level";
4722 $sql .= ", price";
4723 $sql .= ", price_ttc";
4724 $sql .= ", price_min";
4725 $sql .= ", price_min_ttc";
4726 $sql .= ", price_base_type";
4727 $sql .= ", default_vat_code";
4728 $sql .= ", tva_tx";
4729 $sql .= ", recuperableonly";
4730 $sql .= ", localtax1_tx";
4731 $sql .= ", localtax1_type";
4732 $sql .= ", localtax2_tx";
4733 $sql .= ", localtax2_type";
4734 $sql .= ", fk_user_author";
4735 $sql .= ", tosell";
4736 $sql .= ", price_by_qty";
4737 $sql .= ", fk_price_expression";
4738 $sql .= ", fk_multicurrency";
4739 $sql .= ", multicurrency_code";
4740 $sql .= ", multicurrency_tx";
4741 $sql .= ", multicurrency_price";
4742 $sql .= ", multicurrency_price_ttc";
4743 $sql .= ")";
4744 $sql .= " SELECT";
4745 $sql .= " entity";
4746 $sql .= ", ".$toId;
4747 $sql .= ", '".$this->db->idate($now)."'";
4748 $sql .= ", price_level";
4749 $sql .= ", price";
4750 $sql .= ", price_ttc";
4751 $sql .= ", price_min";
4752 $sql .= ", price_min_ttc";
4753 $sql .= ", price_base_type";
4754 $sql .= ", default_vat_code";
4755 $sql .= ", tva_tx";
4756 $sql .= ", recuperableonly";
4757 $sql .= ", localtax1_tx";
4758 $sql .= ", localtax1_type";
4759 $sql .= ", localtax2_tx";
4760 $sql .= ", localtax2_type";
4761 $sql .= ", ".$user->id;
4762 $sql .= ", tosell";
4763 $sql .= ", price_by_qty";
4764 $sql .= ", fk_price_expression";
4765 $sql .= ", fk_multicurrency";
4766 $sql .= ", multicurrency_code";
4767 $sql .= ", multicurrency_tx";
4768 $sql .= ", multicurrency_price";
4769 $sql .= ", multicurrency_price_ttc";
4770 $sql .= " FROM ".$this->db->prefix()."product_price ps";
4771 $sql .= " WHERE fk_product = ".((int) $fromId);
4772 $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)";
4773 $sql .= " ORDER BY date_price DESC";
4774
4775 dol_syslog(__METHOD__, LOG_DEBUG);
4776 $resql = $this->db->query($sql);
4777 if (!$resql) {
4778 $this->db->rollback();
4779 return -1;
4780 }
4781
4782 $this->db->commit();
4783 return 1;
4784 }
4785
4786 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4794 public function clone_associations($fromId, $toId)
4795 {
4796 // phpcs:enable
4797 $this->db->begin();
4798
4799 $sql = 'INSERT INTO '.$this->db->prefix().'product_association (fk_product_pere, fk_product_fils, qty, incdec)';
4800 $sql .= " SELECT ".$toId.", fk_product_fils, qty, incdec FROM ".$this->db->prefix()."product_association";
4801 $sql .= " WHERE fk_product_pere = ".((int) $fromId);
4802
4803 dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
4804 if (!$this->db->query($sql)) {
4805 $this->db->rollback();
4806 return -1;
4807 }
4808
4809 $this->db->commit();
4810 return 1;
4811 }
4812
4813 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4821 public function clone_fournisseurs($fromId, $toId)
4822 {
4823 // phpcs:enable
4824 $this->db->begin();
4825
4826 $now = dol_now();
4827
4828 // les fournisseurs
4829 /*$sql = "INSERT ".$this->db->prefix()."product_fournisseur ("
4830 . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
4831 . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
4832 . " FROM ".$this->db->prefix()."product_fournisseur"
4833 . " WHERE fk_product = ".((int) $fromId);
4834
4835 if ( ! $this->db->query($sql ) )
4836 {
4837 $this->db->rollback();
4838 return -1;
4839 }*/
4840
4841 // les prix de fournisseurs.
4842 $sql = "INSERT ".$this->db->prefix()."product_fournisseur_price (";
4843 $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user, tva_tx)";
4844 $sql .= " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, price, quantity, fk_user, tva_tx";
4845 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4846 $sql .= " WHERE fk_product = ".((int) $fromId);
4847
4848 dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
4849 $resql = $this->db->query($sql);
4850 if (!$resql) {
4851 $this->db->rollback();
4852 return -1;
4853 } else {
4854 $this->db->commit();
4855 return 1;
4856 }
4857 }
4858
4859 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4872 public function fetch_prod_arbo($prod, $compl_path = '', $multiply = 1, $level = 1, $id_parent = 0, $ignore_stock_load = 0)
4873 {
4874 // phpcs:enable
4875 global $conf, $langs;
4876
4877 $tmpproduct = null;
4878 //var_dump($prod);
4879 foreach ($prod as $id_product => $desc_pere) { // $id_product is 0 (first call starting with root top) or an id of a sub_product
4880 if (is_array($desc_pere)) { // If desc_pere is an array, this means it's a child
4881 $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
4882 $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
4883 $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
4884 $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
4885 $incdec = (!empty($desc_pere[4]) ? $desc_pere[4] : 0);
4886
4887 if ($multiply < 1) {
4888 $multiply = 1;
4889 }
4890
4891 //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
4892 if (is_null($tmpproduct)) {
4893 $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
4894 }
4895 $tmpproduct->fetch($id); // Load product to get ->ref
4896
4897 if (empty($ignore_stock_load) && ($tmpproduct->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
4898 $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
4899 }
4900
4901 $this->res[] = array(
4902 'id'=>$id, // Id product
4903 'id_parent'=>$id_parent,
4904 'ref'=>$tmpproduct->ref, // Ref product
4905 'nb'=>$nb, // Nb of units that compose parent product
4906 'nb_total'=>$nb * $multiply, // Nb of units for all nb of product
4907 'stock'=>$tmpproduct->stock_reel, // Stock
4908 'stock_alert'=>$tmpproduct->seuil_stock_alerte, // Stock alert
4909 'label'=>$label,
4910 'fullpath'=>$compl_path.$label, // Label
4911 'type'=>$type, // Nb of units that compose parent product
4912 'desiredstock'=>$tmpproduct->desiredstock,
4913 'level'=>$level,
4914 'incdec'=>$incdec,
4915 'entity'=>$tmpproduct->entity
4916 );
4917
4918 // Recursive call if there is childs to child
4919 if (isset($desc_pere['childs']) && is_array($desc_pere['childs'])) {
4920 //print 'YYY We go down for '.$desc_pere[3]." -> \n";
4921 $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1] * $multiply, $level + 1, $id, $ignore_stock_load);
4922 }
4923 }
4924 }
4925 }
4926
4927 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4936 public function get_arbo_each_prod($multiply = 1, $ignore_stock_load = 0)
4937 {
4938 // phpcs:enable
4939 $this->res = array();
4940 if (isset($this->sousprods) && is_array($this->sousprods)) {
4941 foreach ($this->sousprods as $prod_name => $desc_product) {
4942 if (is_array($desc_product)) {
4943 $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id, $ignore_stock_load); // This set $this->res
4944 }
4945 }
4946 }
4947 //var_dump($res);
4948 return $this->res;
4949 }
4950
4958 public function hasFatherOrChild($mode = 0)
4959 {
4960 $nb = 0;
4961
4962 $sql = "SELECT COUNT(pa.rowid) as nb";
4963 $sql .= " FROM ".$this->db->prefix()."product_association as pa";
4964 if ($mode == 0) {
4965 $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id)." OR pa.fk_product_pere = ".((int) $this->id);
4966 } elseif ($mode == -1) {
4967 $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)
4968 } elseif ($mode == 1) {
4969 $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)
4970 }
4971
4972 $resql = $this->db->query($sql);
4973 if ($resql) {
4974 $obj = $this->db->fetch_object($resql);
4975 if ($obj) {
4976 $nb = $obj->nb;
4977 }
4978 } else {
4979 return -1;
4980 }
4981
4982 return $nb;
4983 }
4984
4990 public function hasVariants()
4991 {
4992 $nb = 0;
4993 $sql = "SELECT count(rowid) as nb FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_parent = ".((int) $this->id);
4994 $sql .= " AND entity IN (".getEntity('product').")";
4995
4996 $resql = $this->db->query($sql);
4997 if ($resql) {
4998 $obj = $this->db->fetch_object($resql);
4999 if ($obj) {
5000 $nb = $obj->nb;
5001 }
5002 }
5003
5004 return $nb;
5005 }
5006
5007
5013 public function isVariant()
5014 {
5015 global $conf;
5016 if (isModEnabled('variants')) {
5017 $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_child = ".((int) $this->id)." AND entity IN (".getEntity('product').")";
5018
5019 $query = $this->db->query($sql);
5020
5021 if ($query) {
5022 if (!$this->db->num_rows($query)) {
5023 return false;
5024 }
5025 return true;
5026 } else {
5027 dol_print_error($this->db);
5028 return -1;
5029 }
5030 } else {
5031 return false;
5032 }
5033 }
5034
5041 public function getFather()
5042 {
5043 $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";
5044 $sql .= ", p.tosell as status, p.tobuy as status_buy";
5045 $sql .= " FROM ".$this->db->prefix()."product_association as pa,";
5046 $sql .= " ".$this->db->prefix()."product as p";
5047 $sql .= " WHERE p.rowid = pa.fk_product_pere";
5048 $sql .= " AND pa.fk_product_fils = ".((int) $this->id);
5049
5050 $res = $this->db->query($sql);
5051 if ($res) {
5052 $prods = array();
5053 while ($record = $this->db->fetch_array($res)) {
5054 // $record['id'] = $record['rowid'] = id of father
5055 $prods[$record['id']]['id'] = $record['rowid'];
5056 $prods[$record['id']]['ref'] = $record['ref'];
5057 $prods[$record['id']]['label'] = $record['label'];
5058 $prods[$record['id']]['qty'] = $record['qty'];
5059 $prods[$record['id']]['incdec'] = $record['incdec'];
5060 $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
5061 $prods[$record['id']]['entity'] = $record['entity'];
5062 $prods[$record['id']]['status'] = $record['status'];
5063 $prods[$record['id']]['status_buy'] = $record['status_buy'];
5064 }
5065 return $prods;
5066 } else {
5067 dol_print_error($this->db);
5068 return -1;
5069 }
5070 }
5071
5072
5082 public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array())
5083 {
5084 if (empty($id)) {
5085 return array();
5086 }
5087
5088 $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
5089 $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
5090 $sql .= " pa.rowid as fk_association, pa.rang";
5091 $sql .= " FROM ".$this->db->prefix()."product as p,";
5092 $sql .= " ".$this->db->prefix()."product_association as pa";
5093 $sql .= " WHERE p.rowid = pa.fk_product_fils";
5094 $sql .= " AND pa.fk_product_pere = ".((int) $id);
5095 $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
5096 $sql.= " ORDER BY pa.rang";
5097
5098 dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG);
5099
5100 // Protection against infinite loop
5101 if ($level > 30) {
5102 return array();
5103 }
5104
5105 $res = $this->db->query($sql);
5106 if ($res) {
5107 $prods = array();
5108 if ($this->db->num_rows($res) > 0) {
5109 $parents[] = $id;
5110 }
5111
5112 while ($rec = $this->db->fetch_array($res)) {
5113 if (in_array($rec['id'], $parents)) {
5114 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);
5115 continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5116 }
5117
5118 $prods[$rec['rowid']] = array(
5119 0=>$rec['rowid'],
5120 1=>$rec['qty'],
5121 2=>$rec['fk_product_type'],
5122 3=>$this->db->escape($rec['label']),
5123 4=>$rec['incdec'],
5124 5=>$rec['ref'],
5125 6=>$rec['fk_association'],
5126 7=>$rec['rang']
5127 );
5128 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5129 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5130 if (empty($firstlevelonly)) {
5131 $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents);
5132 foreach ($listofchilds as $keyChild => $valueChild) {
5133 $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5134 }
5135 }
5136 }
5137
5138 return $prods;
5139 } else {
5140 dol_print_error($this->db);
5141 return -1;
5142 }
5143 }
5144
5145 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5152 public function get_sousproduits_arbo()
5153 {
5154 // phpcs:enable
5155 $parent = array();
5156
5157 foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5158 $parent[$this->label][$keyChild] = $valueChild;
5159 }
5160 foreach ($parent as $key => $value) { // key=label, value is array of childs
5161 $this->sousprods[$key] = $value;
5162 }
5163 }
5164
5171 public function getTooltipContentArray($params)
5172 {
5173 global $conf, $langs;
5174
5175 $langs->loadLangs(array('products', 'other'));
5176
5177 $datas = array();
5178 $nofetch = !empty($params['nofetch']);
5179
5180 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5181 return ['optimize' => $langs->trans("ShowProduct")];
5182 }
5183
5184 if (!empty($this->entity)) {
5185 $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, 1);
5186 if ($this->nbphoto > 0) {
5187 $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5188 }
5189 }
5190
5191 if ($this->type == Product::TYPE_PRODUCT) {
5192 $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5193 } elseif ($this->type == Product::TYPE_SERVICE) {
5194 $datas['picto']= img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5195 }
5196 if (isset($this->status) && isset($this->status_buy)) {
5197 $datas['status']= ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5198 }
5199
5200 if (!empty($this->ref)) {
5201 $datas['ref']= '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5202 }
5203 if (!empty($this->label)) {
5204 $datas['label']= '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label;
5205 }
5206 if (!empty($this->description)) {
5207 $datas['description']= '<br><b>'.$langs->trans('ProductDescription').':</b> '.dolGetFirstLineofText($this->description, 5);
5208 }
5209 if ($this->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
5210 if (isModEnabled('productbatch')) {
5211 $langs->load("productbatch");
5212 $datas['batchstatus']= "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5213 }
5214 }
5215 if (isModEnabled('barcode')) {
5216 $datas['barcode']= '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5217 }
5218
5219 if ($this->type == Product::TYPE_PRODUCT) {
5220 if ($this->weight) {
5221 $datas['weight']= "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5222 }
5223 $labelsize = "";
5224 if ($this->length) {
5225 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5226 }
5227 if ($this->width) {
5228 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5229 }
5230 if ($this->height) {
5231 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5232 }
5233 if ($labelsize) {
5234 $datas['size']= "<br>".$labelsize;
5235 }
5236
5237 $labelsurfacevolume = "";
5238 if ($this->surface) {
5239 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5240 }
5241 if ($this->volume) {
5242 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5243 }
5244 if ($labelsurfacevolume) {
5245 $datas['surface']= "<br>" . $labelsurfacevolume;
5246 }
5247 }
5248 if (!empty($this->pmp) && $this->pmp) {
5249 $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5250 }
5251
5252 if (isModEnabled('accounting')) {
5253 if ($this->status && isset($this->accountancy_code_sell)) {
5254 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5255 $selllabel = '<br>';
5256 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5257 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5258 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5259 $datas['accountancysell'] = $selllabel;
5260 }
5261 if ($this->status_buy && isset($this->accountancy_code_buy)) {
5262 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5263 $buylabel = '';
5264 if (empty($this->status)) {
5265 $buylabel .= '<br>';
5266 }
5267 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5268 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5269 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5270 $datas['accountancybuy'] = $buylabel;
5271 }
5272 }
5273 // show categories for this record only in ajax to not overload lists
5274 if (isModEnabled('categorie') && !$nofetch) {
5275 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5276 $form = new Form($this->db);
5277 $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5278 }
5279
5280 return $datas;
5281 }
5282
5296 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5297 {
5298 global $conf, $langs, $hookmanager;
5299 include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5300
5301 $result = '';
5302
5303 $newref = $this->ref;
5304 if ($maxlength) {
5305 $newref = dol_trunc($newref, $maxlength, 'middle');
5306 }
5307 $params = [
5308 'id' => $this->id,
5309 'objecttype' => (isset($this->type) ? ($this->type == 1 ? 'service' : 'product') : $this->element),
5310 'option' => $option,
5311 'nofetch' => 1,
5312 ];
5313 $classfortooltip = 'classfortooltip';
5314 $dataparams = '';
5315 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5316 $classfortooltip = 'classforajaxtooltip';
5317 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5318 $label = '';
5319 } else {
5320 $label = implode($this->getTooltipContentArray($params));
5321 }
5322
5323 $linkclose = '';
5324 if (empty($notooltip)) {
5325 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5326 $label = $langs->trans("ShowProduct");
5327 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5328 }
5329 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5330 $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5331 } else {
5332 $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5333 }
5334
5335 if ($option == 'supplier' || $option == 'category') {
5336 $url = DOL_URL_ROOT.'/product/fournisseurs.php?id='.$this->id;
5337 } elseif ($option == 'stock') {
5338 $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5339 } elseif ($option == 'composition') {
5340 $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5341 } else {
5342 $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5343 }
5344
5345 if ($option !== 'nolink') {
5346 // Add param to save lastsearch_values or not
5347 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
5348 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
5349 $add_save_lastsearch_values = 1;
5350 }
5351 if ($add_save_lastsearch_values) {
5352 $url .= '&save_lastsearch_values=1';
5353 }
5354 }
5355
5356 $linkstart = '<a href="'.$url.'"';
5357 $linkstart .= $linkclose.'>';
5358 $linkend = '</a>';
5359
5360 $result .= $linkstart;
5361 if ($withpicto) {
5362 if ($this->type == Product::TYPE_PRODUCT) {
5363 $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5364 }
5365 if ($this->type == Product::TYPE_SERVICE) {
5366 $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5367 }
5368 }
5369 $result .= '<span class="aaa">'.dol_escape_htmltag($newref).'</span>';
5370 $result .= $linkend;
5371 if ($withpicto != 2) {
5372 $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
5373 }
5374
5375 global $action;
5376 $hookmanager->initHooks(array('productdao'));
5377 $parameters = array('id'=>$this->id, 'getnomurl' => &$result, 'label' => &$label);
5378 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5379 if ($reshook > 0) {
5380 $result = $hookmanager->resPrint;
5381 } else {
5382 $result .= $hookmanager->resPrint;
5383 }
5384
5385 return $result;
5386 }
5387
5388
5399 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
5400 {
5401 global $conf, $user, $langs;
5402
5403 $langs->load("products");
5404 $outputlangs->load("products");
5405
5406 // Positionne le modele sur le nom du modele a utiliser
5407 if (!dol_strlen($modele)) {
5408 $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
5409 }
5410
5411 $modelpath = "core/modules/product/doc/";
5412
5413 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
5414 }
5415
5423 public function getLibStatut($mode = 0, $type = 0)
5424 {
5425 switch ($type) {
5426 case 0:
5427 return $this->LibStatut($this->status, $mode, $type);
5428 case 1:
5429 return $this->LibStatut($this->status_buy, $mode, $type);
5430 case 2:
5431 return $this->LibStatut($this->status_batch, $mode, $type);
5432 default:
5433 //Simulate previous behavior but should return an error string
5434 return $this->LibStatut($this->status_buy, $mode, $type);
5435 }
5436 }
5437
5438 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5447 public function LibStatut($status, $mode = 0, $type = 0)
5448 {
5449 // phpcs:enable
5450 global $conf, $langs;
5451
5452 $labelStatus = $labelStatusShort = '';
5453
5454 $langs->load('products');
5455 if (isModEnabled('productbatch')) {
5456 $langs->load("productbatch");
5457 }
5458
5459 if ($type == 2) {
5460 switch ($mode) {
5461 case 0:
5462 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
5463 return dolGetStatus($label);
5464 case 1:
5465 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
5466 return dolGetStatus($label);
5467 case 2:
5468 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
5469 case 3:
5470 return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
5471 case 4:
5472 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
5473 case 5:
5474 return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
5475 default:
5476 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
5477 }
5478 }
5479
5480 $statuttrans = empty($status) ? 'status5' : 'status4';
5481
5482 if ($status == 0) {
5483 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5484 if ($type == 0) {
5485 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
5486 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
5487 } elseif ($type == 1) {
5488 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
5489 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
5490 } elseif ($type == 2) {
5491 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
5492 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
5493 }
5494 } elseif ($status == 1) {
5495 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5496 if ($type == 0) {
5497 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
5498 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
5499 } elseif ($type == 1) {
5500 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
5501 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
5502 } elseif ($type == 2) {
5503 $labelStatus = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial'));
5504 $labelStatusShort = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort'));
5505 }
5506 } elseif ($type == 2 && $status == 2) {
5507 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
5508 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
5509 }
5510
5511 if ($mode > 6) {
5512 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
5513 } else {
5514 return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
5515 }
5516 }
5517
5518
5524 public function getLibFinished()
5525 {
5526 global $langs;
5527 $langs->load('products');
5528
5529 if (isset($this->finished) && $this->finished >= 0) {
5530 $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
5531 $resql = $this->db->query($sql);
5532 if ($resql && $this->db->num_rows($resql) > 0) {
5533 $res = $this->db->fetch_array($resql);
5534 $label = $langs->trans($res['label']);
5535 $this->db->free($resql);
5536 return $label;
5537 } else {
5538 $this->error = $this->db->error().' sql='.$sql;
5539 dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
5540 return -1;
5541 }
5542 }
5543
5544 return '';
5545 }
5546
5547
5548 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5565 public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
5566 {
5567 // phpcs:enable
5568 if ($id_entrepot) {
5569 $this->db->begin();
5570
5571 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5572
5573 if ($nbpiece < 0) {
5574 if (!$movement) {
5575 $movement = 1;
5576 }
5577 $nbpiece = abs($nbpiece);
5578 }
5579
5580 $op[0] = "+".trim($nbpiece);
5581 $op[1] = "-".trim($nbpiece);
5582
5583 $movementstock = new MouvementStock($this->db);
5584 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->origin_id
5585 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
5586
5587 if ($result >= 0) {
5588 if ($extrafields) {
5589 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5590 $movementstock->array_options = $array_options;
5591 $movementstock->insertExtraFields();
5592 }
5593 $this->db->commit();
5594 return 1;
5595 } else {
5596 $this->error = $movementstock->error;
5597 $this->errors = $movementstock->errors;
5598
5599 $this->db->rollback();
5600 return -1;
5601 }
5602 }
5603
5604 return -1;
5605 }
5606
5607 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5628 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)
5629 {
5630 // phpcs:enable
5631 if ($id_entrepot) {
5632 $this->db->begin();
5633
5634 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5635
5636 if ($nbpiece < 0) {
5637 if (!$movement) {
5638 $movement = 1;
5639 }
5640 $nbpiece = abs($nbpiece);
5641 }
5642
5643 $op[0] = "+".trim($nbpiece);
5644 $op[1] = "-".trim($nbpiece);
5645
5646 $movementstock = new MouvementStock($this->db);
5647 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->fk_origin
5648 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct, 0, $force_update_batch);
5649
5650 if ($result >= 0) {
5651 if ($extrafields) {
5652 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5653 $movementstock->array_options = $array_options;
5654 $movementstock->insertExtraFields();
5655 }
5656 $this->db->commit();
5657 return 1;
5658 } else {
5659 $this->error = $movementstock->error;
5660 $this->errors = $movementstock->errors;
5661
5662 $this->db->rollback();
5663 return -1;
5664 }
5665 }
5666 return -1;
5667 }
5668
5669 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5682 public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
5683 {
5684 // phpcs:enable
5685 global $conf;
5686
5687 $this->stock_reel = 0;
5688 $this->stock_warehouse = array();
5689 $this->stock_theorique = 0;
5690
5691 // Set filter on warehouse status
5692 $warehouseStatus = array();
5693 if (preg_match('/warehouseclosed/', $option)) {
5695 }
5696 if (preg_match('/warehouseopen/', $option)) {
5698 }
5699 if (preg_match('/warehouseinternal/', $option)) {
5700 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
5702 } else {
5704 }
5705 }
5706
5707 $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
5708 $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
5709 $sql .= ", ".$this->db->prefix()."entrepot as w";
5710 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
5711 $sql .= " AND w.rowid = ps.fk_entrepot";
5712 $sql .= " AND ps.fk_product = ".((int) $this->id);
5713 if (count($warehouseStatus)) {
5714 $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
5715 }
5716
5717 $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;
5718
5719 dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
5720 $result = $this->db->query($sql);
5721 if ($result) {
5722 $num = $this->db->num_rows($result);
5723 $i = 0;
5724 if ($num > 0) {
5725 while ($i < $num) {
5726 $row = $this->db->fetch_object($result);
5727 $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
5728 $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
5729 $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
5730 if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
5731 $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
5732 }
5733 $this->stock_reel += $row->reel;
5734 $i++;
5735 }
5736 }
5737 $this->db->free($result);
5738
5739 if (!preg_match('/novirtual/', $option)) {
5740 $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
5741 }
5742
5743 return 1;
5744 } else {
5745 $this->error = $this->db->lasterror();
5746 return -1;
5747 }
5748 }
5749
5750
5751 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5761 public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
5762 {
5763 // phpcs:enable
5764 global $conf, $hookmanager, $action;
5765
5766 $stock_commande_client = 0;
5767 $stock_commande_fournisseur = 0;
5768 $stock_sending_client = 0;
5769 $stock_reception_fournisseur = 0;
5770 $stock_inproduction = 0;
5771
5772 //dol_syslog("load_virtual_stock");
5773
5774 if (isModEnabled('commande')) {
5775 $result = $this->load_stats_commande(0, '1,2', 1);
5776 if ($result < 0) {
5777 dol_print_error($this->db, $this->error);
5778 }
5779 $stock_commande_client = $this->stats_commande['qty'];
5780 }
5781 if (isModEnabled("expedition")) {
5782 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
5783 $filterShipmentStatus = '';
5784 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
5785 $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
5786 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
5787 $filterShipmentStatus = Expedition::STATUS_CLOSED;
5788 }
5789 $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
5790 if ($result < 0) {
5791 dol_print_error($this->db, $this->error);
5792 }
5793 $stock_sending_client = $this->stats_expedition['qty'];
5794 }
5795 if (isModEnabled("supplier_order")) {
5796 $filterStatus = !getDolGlobalString('SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK') ? '3,4' : $conf->global->SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK;
5797 if (isset($includedraftpoforvirtual)) {
5798 $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
5799 }
5800 $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
5801 if ($result < 0) {
5802 dol_print_error($this->db, $this->error);
5803 }
5804 $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
5805 }
5806 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && empty($conf->reception->enabled)) {
5807 // Case module reception is not used
5808 $filterStatus = '4';
5809 if (isset($includedraftpoforvirtual)) {
5810 $filterStatus = '0,'.$filterStatus;
5811 }
5812 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
5813 if ($result < 0) {
5814 dol_print_error($this->db, $this->error);
5815 }
5816 $stock_reception_fournisseur = $this->stats_reception['qty'];
5817 }
5818 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && isModEnabled("reception")) {
5819 // Case module reception is used
5820 $filterStatus = '4';
5821 if (isset($includedraftpoforvirtual)) {
5822 $filterStatus = '0,'.$filterStatus;
5823 }
5824 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock); // Use same tables than when module reception is not used.
5825 if ($result < 0) {
5826 dol_print_error($this->db, $this->error);
5827 }
5828 $stock_reception_fournisseur = $this->stats_reception['qty'];
5829 }
5830 if (isModEnabled('mrp')) {
5831 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
5832 if ($result < 0) {
5833 dol_print_error($this->db, $this->error);
5834 }
5835 $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
5836 }
5837
5838 $this->stock_theorique = $this->stock_reel + $stock_inproduction;
5839
5840 // $weBillOrderOrShipmentReception is set to 'order' or 'shipmentreception'. it will be used to know how to make virtual stock
5841 // calculation when we have a stock increase or decrease on billing. Do we have to count orders to bill or shipment/reception to bill ?
5842 $weBillOrderOrShipmentReception = getDolGlobalString('STOCK_DO_WE_BILL_ORDER_OR_SHIPMENTECEPTION_FOR_VIRTUALSTOCK', 'order');
5843
5844 // Stock decrease mode
5845 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
5846 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
5847 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER')) {
5848 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
5849 $tmpnewprod = dol_clone($this, 1);
5850 $result = $tmpnewprod->load_stats_commande(0, '0', 1); // Get qty in draft orders
5851 $this->stock_theorique += $tmpnewprod->stats_commande['qty'];
5852 }
5853 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'order') {
5854 $this->stock_theorique -= $stock_commande_client;
5855 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
5856 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
5857 }
5858
5859 // Stock Increase mode
5860 if (getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION') || getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
5861 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5862 } 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
5863 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5864 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) { // Warning: stock change "on approval", not on validation !
5865 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
5866 $tmpnewprod = dol_clone($this, 1);
5867 $result = $tmpnewprod->load_stats_commande_fournisseur(0, '0', 1); // Get qty in draft orders
5868 $this->stock_theorique += $this->stats_commande_fournisseur['qty'];
5869 }
5870 $this->stock_theorique -= $stock_reception_fournisseur;
5871 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'order') {
5872 $this->stock_theorique += $stock_commande_fournisseur;
5873 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
5874 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5875 }
5876
5877 $parameters = array('id'=>$this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
5878 // Note that $action and $object may have been modified by some hooks
5879 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
5880 if ($reshook > 0) {
5881 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
5882 } elseif ($reshook == 0 && isset($hookmanager->resArray['stock_stats_hook'])) {
5883 $this->stock_theorique += $hookmanager->resArray['stock_stats_hook'];
5884 }
5885
5886 return 1;
5887 }
5888
5889
5897 public function loadBatchInfo($batch)
5898 {
5899 $result = array();
5900
5901 $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";
5902 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
5903 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
5904 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
5905 $resql = $this->db->query($sql);
5906 if ($resql) {
5907 $num = $this->db->num_rows($resql);
5908 $i = 0;
5909 while ($i < $num) {
5910 $obj = $this->db->fetch_object($resql);
5911 $result[] = array('batch'=>$batch, 'eatby'=>$this->db->jdate($obj->eatby), 'sellby'=>$this->db->jdate($obj->sellby), 'qty'=>$obj->qty);
5912 $i++;
5913 }
5914 return $result;
5915 } else {
5916 dol_print_error($this->db);
5917 $this->db->rollback();
5918 return array();
5919 }
5920 }
5921
5922 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5930 public function add_photo($sdir, $file)
5931 {
5932 // phpcs:enable
5933 global $conf;
5934
5935 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5936
5937 $result = 0;
5938
5939 $dir = $sdir;
5940 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5941 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
5942 } else {
5943 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
5944 }
5945
5946 dol_mkdir($dir);
5947
5948 $dir_osencoded = $dir;
5949
5950 if (is_dir($dir_osencoded)) {
5951 $originImage = $dir.'/'.$file['name'];
5952
5953 // Cree fichier en taille origine
5954 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
5955
5956 if (file_exists(dol_osencode($originImage))) {
5957 // Create thumbs
5958 $this->addThumbs($originImage);
5959 }
5960 }
5961
5962 if (is_numeric($result) && $result > 0) {
5963 return 1;
5964 } else {
5965 return -1;
5966 }
5967 }
5968
5969 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5976 public function is_photo_available($sdir)
5977 {
5978 // phpcs:enable
5979 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5980 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5981
5982 global $conf;
5983
5984 $dir = $sdir;
5985 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5986 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
5987 } else {
5988 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
5989 }
5990
5991 $nbphoto = 0;
5992
5993 $dir_osencoded = dol_osencode($dir);
5994 if (file_exists($dir_osencoded)) {
5995 $handle = opendir($dir_osencoded);
5996 if (is_resource($handle)) {
5997 while (($file = readdir($handle)) !== false) {
5998 if (!utf8_check($file)) {
5999 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
6000 }
6001 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6002 return true;
6003 }
6004 }
6005 }
6006 }
6007 return false;
6008 }
6009
6010 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6018 public function liste_photos($dir, $nbmax = 0)
6019 {
6020 // phpcs:enable
6021 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6022 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6023
6024 $nbphoto = 0;
6025 $tabobj = array();
6026
6027 $dir_osencoded = dol_osencode($dir);
6028 $handle = @opendir($dir_osencoded);
6029 if (is_resource($handle)) {
6030 while (($file = readdir($handle)) !== false) {
6031 if (!utf8_check($file)) {
6032 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // readdir returns ISO
6033 }
6034 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6035 $nbphoto++;
6036
6037 // We forge name of thumb.
6038 $photo = $file;
6039 $photo_vignette = '';
6040 $regs = array();
6041 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
6042 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
6043 }
6044
6045 $dirthumb = $dir.'thumbs/';
6046
6047 // Objet
6048 $obj = array();
6049 $obj['photo'] = $photo;
6050 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
6051 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
6052 } else {
6053 $obj['photo_vignette'] = "";
6054 }
6055
6056 $tabobj[$nbphoto - 1] = $obj;
6057
6058 // Do we have to continue with next photo ?
6059 if ($nbmax && $nbphoto >= $nbmax) {
6060 break;
6061 }
6062 }
6063 }
6064
6065 closedir($handle);
6066 }
6067
6068 return $tabobj;
6069 }
6070
6071 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6078 public function delete_photo($file)
6079 {
6080 // phpcs:enable
6081 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6082 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6083
6084 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
6085 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
6086 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
6087
6088 // On efface l'image d'origine
6089 dol_delete_file($file, 0, 0, 0, $this); // For triggers
6090
6091 // Si elle existe, on efface la vignette
6092 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
6093 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
6094 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6095 dol_delete_file($dirthumb.$photo_vignette);
6096 }
6097
6098 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
6099 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6100 dol_delete_file($dirthumb.$photo_vignette);
6101 }
6102 }
6103 }
6104
6105 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6112 public function get_image_size($file)
6113 {
6114 // phpcs:enable
6115 $file_osencoded = dol_osencode($file);
6116 $infoImg = getimagesize($file_osencoded); // Get information on image
6117 $this->imgWidth = $infoImg[0]; // Largeur de l'image
6118 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
6119 }
6120
6121 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6127 public function load_state_board()
6128 {
6129 // phpcs:enable
6130 global $hookmanager;
6131
6132 $this->nb = array();
6133
6134 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6135 $sql .= " FROM ".$this->db->prefix()."product as p";
6136 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6137 // Add where from hooks
6138 if (is_object($hookmanager)) {
6139 $parameters = array();
6140 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6141 $sql .= $hookmanager->resPrint;
6142 }
6143 $sql .= ' GROUP BY fk_product_type';
6144
6145 $resql = $this->db->query($sql);
6146 if ($resql) {
6147 while ($obj = $this->db->fetch_object($resql)) {
6148 if ($obj->fk_product_type == 1) {
6149 $this->nb["services"] = $obj->nb;
6150 } else {
6151 $this->nb["products"] = $obj->nb;
6152 }
6153 }
6154 $this->db->free($resql);
6155 return 1;
6156 } else {
6157 dol_print_error($this->db);
6158 $this->error = $this->db->error();
6159 return -1;
6160 }
6161 }
6162
6168 public function isProduct()
6169 {
6170 return ($this->type == Product::TYPE_PRODUCT ? true : false);
6171 }
6172
6178 public function isService()
6179 {
6180 return ($this->type == Product::TYPE_SERVICE ? true : false);
6181 }
6182
6188 public function isStockManaged()
6189 {
6190 return ($this->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'));
6191 }
6192
6198 public function isMandatoryPeriod()
6199 {
6200 return ($this->mandatory_period == 1 ? true : false);
6201 }
6202
6208 public function hasbatch()
6209 {
6210 return ($this->status_batch > 0 ? true : false);
6211 }
6212
6213
6214 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6223 public function get_barcode($object, $type = '')
6224 {
6225 // phpcs:enable
6226 global $conf;
6227
6228 $result = '';
6229 if (getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
6230 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6231 foreach ($dirsociete as $dirroot) {
6232 $res = dol_include_once($dirroot . getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM').'.php');
6233 if ($res) {
6234 break;
6235 }
6236 }
6237 $var = $conf->global->BARCODE_PRODUCT_ADDON_NUM;
6238 $mod = new $var();
6239
6240 $result = $mod->getNextValue($object, $type);
6241
6242 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6243 }
6244 return $result;
6245 }
6246
6254 public function initAsSpecimen()
6255 {
6256 $now = dol_now();
6257
6258 // Initialize parameters
6259 $this->specimen = 1;
6260 $this->id = 0;
6261 $this->ref = 'PRODUCT_SPEC';
6262 $this->label = 'PRODUCT SPECIMEN';
6263 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
6264 $this->specimen = 1;
6265 $this->country_id = 1;
6266 $this->status = 1;
6267 $this->status_buy = 1;
6268 $this->tobatch = 0;
6269 $this->note_private = 'This is a comment (private)';
6270 $this->note_public = 'This is a comment (public)';
6271 $this->date_creation = $now;
6272 $this->date_modification = $now;
6273
6274 $this->weight = 4;
6275 $this->weight_units = 3;
6276
6277 $this->length = 5;
6278 $this->length_units = 1;
6279 $this->width = 6;
6280 $this->width_units = 0;
6281 $this->height = null;
6282 $this->height_units = null;
6283
6284 $this->surface = 30;
6285 $this->surface_units = 0;
6286 $this->volume = 300;
6287 $this->volume_units = 0;
6288
6289 $this->barcode = -1; // Create barcode automatically
6290 }
6291
6298 public function getLabelOfUnit($type = 'long')
6299 {
6300 global $langs;
6301
6302 if (!$this->fk_unit) {
6303 return '';
6304 }
6305
6306 $langs->load('products');
6307
6308 $label_type = 'label';
6309 if ($type == 'short') {
6310 $label_type = 'short_label';
6311 }
6312
6313 $sql = "SELECT ".$label_type.", code from ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
6314
6315 $resql = $this->db->query($sql);
6316 if ($resql && $this->db->num_rows($resql) > 0) {
6317 $res = $this->db->fetch_array($resql);
6318 $label = ($label_type == 'short_label' ? $res[$label_type] : 'unit'.$res['code']);
6319 $this->db->free($resql);
6320 return $label;
6321 } else {
6322 $this->error = $this->db->error();
6323 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
6324 return -1;
6325 }
6326 }
6327
6328 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6334 public function min_recommended_price()
6335 {
6336 // phpcs:enable
6337 global $conf;
6338
6339 $maxpricesupplier = 0;
6340
6341 if (getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE')) {
6342 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6343 $product_fourn = new ProductFournisseur($this->db);
6344 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
6345
6346 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
6347 foreach ($product_fourn_list as $productfourn) {
6348 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
6349 $maxpricesupplier = $productfourn->fourn_unitprice;
6350 }
6351 }
6352
6353 $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
6354 }
6355 }
6356
6357 return $maxpricesupplier;
6358 }
6359
6360
6371 public function setCategories($categories)
6372 {
6373 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
6374 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
6375 }
6376
6385 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
6386 {
6387 $tables = array(
6388 'product_customer_price',
6389 'product_customer_price_log'
6390 );
6391
6392 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
6393 }
6394
6406 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
6407 {
6408 global $conf;
6409
6410 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
6411 $query = $this->db->query($sql);
6412
6413 $rules = array();
6414
6415 while ($result = $this->db->fetch_object($query)) {
6416 $rules[$result->level] = $result;
6417 }
6418
6419 //Because prices can be based on other level's prices, we temporarily store them
6420 $prices = array(
6421 1 => $baseprice
6422 );
6423
6424 $nbofproducts = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
6425 for ($i = 1; $i <= $nbofproducts; $i++) {
6426 $price = $baseprice;
6427 $price_min = $baseprice;
6428
6429 //We have to make sure it does exist and it is > 0
6430 //First price level only allows changing min_price
6431 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
6432 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
6433 }
6434
6435 $prices[$i] = $price;
6436
6437 //We have to make sure it does exist and it is > 0
6438 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
6439 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
6440 }
6441
6442 //Little check to make sure the price is modified before triggering generation
6443 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
6444 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
6445
6446 if ($check_amount && $check_type) {
6447 continue;
6448 }
6449
6450 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
6451 return -1;
6452 }
6453 }
6454
6455 return 1;
6456 }
6457
6463 public function getRights()
6464 {
6465 global $user;
6466
6467 if ($this->isProduct()) {
6468 return $user->rights->produit;
6469 } else {
6470 return $user->rights->service;
6471 }
6472 }
6473
6480 public function info($id)
6481 {
6482 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
6483 $sql .= " p.fk_user_author, p.fk_user_modif";
6484 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
6485 $sql .= " WHERE p.rowid = ".((int) $id);
6486
6487 $result = $this->db->query($sql);
6488 if ($result) {
6489 if ($this->db->num_rows($result)) {
6490 $obj = $this->db->fetch_object($result);
6491
6492 $this->id = $obj->rowid;
6493 $this->ref = $obj->ref;
6494
6495 $this->user_creation_id = $obj->fk_user_author;
6496 $this->user_modification_id = $obj->fk_user_modif;
6497
6498 $this->date_creation = $this->db->jdate($obj->date_creation);
6499 $this->date_modification = $this->db->jdate($obj->date_modification);
6500 }
6501
6502 $this->db->free($result);
6503 } else {
6504 dol_print_error($this->db);
6505 }
6506 }
6507
6508
6513 public function getProductDurationHours()
6514 {
6515 global $langs;
6516
6517 if (empty($this->duration_value)) {
6518 $this->errors[]='ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
6519 return -1;
6520 }
6521
6522 if ($this->duration_unit == 'i') {
6523 $prodDurationHours = 1. / 60;
6524 }
6525 if ($this->duration_unit == 'h') {
6526 $prodDurationHours = 1.;
6527 }
6528 if ($this->duration_unit == 'd') {
6529 $prodDurationHours = 24.;
6530 }
6531 if ($this->duration_unit == 'w') {
6532 $prodDurationHours = 24. * 7;
6533 }
6534 if ($this->duration_unit == 'm') {
6535 $prodDurationHours = 24. * 30;
6536 }
6537 if ($this->duration_unit == 'y') {
6538 $prodDurationHours = 24. * 365;
6539 }
6540 $prodDurationHours *= $this->duration_value;
6541
6542 return $prodDurationHours;
6543 }
6544
6545
6553 public function getKanbanView($option = '', $arraydata = null)
6554 {
6555 global $langs,$conf;
6556
6557 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6558
6559 $return = '<div class="box-flex-item box-flex-grow-zero">';
6560 $return .= '<div class="info-box info-box-sm">';
6561 $return .= '<div class="info-box-img">';
6562 $label = '';
6563 if ($this->is_photo_available($conf->product->multidir_output[$this->entity])) {
6564 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
6565 $return .= $label;
6566 } else {
6567 if ($this->type == Product::TYPE_PRODUCT) {
6568 $label .= img_picto('', 'product');
6569 } elseif ($this->type == Product::TYPE_SERVICE) {
6570 $label .= img_picto('', 'service');
6571 }
6572 $return .= $label;
6573 }
6574 $return .= '</div>';
6575 $return .= '<div class="info-box-content">';
6576 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
6577 if ($selected >= 0) {
6578 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
6579 }
6580 if (property_exists($this, 'label')) {
6581 $return .= '<br><span class="info-box-label opacitymedium">'.$this->label.'</span>';
6582 }
6583 if (property_exists($this, 'price') && property_exists($this, 'price_ttc')) {
6584 if ($this->price_base_type == 'TTC') {
6585 $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
6586 } else {
6587 if ($this->status) {
6588 $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
6589 }
6590 }
6591 }
6592 $br = 1;
6593 if (property_exists($this, 'stock_reel') && $this->isProduct()) {
6594 $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>';
6595 $br = 0;
6596 }
6597 if (method_exists($this, 'getLibStatut')) {
6598 if ($br) {
6599 $return .= '<br><div class="info-box-status inline-block valignmiddle">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6600 } else {
6601 $return .= '<div class="info-box-status inline-block valignmiddle marginleftonly paddingleft">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6602 }
6603 }
6604 $return .= '</div>';
6605 $return .= '</div>';
6606 $return .= '</div>';
6607 return $return;
6608 }
6609}
6610
6616{
6617 public $picto = 'service';
6618}
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
Definition security.php:604
$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.
const STATUS_VALIDATED
Validated status.
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 a product combination.
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.
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.
updatePrice($newprice, $newpricebase, $user, $newvat='', $newminprice=0, $level=0, $newnpr=0, $newpbq=0, $ignore_autogen=0, $localtaxes_array=array(), $newdefaultvatcode='', $notrigger=0)
Modify customer price of a product/Service for a given level.
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 in Hours of a service base on duration fields.
fetch($id='', $ref='', $ref_ext='', $barcode='', $ignore_expression=0, $ignore_price_load=0, $ignore_lang_load=0)
Load a product in memory from database.
$default_vat_code
Default VAT code for product (link to code into llx_c_tva but without foreign keys)
$duration_unit
Serivce 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.
$fk_default_workstation
Service Workstation.
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.
update($id, $user, $notrigger=false, $action='update', $updatetype=false)
Update a record into database.
info($id)
Load information for tab info.
load_stats_inproduction($socid=0, $filtrestatut='', $forVirtualStock=0, $dateofvirtualstock=null)
Charge tableau des stats production pour le produit/service.
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.
$multiprices
Arrays for multiprices.
$localtax1_tx
Other local taxes.
getChildsArbo($id, $firstlevelonly=0, $level=1, $parents=array())
Return childs 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.
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.
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.
$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.
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.
$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.
load_state_board()
Load indicators this->nb for the dashboard.
min_recommended_price()
Return minimum product recommended price.
_log_price($user, $level=0)
Insert a track that we changed a customer price.
$res
Path of subproducts.
_get_stats($sql, $mode, $year=0)
Return an array formated 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=0, $outputlangs='', $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='')
Make control on an uploaded file from an GUI page and move it to 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 informations (by default a local PHP server timestamp) Re...
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_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
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_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
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)
get_localtax($vatrate, $local, $thirdparty_buyer="", $thirdparty_seller="", $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
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...
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)
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:121