dolibarr 19.0.3
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 <= $conf->global->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 <= $conf->global->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 to ship in theorical 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 global $alreadyfound;
5085
5086 if (empty($id)) {
5087 return array();
5088 }
5089
5090 $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
5091 $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
5092 $sql .= " pa.rowid as fk_association, pa.rang";
5093 $sql .= " FROM ".$this->db->prefix()."product as p,";
5094 $sql .= " ".$this->db->prefix()."product_association as pa";
5095 $sql .= " WHERE p.rowid = pa.fk_product_fils";
5096 $sql .= " AND pa.fk_product_pere = ".((int) $id);
5097 $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
5098 $sql.= " ORDER BY pa.rang";
5099
5100 dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG);
5101
5102 if ($level == 1) {
5103 $alreadyfound = array($id=>1); // We init array of found object to start of tree, so if we found it later (should not happened), we stop immediatly
5104 }
5105 // Protection against infinite loop
5106 if ($level > 30) {
5107 return array();
5108 }
5109
5110 $res = $this->db->query($sql);
5111 if ($res) {
5112 $prods = array();
5113 while ($rec = $this->db->fetch_array($res)) {
5114 if (!empty($alreadyfound[$rec['rowid']])) {
5115 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);
5116 if (in_array($rec['id'], $parents)) {
5117 continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5118 }
5119 }
5120 $alreadyfound[$rec['rowid']] = 1;
5121 $prods[$rec['rowid']] = array(
5122 0=>$rec['rowid'],
5123 1=>$rec['qty'],
5124 2=>$rec['fk_product_type'],
5125 3=>$this->db->escape($rec['label']),
5126 4=>$rec['incdec'],
5127 5=>$rec['ref'],
5128 6=>$rec['fk_association'],
5129 7=>$rec['rang']
5130 );
5131 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5132 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5133 if (empty($firstlevelonly)) {
5134 $parents[] = $rec['rowid'];
5135 $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents);
5136 foreach ($listofchilds as $keyChild => $valueChild) {
5137 $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5138 }
5139 }
5140 }
5141
5142 return $prods;
5143 } else {
5144 dol_print_error($this->db);
5145 return -1;
5146 }
5147 }
5148
5149 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5156 public function get_sousproduits_arbo()
5157 {
5158 // phpcs:enable
5159 $parent = array();
5160
5161 foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5162 $parent[$this->label][$keyChild] = $valueChild;
5163 }
5164 foreach ($parent as $key => $value) { // key=label, value is array of childs
5165 $this->sousprods[$key] = $value;
5166 }
5167 }
5168
5175 public function getTooltipContentArray($params)
5176 {
5177 global $conf, $langs;
5178
5179 $langs->loadLangs(array('products', 'other'));
5180
5181 $datas = array();
5182 $nofetch = !empty($params['nofetch']);
5183
5184 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5185 return ['optimize' => $langs->trans("ShowProduct")];
5186 }
5187
5188 if (!empty($this->entity)) {
5189 $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, 1);
5190 if ($this->nbphoto > 0) {
5191 $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5192 }
5193 }
5194
5195 if ($this->type == Product::TYPE_PRODUCT) {
5196 $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5197 } elseif ($this->type == Product::TYPE_SERVICE) {
5198 $datas['picto']= img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5199 }
5200 if (isset($this->status) && isset($this->status_buy)) {
5201 $datas['status']= ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5202 }
5203
5204 if (!empty($this->ref)) {
5205 $datas['ref']= '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5206 }
5207 if (!empty($this->label)) {
5208 $datas['label']= '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label;
5209 }
5210 if (!empty($this->description)) {
5211 $datas['description']= '<br><b>'.$langs->trans('ProductDescription').':</b> '.dolGetFirstLineofText($this->description, 5);
5212 }
5213 if ($this->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
5214 if (isModEnabled('productbatch')) {
5215 $langs->load("productbatch");
5216 $datas['batchstatus']= "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5217 }
5218 }
5219 if (isModEnabled('barcode')) {
5220 $datas['barcode']= '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5221 }
5222
5223 if ($this->type == Product::TYPE_PRODUCT) {
5224 if ($this->weight) {
5225 $datas['weight']= "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5226 }
5227 $labelsize = "";
5228 if ($this->length) {
5229 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5230 }
5231 if ($this->width) {
5232 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5233 }
5234 if ($this->height) {
5235 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5236 }
5237 if ($labelsize) {
5238 $datas['size']= "<br>".$labelsize;
5239 }
5240
5241 $labelsurfacevolume = "";
5242 if ($this->surface) {
5243 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5244 }
5245 if ($this->volume) {
5246 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5247 }
5248 if ($labelsurfacevolume) {
5249 $datas['surface']= "<br>" . $labelsurfacevolume;
5250 }
5251 }
5252 if (!empty($this->pmp) && $this->pmp) {
5253 $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5254 }
5255
5256 if (isModEnabled('accounting')) {
5257 if ($this->status && isset($this->accountancy_code_sell)) {
5258 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5259 $selllabel = '<br>';
5260 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5261 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5262 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5263 $datas['accountancysell'] = $selllabel;
5264 }
5265 if ($this->status_buy && isset($this->accountancy_code_buy)) {
5266 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5267 $buylabel = '';
5268 if (empty($this->status)) {
5269 $buylabel .= '<br>';
5270 }
5271 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5272 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5273 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5274 $datas['accountancybuy'] = $buylabel;
5275 }
5276 }
5277 // show categories for this record only in ajax to not overload lists
5278 if (isModEnabled('categorie') && !$nofetch) {
5279 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5280 $form = new Form($this->db);
5281 $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5282 }
5283
5284 return $datas;
5285 }
5286
5300 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5301 {
5302 global $conf, $langs, $hookmanager;
5303 include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5304
5305 $result = '';
5306
5307 $newref = $this->ref;
5308 if ($maxlength) {
5309 $newref = dol_trunc($newref, $maxlength, 'middle');
5310 }
5311 $params = [
5312 'id' => $this->id,
5313 'objecttype' => (isset($this->type) ? ($this->type == 1 ? 'service' : 'product') : $this->element),
5314 'option' => $option,
5315 'nofetch' => 1,
5316 ];
5317 $classfortooltip = 'classfortooltip';
5318 $dataparams = '';
5319 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5320 $classfortooltip = 'classforajaxtooltip';
5321 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5322 $label = '';
5323 } else {
5324 $label = implode($this->getTooltipContentArray($params));
5325 }
5326
5327 $linkclose = '';
5328 if (empty($notooltip)) {
5329 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5330 $label = $langs->trans("ShowProduct");
5331 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5332 }
5333 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5334 $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5335 } else {
5336 $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5337 }
5338
5339 if ($option == 'supplier' || $option == 'category') {
5340 $url = DOL_URL_ROOT.'/product/fournisseurs.php?id='.$this->id;
5341 } elseif ($option == 'stock') {
5342 $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5343 } elseif ($option == 'composition') {
5344 $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5345 } else {
5346 $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5347 }
5348
5349 if ($option !== 'nolink') {
5350 // Add param to save lastsearch_values or not
5351 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
5352 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
5353 $add_save_lastsearch_values = 1;
5354 }
5355 if ($add_save_lastsearch_values) {
5356 $url .= '&save_lastsearch_values=1';
5357 }
5358 }
5359
5360 $linkstart = '<a href="'.$url.'"';
5361 $linkstart .= $linkclose.'>';
5362 $linkend = '</a>';
5363
5364 $result .= $linkstart;
5365 if ($withpicto) {
5366 if ($this->type == Product::TYPE_PRODUCT) {
5367 $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5368 }
5369 if ($this->type == Product::TYPE_SERVICE) {
5370 $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5371 }
5372 }
5373 $result .= '<span class="aaa">'.dol_escape_htmltag($newref).'</span>';
5374 $result .= $linkend;
5375 if ($withpicto != 2) {
5376 $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
5377 }
5378
5379 global $action;
5380 $hookmanager->initHooks(array('productdao'));
5381 $parameters = array('id'=>$this->id, 'getnomurl' => &$result, 'label' => &$label);
5382 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5383 if ($reshook > 0) {
5384 $result = $hookmanager->resPrint;
5385 } else {
5386 $result .= $hookmanager->resPrint;
5387 }
5388
5389 return $result;
5390 }
5391
5392
5403 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
5404 {
5405 global $conf, $user, $langs;
5406
5407 $langs->load("products");
5408 $outputlangs->load("products");
5409
5410 // Positionne le modele sur le nom du modele a utiliser
5411 if (!dol_strlen($modele)) {
5412 $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
5413 }
5414
5415 $modelpath = "core/modules/product/doc/";
5416
5417 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
5418 }
5419
5427 public function getLibStatut($mode = 0, $type = 0)
5428 {
5429 switch ($type) {
5430 case 0:
5431 return $this->LibStatut($this->status, $mode, $type);
5432 case 1:
5433 return $this->LibStatut($this->status_buy, $mode, $type);
5434 case 2:
5435 return $this->LibStatut($this->status_batch, $mode, $type);
5436 default:
5437 //Simulate previous behavior but should return an error string
5438 return $this->LibStatut($this->status_buy, $mode, $type);
5439 }
5440 }
5441
5442 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5451 public function LibStatut($status, $mode = 0, $type = 0)
5452 {
5453 // phpcs:enable
5454 global $conf, $langs;
5455
5456 $labelStatus = $labelStatusShort = '';
5457
5458 $langs->load('products');
5459 if (isModEnabled('productbatch')) {
5460 $langs->load("productbatch");
5461 }
5462
5463 if ($type == 2) {
5464 switch ($mode) {
5465 case 0:
5466 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
5467 return dolGetStatus($label);
5468 case 1:
5469 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
5470 return dolGetStatus($label);
5471 case 2:
5472 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
5473 case 3:
5474 return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
5475 case 4:
5476 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
5477 case 5:
5478 return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
5479 default:
5480 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
5481 }
5482 }
5483
5484 $statuttrans = empty($status) ? 'status5' : 'status4';
5485
5486 if ($status == 0) {
5487 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5488 if ($type == 0) {
5489 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
5490 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
5491 } elseif ($type == 1) {
5492 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
5493 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
5494 } elseif ($type == 2) {
5495 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
5496 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
5497 }
5498 } elseif ($status == 1) {
5499 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5500 if ($type == 0) {
5501 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
5502 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
5503 } elseif ($type == 1) {
5504 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
5505 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
5506 } elseif ($type == 2) {
5507 $labelStatus = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial'));
5508 $labelStatusShort = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort'));
5509 }
5510 } elseif ($type == 2 && $status == 2) {
5511 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
5512 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
5513 }
5514
5515 if ($mode > 6) {
5516 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
5517 } else {
5518 return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
5519 }
5520 }
5521
5522
5528 public function getLibFinished()
5529 {
5530 global $langs;
5531 $langs->load('products');
5532
5533 if (isset($this->finished) && $this->finished >= 0) {
5534 $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
5535 $resql = $this->db->query($sql);
5536 if ($resql && $this->db->num_rows($resql) > 0) {
5537 $res = $this->db->fetch_array($resql);
5538 $label = $langs->trans($res['label']);
5539 $this->db->free($resql);
5540 return $label;
5541 } else {
5542 $this->error = $this->db->error().' sql='.$sql;
5543 dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
5544 return -1;
5545 }
5546 }
5547
5548 return '';
5549 }
5550
5551
5552 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5569 public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
5570 {
5571 // phpcs:enable
5572 if ($id_entrepot) {
5573 $this->db->begin();
5574
5575 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5576
5577 if ($nbpiece < 0) {
5578 if (!$movement) {
5579 $movement = 1;
5580 }
5581 $nbpiece = abs($nbpiece);
5582 }
5583
5584 $op[0] = "+".trim($nbpiece);
5585 $op[1] = "-".trim($nbpiece);
5586
5587 $movementstock = new MouvementStock($this->db);
5588 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->origin_id
5589 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
5590
5591 if ($result >= 0) {
5592 if ($extrafields) {
5593 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5594 $movementstock->array_options = $array_options;
5595 $movementstock->insertExtraFields();
5596 }
5597 $this->db->commit();
5598 return 1;
5599 } else {
5600 $this->error = $movementstock->error;
5601 $this->errors = $movementstock->errors;
5602
5603 $this->db->rollback();
5604 return -1;
5605 }
5606 }
5607
5608 return -1;
5609 }
5610
5611 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5632 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)
5633 {
5634 // phpcs:enable
5635 if ($id_entrepot) {
5636 $this->db->begin();
5637
5638 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5639
5640 if ($nbpiece < 0) {
5641 if (!$movement) {
5642 $movement = 1;
5643 }
5644 $nbpiece = abs($nbpiece);
5645 }
5646
5647 $op[0] = "+".trim($nbpiece);
5648 $op[1] = "-".trim($nbpiece);
5649
5650 $movementstock = new MouvementStock($this->db);
5651 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->fk_origin
5652 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct, 0, $force_update_batch);
5653
5654 if ($result >= 0) {
5655 if ($extrafields) {
5656 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5657 $movementstock->array_options = $array_options;
5658 $movementstock->insertExtraFields();
5659 }
5660 $this->db->commit();
5661 return 1;
5662 } else {
5663 $this->error = $movementstock->error;
5664 $this->errors = $movementstock->errors;
5665
5666 $this->db->rollback();
5667 return -1;
5668 }
5669 }
5670 return -1;
5671 }
5672
5673 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5686 public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
5687 {
5688 // phpcs:enable
5689 global $conf;
5690
5691 $this->stock_reel = 0;
5692 $this->stock_warehouse = array();
5693 $this->stock_theorique = 0;
5694
5695 // Set filter on warehouse status
5696 $warehouseStatus = array();
5697 if (preg_match('/warehouseclosed/', $option)) {
5699 }
5700 if (preg_match('/warehouseopen/', $option)) {
5702 }
5703 if (preg_match('/warehouseinternal/', $option)) {
5704 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
5706 } else {
5708 }
5709 }
5710
5711 $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
5712 $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
5713 $sql .= ", ".$this->db->prefix()."entrepot as w";
5714 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
5715 $sql .= " AND w.rowid = ps.fk_entrepot";
5716 $sql .= " AND ps.fk_product = ".((int) $this->id);
5717 if (count($warehouseStatus)) {
5718 $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
5719 }
5720
5721 $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;
5722
5723 dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
5724 $result = $this->db->query($sql);
5725 if ($result) {
5726 $num = $this->db->num_rows($result);
5727 $i = 0;
5728 if ($num > 0) {
5729 while ($i < $num) {
5730 $row = $this->db->fetch_object($result);
5731 $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
5732 $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
5733 $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
5734 if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
5735 $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
5736 }
5737 $this->stock_reel += $row->reel;
5738 $i++;
5739 }
5740 }
5741 $this->db->free($result);
5742
5743 if (!preg_match('/novirtual/', $option)) {
5744 $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
5745 }
5746
5747 return 1;
5748 } else {
5749 $this->error = $this->db->lasterror();
5750 return -1;
5751 }
5752 }
5753
5754
5755 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5765 public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
5766 {
5767 // phpcs:enable
5768 global $conf, $hookmanager, $action;
5769
5770 $stock_commande_client = 0;
5771 $stock_commande_fournisseur = 0;
5772 $stock_sending_client = 0;
5773 $stock_reception_fournisseur = 0;
5774 $stock_inproduction = 0;
5775
5776 //dol_syslog("load_virtual_stock");
5777
5778 if (isModEnabled('commande')) {
5779 $result = $this->load_stats_commande(0, '1,2', 1);
5780 if ($result < 0) {
5781 dol_print_error($this->db, $this->error);
5782 }
5783 $stock_commande_client = $this->stats_commande['qty'];
5784 }
5785 if (isModEnabled("expedition")) {
5786 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
5787 $filterShipmentStatus = '';
5788 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
5789 $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
5790 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
5791 $filterShipmentStatus = Expedition::STATUS_CLOSED;
5792 }
5793 $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
5794 if ($result < 0) {
5795 dol_print_error($this->db, $this->error);
5796 }
5797 $stock_sending_client = $this->stats_expedition['qty'];
5798 }
5799 if (isModEnabled("supplier_order")) {
5800 $filterStatus = !getDolGlobalString('SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK') ? '3,4' : $conf->global->SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK;
5801 if (isset($includedraftpoforvirtual)) {
5802 $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
5803 }
5804 $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
5805 if ($result < 0) {
5806 dol_print_error($this->db, $this->error);
5807 }
5808 $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
5809 }
5810 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && empty($conf->reception->enabled)) {
5811 // Case module reception is not used
5812 $filterStatus = '4';
5813 if (isset($includedraftpoforvirtual)) {
5814 $filterStatus = '0,'.$filterStatus;
5815 }
5816 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
5817 if ($result < 0) {
5818 dol_print_error($this->db, $this->error);
5819 }
5820 $stock_reception_fournisseur = $this->stats_reception['qty'];
5821 }
5822 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && isModEnabled("reception")) {
5823 // Case module reception is used
5824 $filterStatus = '4';
5825 if (isset($includedraftpoforvirtual)) {
5826 $filterStatus = '0,'.$filterStatus;
5827 }
5828 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock); // Use same tables than when module reception is not used.
5829 if ($result < 0) {
5830 dol_print_error($this->db, $this->error);
5831 }
5832 $stock_reception_fournisseur = $this->stats_reception['qty'];
5833 }
5834 if (isModEnabled('mrp')) {
5835 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
5836 if ($result < 0) {
5837 dol_print_error($this->db, $this->error);
5838 }
5839 $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
5840 }
5841
5842 $this->stock_theorique = $this->stock_reel + $stock_inproduction;
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 $this->stock_theorique += 0;
5849 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
5850 $this->stock_theorique -= $stock_commande_client;
5851 }
5852 // Stock Increase mode
5853 if (getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION') || getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
5854 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5855 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
5856 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5857 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) {
5858 $this->stock_theorique -= $stock_reception_fournisseur;
5859 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
5860 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5861 }
5862
5863 $parameters = array('id'=>$this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
5864 // Note that $action and $object may have been modified by some hooks
5865 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
5866 if ($reshook > 0) {
5867 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
5868 } elseif ($reshook == 0 && isset($hookmanager->resArray['stock_stats_hook'])) {
5869 $this->stock_theorique += $hookmanager->resArray['stock_stats_hook'];
5870 }
5871
5872 return 1;
5873 }
5874
5875
5883 public function loadBatchInfo($batch)
5884 {
5885 $result = array();
5886
5887 $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";
5888 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
5889 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
5890 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
5891 $resql = $this->db->query($sql);
5892 if ($resql) {
5893 $num = $this->db->num_rows($resql);
5894 $i = 0;
5895 while ($i < $num) {
5896 $obj = $this->db->fetch_object($resql);
5897 $result[] = array('batch'=>$batch, 'eatby'=>$this->db->jdate($obj->eatby), 'sellby'=>$this->db->jdate($obj->sellby), 'qty'=>$obj->qty);
5898 $i++;
5899 }
5900 return $result;
5901 } else {
5902 dol_print_error($this->db);
5903 $this->db->rollback();
5904 return array();
5905 }
5906 }
5907
5908 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5916 public function add_photo($sdir, $file)
5917 {
5918 // phpcs:enable
5919 global $conf;
5920
5921 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5922
5923 $result = 0;
5924
5925 $dir = $sdir;
5926 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5927 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
5928 } else {
5929 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
5930 }
5931
5932 dol_mkdir($dir);
5933
5934 $dir_osencoded = $dir;
5935
5936 if (is_dir($dir_osencoded)) {
5937 $originImage = $dir.'/'.$file['name'];
5938
5939 // Cree fichier en taille origine
5940 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
5941
5942 if (file_exists(dol_osencode($originImage))) {
5943 // Create thumbs
5944 $this->addThumbs($originImage);
5945 }
5946 }
5947
5948 if (is_numeric($result) && $result > 0) {
5949 return 1;
5950 } else {
5951 return -1;
5952 }
5953 }
5954
5955 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5962 public function is_photo_available($sdir)
5963 {
5964 // phpcs:enable
5965 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5966 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5967
5968 global $conf;
5969
5970 $dir = $sdir;
5971 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5972 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
5973 } else {
5974 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
5975 }
5976
5977 $nbphoto = 0;
5978
5979 $dir_osencoded = dol_osencode($dir);
5980 if (file_exists($dir_osencoded)) {
5981 $handle = opendir($dir_osencoded);
5982 if (is_resource($handle)) {
5983 while (($file = readdir($handle)) !== false) {
5984 if (!utf8_check($file)) {
5985 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
5986 }
5987 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
5988 return true;
5989 }
5990 }
5991 }
5992 }
5993 return false;
5994 }
5995
5996 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6004 public function liste_photos($dir, $nbmax = 0)
6005 {
6006 // phpcs:enable
6007 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6008 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6009
6010 $nbphoto = 0;
6011 $tabobj = array();
6012
6013 $dir_osencoded = dol_osencode($dir);
6014 $handle = @opendir($dir_osencoded);
6015 if (is_resource($handle)) {
6016 while (($file = readdir($handle)) !== false) {
6017 if (!utf8_check($file)) {
6018 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // readdir returns ISO
6019 }
6020 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6021 $nbphoto++;
6022
6023 // We forge name of thumb.
6024 $photo = $file;
6025 $photo_vignette = '';
6026 $regs = array();
6027 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
6028 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
6029 }
6030
6031 $dirthumb = $dir.'thumbs/';
6032
6033 // Objet
6034 $obj = array();
6035 $obj['photo'] = $photo;
6036 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
6037 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
6038 } else {
6039 $obj['photo_vignette'] = "";
6040 }
6041
6042 $tabobj[$nbphoto - 1] = $obj;
6043
6044 // Do we have to continue with next photo ?
6045 if ($nbmax && $nbphoto >= $nbmax) {
6046 break;
6047 }
6048 }
6049 }
6050
6051 closedir($handle);
6052 }
6053
6054 return $tabobj;
6055 }
6056
6057 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6064 public function delete_photo($file)
6065 {
6066 // phpcs:enable
6067 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6068 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6069
6070 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
6071 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
6072 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
6073
6074 // On efface l'image d'origine
6075 dol_delete_file($file, 0, 0, 0, $this); // For triggers
6076
6077 // Si elle existe, on efface la vignette
6078 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
6079 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
6080 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6081 dol_delete_file($dirthumb.$photo_vignette);
6082 }
6083
6084 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
6085 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6086 dol_delete_file($dirthumb.$photo_vignette);
6087 }
6088 }
6089 }
6090
6091 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6098 public function get_image_size($file)
6099 {
6100 // phpcs:enable
6101 $file_osencoded = dol_osencode($file);
6102 $infoImg = getimagesize($file_osencoded); // Get information on image
6103 $this->imgWidth = $infoImg[0]; // Largeur de l'image
6104 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
6105 }
6106
6107 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6113 public function load_state_board()
6114 {
6115 // phpcs:enable
6116 global $hookmanager;
6117
6118 $this->nb = array();
6119
6120 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6121 $sql .= " FROM ".$this->db->prefix()."product as p";
6122 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6123 // Add where from hooks
6124 if (is_object($hookmanager)) {
6125 $parameters = array();
6126 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6127 $sql .= $hookmanager->resPrint;
6128 }
6129 $sql .= ' GROUP BY fk_product_type';
6130
6131 $resql = $this->db->query($sql);
6132 if ($resql) {
6133 while ($obj = $this->db->fetch_object($resql)) {
6134 if ($obj->fk_product_type == 1) {
6135 $this->nb["services"] = $obj->nb;
6136 } else {
6137 $this->nb["products"] = $obj->nb;
6138 }
6139 }
6140 $this->db->free($resql);
6141 return 1;
6142 } else {
6143 dol_print_error($this->db);
6144 $this->error = $this->db->error();
6145 return -1;
6146 }
6147 }
6148
6154 public function isProduct()
6155 {
6156 return ($this->type == Product::TYPE_PRODUCT ? true : false);
6157 }
6158
6164 public function isService()
6165 {
6166 return ($this->type == Product::TYPE_SERVICE ? true : false);
6167 }
6168
6174 public function isStockManaged()
6175 {
6176 return ($this->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'));
6177 }
6178
6184 public function isMandatoryPeriod()
6185 {
6186 return ($this->mandatory_period == 1 ? true : false);
6187 }
6188
6194 public function hasbatch()
6195 {
6196 return ($this->status_batch > 0 ? true : false);
6197 }
6198
6199
6200 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6209 public function get_barcode($object, $type = '')
6210 {
6211 // phpcs:enable
6212 global $conf;
6213
6214 $result = '';
6215 if (getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
6216 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6217 foreach ($dirsociete as $dirroot) {
6218 $res = dol_include_once($dirroot . getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM').'.php');
6219 if ($res) {
6220 break;
6221 }
6222 }
6223 $var = $conf->global->BARCODE_PRODUCT_ADDON_NUM;
6224 $mod = new $var();
6225
6226 $result = $mod->getNextValue($object, $type);
6227
6228 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6229 }
6230 return $result;
6231 }
6232
6240 public function initAsSpecimen()
6241 {
6242 $now = dol_now();
6243
6244 // Initialize parameters
6245 $this->specimen = 1;
6246 $this->id = 0;
6247 $this->ref = 'PRODUCT_SPEC';
6248 $this->label = 'PRODUCT SPECIMEN';
6249 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
6250 $this->specimen = 1;
6251 $this->country_id = 1;
6252 $this->status = 1;
6253 $this->status_buy = 1;
6254 $this->tobatch = 0;
6255 $this->note_private = 'This is a comment (private)';
6256 $this->note_public = 'This is a comment (public)';
6257 $this->date_creation = $now;
6258 $this->date_modification = $now;
6259
6260 $this->weight = 4;
6261 $this->weight_units = 3;
6262
6263 $this->length = 5;
6264 $this->length_units = 1;
6265 $this->width = 6;
6266 $this->width_units = 0;
6267 $this->height = null;
6268 $this->height_units = null;
6269
6270 $this->surface = 30;
6271 $this->surface_units = 0;
6272 $this->volume = 300;
6273 $this->volume_units = 0;
6274
6275 $this->barcode = -1; // Create barcode automatically
6276 }
6277
6284 public function getLabelOfUnit($type = 'long')
6285 {
6286 global $langs;
6287
6288 if (!$this->fk_unit) {
6289 return '';
6290 }
6291
6292 $langs->load('products');
6293
6294 $label_type = 'label';
6295 if ($type == 'short') {
6296 $label_type = 'short_label';
6297 }
6298
6299 $sql = "SELECT ".$label_type.", code from ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
6300
6301 $resql = $this->db->query($sql);
6302 if ($resql && $this->db->num_rows($resql) > 0) {
6303 $res = $this->db->fetch_array($resql);
6304 $label = ($label_type == 'short_label' ? $res[$label_type] : 'unit'.$res['code']);
6305 $this->db->free($resql);
6306 return $label;
6307 } else {
6308 $this->error = $this->db->error();
6309 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
6310 return -1;
6311 }
6312 }
6313
6314 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6320 public function min_recommended_price()
6321 {
6322 // phpcs:enable
6323 global $conf;
6324
6325 $maxpricesupplier = 0;
6326
6327 if (getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE')) {
6328 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6329 $product_fourn = new ProductFournisseur($this->db);
6330 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
6331
6332 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
6333 foreach ($product_fourn_list as $productfourn) {
6334 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
6335 $maxpricesupplier = $productfourn->fourn_unitprice;
6336 }
6337 }
6338
6339 $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
6340 }
6341 }
6342
6343 return $maxpricesupplier;
6344 }
6345
6346
6357 public function setCategories($categories)
6358 {
6359 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
6360 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
6361 }
6362
6371 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
6372 {
6373 $tables = array(
6374 'product_customer_price',
6375 'product_customer_price_log'
6376 );
6377
6378 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
6379 }
6380
6392 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
6393 {
6394 global $conf;
6395
6396 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
6397 $query = $this->db->query($sql);
6398
6399 $rules = array();
6400
6401 while ($result = $this->db->fetch_object($query)) {
6402 $rules[$result->level] = $result;
6403 }
6404
6405 //Because prices can be based on other level's prices, we temporarily store them
6406 $prices = array(
6407 1 => $baseprice
6408 );
6409
6410 $nbofproducts = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
6411 for ($i = 1; $i <= $nbofproducts; $i++) {
6412 $price = $baseprice;
6413 $price_min = $baseprice;
6414
6415 //We have to make sure it does exist and it is > 0
6416 //First price level only allows changing min_price
6417 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
6418 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
6419 }
6420
6421 $prices[$i] = $price;
6422
6423 //We have to make sure it does exist and it is > 0
6424 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
6425 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
6426 }
6427
6428 //Little check to make sure the price is modified before triggering generation
6429 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
6430 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
6431
6432 if ($check_amount && $check_type) {
6433 continue;
6434 }
6435
6436 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
6437 return -1;
6438 }
6439 }
6440
6441 return 1;
6442 }
6443
6449 public function getRights()
6450 {
6451 global $user;
6452
6453 if ($this->isProduct()) {
6454 return $user->rights->produit;
6455 } else {
6456 return $user->rights->service;
6457 }
6458 }
6459
6466 public function info($id)
6467 {
6468 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
6469 $sql .= " p.fk_user_author, p.fk_user_modif";
6470 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
6471 $sql .= " WHERE p.rowid = ".((int) $id);
6472
6473 $result = $this->db->query($sql);
6474 if ($result) {
6475 if ($this->db->num_rows($result)) {
6476 $obj = $this->db->fetch_object($result);
6477
6478 $this->id = $obj->rowid;
6479 $this->ref = $obj->ref;
6480
6481 $this->user_creation_id = $obj->fk_user_author;
6482 $this->user_modification_id = $obj->fk_user_modif;
6483
6484 $this->date_creation = $this->db->jdate($obj->date_creation);
6485 $this->date_modification = $this->db->jdate($obj->date_modification);
6486 }
6487
6488 $this->db->free($result);
6489 } else {
6490 dol_print_error($this->db);
6491 }
6492 }
6493
6494
6499 public function getProductDurationHours()
6500 {
6501 global $langs;
6502
6503 if (empty($this->duration_value)) {
6504 $this->errors[]='ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
6505 return -1;
6506 }
6507
6508 if ($this->duration_unit == 'i') {
6509 $prodDurationHours = 1. / 60;
6510 }
6511 if ($this->duration_unit == 'h') {
6512 $prodDurationHours = 1.;
6513 }
6514 if ($this->duration_unit == 'd') {
6515 $prodDurationHours = 24.;
6516 }
6517 if ($this->duration_unit == 'w') {
6518 $prodDurationHours = 24. * 7;
6519 }
6520 if ($this->duration_unit == 'm') {
6521 $prodDurationHours = 24. * 30;
6522 }
6523 if ($this->duration_unit == 'y') {
6524 $prodDurationHours = 24. * 365;
6525 }
6526 $prodDurationHours *= $this->duration_value;
6527
6528 return $prodDurationHours;
6529 }
6530
6531
6539 public function getKanbanView($option = '', $arraydata = null)
6540 {
6541 global $langs,$conf;
6542
6543 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6544
6545 $return = '<div class="box-flex-item box-flex-grow-zero">';
6546 $return .= '<div class="info-box info-box-sm">';
6547 $return .= '<div class="info-box-img">';
6548 $label = '';
6549 if ($this->is_photo_available($conf->product->multidir_output[$this->entity])) {
6550 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
6551 $return .= $label;
6552 } else {
6553 if ($this->type == Product::TYPE_PRODUCT) {
6554 $label .= img_picto('', 'product');
6555 } elseif ($this->type == Product::TYPE_SERVICE) {
6556 $label .= img_picto('', 'service');
6557 }
6558 $return .= $label;
6559 }
6560 $return .= '</div>';
6561 $return .= '<div class="info-box-content">';
6562 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
6563 if ($selected >= 0) {
6564 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
6565 }
6566 if (property_exists($this, 'label')) {
6567 $return .= '<br><span class="info-box-label opacitymedium">'.$this->label.'</span>';
6568 }
6569 if (property_exists($this, 'price') && property_exists($this, 'price_ttc')) {
6570 if ($this->price_base_type == 'TTC') {
6571 $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
6572 } else {
6573 if ($this->status) {
6574 $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
6575 }
6576 }
6577 }
6578 $br = 1;
6579 if (property_exists($this, 'stock_reel') && $this->isProduct()) {
6580 $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>';
6581 $br = 0;
6582 }
6583 if (method_exists($this, 'getLibStatut')) {
6584 if ($br) {
6585 $return .= '<br><div class="info-box-status inline-block valignmiddle">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6586 } else {
6587 $return .= '<div class="info-box-status inline-block valignmiddle marginleftonly paddingleft">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6588 }
6589 }
6590 $return .= '</div>';
6591 $return .= '</div>';
6592 $return .= '</div>';
6593 return $return;
6594 }
6595}
6596
6602{
6603 public $picto = 'service';
6604}
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