dolibarr 19.0.4
product.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2001-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2014 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2005-2015 Regis Houssin <regis.houssin@inodbox.com>
5 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
6 * Copyright (C) 2007-2011 Jean Heimburger <jean@tiaris.info>
7 * Copyright (C) 2010-2018 Juanjo Menent <jmenent@2byte.es>
8 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
9 * Copyright (C) 2013-2014 Cedric GROSS <c.gross@kreiz-it.fr>
10 * Copyright (C) 2013-2016 Marcos García <marcosgdf@gmail.com>
11 * Copyright (C) 2011-2021 Open-DSI <support@open-dsi.fr>
12 * Copyright (C) 2014 Henry Florian <florian.henry@open-concept.pro>
13 * Copyright (C) 2014-2016 Philippe Grand <philippe.grand@atoo-net.com>
14 * Copyright (C) 2014 Ion agorria <ion@agorria.com>
15 * Copyright (C) 2016-2024 Ferran Marcet <fmarcet@2byte.es>
16 * Copyright (C) 2017 Gustavo Novaro
17 * Copyright (C) 2019-2023 Frédéric France <frederic.france@netlogic.fr>
18 * Copyright (C) 2023 Benjamin Falière <benjamin.faliere@altairis.fr>
19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 3 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program. If not, see <https://www.gnu.org/licenses/>.
32 */
33
39require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
40require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
41require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
42require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
43require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
44
48class Product extends CommonObject
49{
53 public $element = 'product';
54
58 public $table_element = 'product';
59
63 public $fk_element = 'fk_product';
64
68 protected $childtables = array(
69 'supplier_proposaldet' => array('name' => 'SupplierProposal', 'parent' => 'supplier_proposal', 'parentkey' => 'fk_supplier_proposal'),
70 'propaldet' => array('name' => 'Proposal', 'parent' => 'propal', 'parentkey' => 'fk_propal'),
71 'commandedet' => array('name' => 'Order', 'parent' => 'commande', 'parentkey' => 'fk_commande'),
72 'facturedet' => array('name' => 'Invoice', 'parent' => 'facture', 'parentkey' => 'fk_facture'),
73 'contratdet' => array('name' => 'Contract', 'parent' => 'contrat', 'parentkey' => 'fk_contrat'),
74 'facture_fourn_det' => array('name' => 'SupplierInvoice', 'parent' => 'facture_fourn', 'parentkey' => 'fk_facture_fourn'),
75 'commande_fournisseurdet' => array('name' => 'SupplierOrder', 'parent' => 'commande_fournisseur', 'parentkey' => 'fk_commande'),
76 'mrp_production' => array('name' => 'Mo', 'parent' => 'mrp_mo', 'parentkey' => 'fk_mo' ),
77 'bom_bom' => array('name' => 'BOM'),
78 'bom_bomline' => array('name' => 'BOMLine', 'parent' => 'bom_bom', 'parentkey' => 'fk_bom'),
79 );
80
86 public $ismultientitymanaged = 1;
87
91 public $isextrafieldmanaged = 1;
92
96 public $picto = 'product';
97
101 protected $table_ref_field = 'ref';
102
103 public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.xpm|\.xbm'; // See also into images.lib.php
104
109 public $libelle;
110
116 public $label;
117
123 public $description;
124
130 public $other;
131
137 public $type = self::TYPE_PRODUCT;
138
144 public $price;
145
146 public $price_formated; // used by takepos/ajax/ajax.php
147
153 public $price_ttc;
154
155 public $price_ttc_formated; // used by takepos/ajax/ajax.php
156
162 public $price_min;
163
169 public $price_min_ttc;
170
175 public $price_base_type;
176
178 public $multiprices = array();
179 public $multiprices_ttc = array();
180 public $multiprices_base_type = array();
181 public $multiprices_default_vat_code = array();
182 public $multiprices_min = array();
183 public $multiprices_min_ttc = array();
184 public $multiprices_tva_tx = array();
185 public $multiprices_recuperableonly = array();
186
189 public $prices_by_qty = array();
190 public $prices_by_qty_id = array();
191 public $prices_by_qty_list = array();
192
196 public $level;
197
199 public $multilangs = array();
200
203
205 public $tva_tx;
206
210 public $tva_npr = 0;
211
214
217 public $localtax2_tx;
218 public $localtax1_type;
219 public $localtax2_type;
220
221 // Properties set by get_buyprice() for return
222
223 public $desc_supplier;
224 public $vatrate_supplier;
225 public $default_vat_code_supplier;
226 public $fourn_multicurrency_price;
227 public $fourn_multicurrency_unitprice;
228 public $fourn_multicurrency_tx;
229 public $fourn_multicurrency_id;
230 public $fourn_multicurrency_code;
231 public $packaging;
232
233
234 public $lifetime; // In seconds
235
236 public $qc_frequency;
237
243 public $stock_reel = 0;
244
250 public $stock_theorique;
251
257 public $cost_price;
258
264 public $pmp;
265
271 public $seuil_stock_alerte = 0;
272
276 public $desiredstock = 0;
277
289 public $duration;
290
295
301 public $status = 0;
302
309 public $tosell;
310
316 public $status_buy = 0;
317
324 public $tobuy;
325
331 public $finished;
332
338 public $fk_default_bom;
339
345 public $product_fourn_price_id;
346
352 public $buyprice;
353
359 public $tobatch;
360
361
367 public $status_batch = 0;
368
374 public $batch_mask = '';
375
381 public $customcode;
382
388 public $url;
389
391 public $weight;
392 public $weight_units; // scale -3, 0, 3, 6
393 public $length;
394 public $length_units; // scale -3, 0, 3, 6
395 public $width;
396 public $width_units; // scale -3, 0, 3, 6
397 public $height;
398 public $height_units; // scale -3, 0, 3, 6
399 public $surface;
400 public $surface_units; // scale -3, 0, 3, 6
401 public $volume;
402 public $volume_units; // scale -3, 0, 3, 6
403
404 public $net_measure;
405 public $net_measure_units; // scale -3, 0, 3, 6
406
407 public $accountancy_code_sell;
408 public $accountancy_code_sell_intra;
409 public $accountancy_code_sell_export;
410 public $accountancy_code_buy;
411 public $accountancy_code_buy_intra;
412 public $accountancy_code_buy_export;
413
417 public $barcode;
418
422 public $barcode_type;
423
427 public $barcode_type_code;
428
429 public $stats_propale = array();
430 public $stats_commande = array();
431 public $stats_contrat = array();
432 public $stats_facture = array();
433 public $stats_proposal_supplier = array();
434 public $stats_commande_fournisseur = array();
435 public $stats_expedition = array();
436 public $stats_reception = array();
437 public $stats_mo = array();
438 public $stats_bom = array();
439 public $stats_mrptoconsume = array();
440 public $stats_mrptoproduce = array();
441 public $stats_facturerec = array();
442 public $stats_facture_fournisseur = array();
443
445 public $imgWidth;
446 public $imgHeight;
447
451 public $date_creation;
452
456 public $date_modification;
457
460
463
464 public $nbphoto = 0;
465
467 public $stock_warehouse = array();
468
472 public $fk_default_warehouse;
476 public $fk_price_expression;
477
478 /* To store supplier price found */
479 public $fourn_qty;
480 public $fourn_pu;
481 public $fourn_price_base_type;
482 public $fourn_socid;
483
489
493 public $ref_supplier;
494
500 public $fk_unit;
501
507 public $price_autogen = 0;
508
514 public $supplierprices;
515
521 public $sousprods = array();
522
526 public $res;
527
528
534 public $is_object_used;
535
536 public $is_sousproduit_qty;
537 public $is_sousproduit_incdec;
538
539 public $mandatory_period;
540
541
570 public $fields = array(
571 'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'index'=>1, 'position'=>1, 'comment'=>'Id'),
572 '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'),
573 'entity' =>array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'default'=>1, 'notnull'=>1, 'index'=>1, 'position'=>5),
574 'label' =>array('type'=>'varchar(255)', 'label'=>'Label', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>2, 'position'=>15, 'csslist'=>'tdoverflowmax250'),
575 'barcode' =>array('type'=>'varchar(255)', 'label'=>'Barcode', 'enabled'=>'isModEnabled("barcode")', 'position'=>20, 'visible'=>-1, 'showoncombobox'=>3),
576 'fk_barcode_type' => array('type'=>'integer', 'label'=>'BarcodeType', 'enabled'=>'1', 'position'=>21, 'notnull'=>0, 'visible'=>-1,),
577 'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>61),
578 'note' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>62),
579 'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>500),
580 'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>501),
581 //'date_valid' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>502),
582 'fk_user_author'=>array('type'=>'integer', 'label'=>'UserAuthor', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>510, 'foreignkey'=>'llx_user.rowid'),
583 'fk_user_modif' =>array('type'=>'integer', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>511),
584 //'fk_user_valid' =>array('type'=>'integer', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>512),
585 'localtax1_tx' => array('type'=>'double(6,3)', 'label'=>'Localtax1tx', 'enabled'=>'1', 'position'=>150, 'notnull'=>0, 'visible'=>-1,),
586 'localtax1_type' => array('type'=>'varchar(10)', 'label'=>'Localtax1type', 'enabled'=>'1', 'position'=>155, 'notnull'=>1, 'visible'=>-1,),
587 'localtax2_tx' => array('type'=>'double(6,3)', 'label'=>'Localtax2tx', 'enabled'=>'1', 'position'=>160, 'notnull'=>0, 'visible'=>-1,),
588 'localtax2_type' => array('type'=>'varchar(10)', 'label'=>'Localtax2type', 'enabled'=>'1', 'position'=>165, 'notnull'=>1, 'visible'=>-1,),
589 'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'index'=>0, 'position'=>1000),
590 //'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')),
591 //'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')),
592 'mandatory_period' => array('type'=>'integer', 'label'=>'mandatoryperiod', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>0, 'index'=>1, 'position'=>1000),
593 );
594
598 const TYPE_PRODUCT = 0;
602 const TYPE_SERVICE = 1;
603
608 const SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY = 1;
609 const SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY = 2;
610 const SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT = 3;
611
612
618 public function __construct($db)
619 {
620 $this->db = $db;
621 $this->canvas = '';
622 }
623
629 public function check()
630 {
631 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
632 $this->ref = trim($this->ref);
633 } else {
634 $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
635 }
636
637 $err = 0;
638 if (dol_strlen(trim($this->ref)) == 0) {
639 $err++;
640 }
641
642 if (dol_strlen(trim($this->label)) == 0) {
643 $err++;
644 }
645
646 if ($err > 0) {
647 return 0;
648 } else {
649 return 1;
650 }
651 }
652
660 public function create($user, $notrigger = 0)
661 {
662 global $conf, $langs;
663
664 $error = 0;
665
666 // Clean parameters
667 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
668 $this->ref = trim($this->ref);
669 } else {
670 $this->ref = dol_sanitizeFileName(dol_string_nospecial(trim($this->ref)));
671 }
672 $this->label = trim($this->label);
673 $this->price_ttc = price2num($this->price_ttc);
674 $this->price = price2num($this->price);
675 $this->price_min_ttc = price2num($this->price_min_ttc);
676 $this->price_min = price2num($this->price_min);
677 if (empty($this->tva_tx)) {
678 $this->tva_tx = 0;
679 }
680 if (empty($this->tva_npr)) {
681 $this->tva_npr = 0;
682 }
683 //Local taxes
684 if (empty($this->localtax1_tx)) {
685 $this->localtax1_tx = 0;
686 }
687 if (empty($this->localtax2_tx)) {
688 $this->localtax2_tx = 0;
689 }
690 if (empty($this->localtax1_type)) {
691 $this->localtax1_type = '0';
692 }
693 if (empty($this->localtax2_type)) {
694 $this->localtax2_type = '0';
695 }
696 if (empty($this->price)) {
697 $this->price = 0;
698 }
699 if (empty($this->price_min)) {
700 $this->price_min = 0;
701 }
702 // Price by quantity
703 if (empty($this->price_by_qty)) {
704 $this->price_by_qty = 0;
705 }
706
707 if (empty($this->status)) {
708 $this->status = 0;
709 }
710 if (empty($this->status_buy)) {
711 $this->status_buy = 0;
712 }
713
714 $price_ht = 0;
715 $price_ttc = 0;
716 $price_min_ht = 0;
717 $price_min_ttc = 0;
718
719 //
720 if ($this->price_base_type == 'TTC' && $this->price_ttc > 0) {
721 $price_ttc = price2num($this->price_ttc, 'MU');
722 $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)), 'MU');
723 }
724
725 //
726 if ($this->price_base_type != 'TTC' && $this->price > 0) {
727 $price_ht = price2num($this->price, 'MU');
728 $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)), 'MU');
729 }
730
731 //
732 if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC')) {
733 $price_min_ttc = price2num($this->price_min_ttc, 'MU');
734 $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)), 'MU');
735 }
736
737 //
738 if (($this->price_min > 0) && ($this->price_base_type != 'TTC')) {
739 $price_min_ht = price2num($this->price_min, 'MU');
740 $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)), 'MU');
741 }
742
743 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
744 $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
745 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
746 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
747 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
748 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
749
750 // Barcode value
751 $this->barcode = trim($this->barcode);
752 $this->mandatory_period = empty($this->mandatory_period) ? 0 : $this->mandatory_period;
753 // Check parameters
754 if (empty($this->label)) {
755 $this->error = 'ErrorMandatoryParametersNotProvided';
756 return -1;
757 }
758
759 if (empty($this->ref) || $this->ref == 'auto') {
760 // Load object modCodeProduct
761 $module = getDolGlobalString('PRODUCT_CODEPRODUCT_ADDON', 'mod_codeproduct_leopard');
762 if ($module != 'mod_codeproduct_leopard') { // Do not load module file for leopard
763 if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
764 $module = substr($module, 0, dol_strlen($module) - 4);
765 }
766 dol_include_once('/core/modules/product/'.$module.'.php');
767 $modCodeProduct = new $module();
768 if (!empty($modCodeProduct->code_auto)) {
769 $this->ref = $modCodeProduct->getNextValue($this, $this->type);
770 }
771 unset($modCodeProduct);
772 }
773
774 if (empty($this->ref)) {
775 $this->error = 'ProductModuleNotSetupForAutoRef';
776 return -2;
777 }
778 }
779
780 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);
781
782 $now = dol_now();
783
784 $this->db->begin();
785
786 // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
787 if ($this->barcode == -1) {
788 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
789 }
790
791 // Check more parameters
792 // If error, this->errors[] is filled
793 $result = $this->verify();
794
795 if ($result >= 0) {
796 $sql = "SELECT count(*) as nb";
797 $sql .= " FROM ".$this->db->prefix()."product";
798 $sql .= " WHERE entity IN (".getEntity('product').")";
799 $sql .= " AND ref = '".$this->db->escape($this->ref)."'";
800
801 $result = $this->db->query($sql);
802 if ($result) {
803 $obj = $this->db->fetch_object($result);
804 if ($obj->nb == 0) {
805 // Produit non deja existant
806 $sql = "INSERT INTO ".$this->db->prefix()."product (";
807 $sql .= "datec";
808 $sql .= ", entity";
809 $sql .= ", ref";
810 $sql .= ", ref_ext";
811 $sql .= ", price_min";
812 $sql .= ", price_min_ttc";
813 $sql .= ", label";
814 $sql .= ", fk_user_author";
815 $sql .= ", fk_product_type";
816 $sql .= ", price";
817 $sql .= ", price_ttc";
818 $sql .= ", price_base_type";
819 $sql .= ", tobuy";
820 $sql .= ", tosell";
821 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
822 $sql .= ", accountancy_code_buy";
823 $sql .= ", accountancy_code_buy_intra";
824 $sql .= ", accountancy_code_buy_export";
825 $sql .= ", accountancy_code_sell";
826 $sql .= ", accountancy_code_sell_intra";
827 $sql .= ", accountancy_code_sell_export";
828 }
829 $sql .= ", canvas";
830 $sql .= ", finished";
831 $sql .= ", tobatch";
832 $sql .= ", batch_mask";
833 $sql .= ", fk_unit";
834 $sql .= ", mandatory_period";
835 $sql .= ") VALUES (";
836 $sql .= "'".$this->db->idate($now)."'";
837 $sql .= ", ".(!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
838 $sql .= ", '".$this->db->escape($this->ref)."'";
839 $sql .= ", ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
840 $sql .= ", ".price2num($price_min_ht);
841 $sql .= ", ".price2num($price_min_ttc);
842 $sql .= ", ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
843 $sql .= ", ".((int) $user->id);
844 $sql .= ", ".((int) $this->type);
845 $sql .= ", ".price2num($price_ht, 'MT');
846 $sql .= ", ".price2num($price_ttc, 'MT');
847 $sql .= ", '".$this->db->escape($this->price_base_type)."'";
848 $sql .= ", ".((int) $this->status);
849 $sql .= ", ".((int) $this->status_buy);
850 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
851 $sql .= ", '".$this->db->escape($this->accountancy_code_buy)."'";
852 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_intra)."'";
853 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_export)."'";
854 $sql .= ", '".$this->db->escape($this->accountancy_code_sell)."'";
855 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
856 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
857 }
858 $sql .= ", '".$this->db->escape($this->canvas)."'";
859 $sql .= ", ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'NULL' : (int) $this->finished);
860 $sql .= ", ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : ((int) $this->status_batch));
861 $sql .= ", '".$this->db->escape($this->batch_mask)."'";
862 $sql .= ", ".($this->fk_unit > 0 ? ((int) $this->fk_unit) : 'NULL');
863 $sql .= ", '".$this->db->escape($this->mandatory_period)."'";
864 $sql .= ")";
865
866 dol_syslog(get_class($this)."::Create", LOG_DEBUG);
867 $result = $this->db->query($sql);
868 if ($result) {
869 $id = $this->db->last_insert_id($this->db->prefix()."product");
870
871 if ($id > 0) {
872 $this->id = $id;
873 $this->price = $price_ht;
874 $this->price_ttc = $price_ttc;
875 $this->price_min = $price_min_ht;
876 $this->price_min_ttc = $price_min_ttc;
877
878 $result = $this->_log_price($user);
879 if ($result > 0) {
880 if ($this->update($id, $user, true, 'add') <= 0) {
881 $error++;
882 }
883 } else {
884 $error++;
885 $this->error = $this->db->lasterror();
886 }
887
888 // update accountancy for this entity
889 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
890 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " .((int) $this->id) . " AND entity = " . ((int) $conf->entity));
891
892 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
893 $sql .= " fk_product";
894 $sql .= ", entity";
895 $sql .= ", accountancy_code_buy";
896 $sql .= ", accountancy_code_buy_intra";
897 $sql .= ", accountancy_code_buy_export";
898 $sql .= ", accountancy_code_sell";
899 $sql .= ", accountancy_code_sell_intra";
900 $sql .= ", accountancy_code_sell_export";
901 $sql .= ") VALUES (";
902 $sql .= $this->id;
903 $sql .= ", " . $conf->entity;
904 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
905 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
906 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
907 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
908 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
909 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
910 $sql .= ")";
911 $result = $this->db->query($sql);
912 if (!$result) {
913 $error++;
914 $this->error = 'ErrorFailedToInsertAccountancyForEntity';
915 }
916 }
917 } else {
918 $error++;
919 $this->error = 'ErrorFailedToGetInsertedId';
920 }
921 } else {
922 $error++;
923 $this->error = $this->db->lasterror();
924 }
925 } else {
926 // Product already exists with this ref
927 $langs->load("products");
928 $error++;
929 $this->error = "ErrorProductAlreadyExists";
930 dol_syslog(get_class($this)."::Create fails, ref ".$this->ref." already exists");
931 }
932 } else {
933 $error++;
934 $this->error = $this->db->lasterror();
935 }
936
937 if (!$error && !$notrigger) {
938 // Call trigger
939 $result = $this->call_trigger('PRODUCT_CREATE', $user);
940 if ($result < 0) {
941 $error++;
942 }
943 // End call triggers
944 }
945
946 if (!$error) {
947 $this->db->commit();
948 return $this->id;
949 } else {
950 $this->db->rollback();
951 return -$error;
952 }
953 } else {
954 $this->db->rollback();
955 dol_syslog(get_class($this)."::Create fails verify ".join(',', $this->errors), LOG_WARNING);
956 return -3;
957 }
958 }
959
960
967 public function verify()
968 {
969 global $langs;
970
971 $this->errors = array();
972
973 $result = 0;
974 $this->ref = trim($this->ref);
975
976 if (!$this->ref) {
977 $this->errors[] = 'ErrorBadRef';
978 $result = -2;
979 }
980
981 $arrayofnonnegativevalue = array('weight'=>'Weight', 'width'=>'Width', 'height'=>'Height', 'length'=>'Length', 'surface'=>'Surface', 'volume'=>'Volume');
982 foreach ($arrayofnonnegativevalue as $key => $value) {
983 if (property_exists($this, $key) && !empty($this->$key) && ($this->$key < 0)) {
984 $langs->loadLangs(array("main", "other"));
985 $this->error = $langs->trans("FieldCannotBeNegative", $langs->transnoentitiesnoconv($value));
986 $this->errors[] = $this->error;
987 $result = -4;
988 }
989 }
990
991 $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
992 if ($rescode) {
993 if ($rescode == -1) {
994 $this->errors[] = 'ErrorBadBarCodeSyntax';
995 } elseif ($rescode == -2) {
996 $this->errors[] = 'ErrorBarCodeRequired';
997 } elseif ($rescode == -3) {
998 // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
999 $this->errors[] = 'ErrorBarCodeAlreadyUsed';
1000 }
1001
1002 $result = -3;
1003 }
1004
1005 return $result;
1006 }
1007
1008 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1019 public function check_barcode($valuetotest, $typefortest)
1020 {
1021 // phpcs:enable
1022 global $conf;
1023 if (isModEnabled('barcode') && getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
1024 $module = strtolower($conf->global->BARCODE_PRODUCT_ADDON_NUM);
1025
1026 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
1027 foreach ($dirsociete as $dirroot) {
1028 $res = dol_include_once($dirroot.$module.'.php');
1029 if ($res) {
1030 break;
1031 }
1032 }
1033
1034 $mod = new $module();
1035
1036 dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
1037 $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
1038 return $result;
1039 } else {
1040 return 0;
1041 }
1042 }
1043
1055 public function update($id, $user, $notrigger = false, $action = 'update', $updatetype = false)
1056 {
1057 global $langs, $conf, $hookmanager;
1058
1059 $error = 0;
1060
1061 // Check parameters
1062 if (!$this->label) {
1063 $this->label = 'MISSING LABEL';
1064 }
1065
1066 // Clean parameters
1067 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
1068 $this->ref = trim($this->ref);
1069 } else {
1070 $this->ref = dol_string_nospecial(trim($this->ref));
1071 }
1072 $this->label = trim($this->label);
1073 $this->description = trim($this->description);
1074 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : null);
1075 $this->note_public = (isset($this->note_public) ? trim($this->note_public) : null);
1076 $this->net_measure = price2num($this->net_measure);
1077 $this->net_measure_units = trim($this->net_measure_units);
1078 $this->weight = price2num($this->weight);
1079 $this->weight_units = trim($this->weight_units);
1080 $this->length = price2num($this->length);
1081 $this->length_units = trim($this->length_units);
1082 $this->width = price2num($this->width);
1083 $this->width_units = trim($this->width_units);
1084 $this->height = price2num($this->height);
1085 $this->height_units = trim($this->height_units);
1086 $this->surface = price2num($this->surface);
1087 $this->surface_units = trim($this->surface_units);
1088 $this->volume = price2num($this->volume);
1089 $this->volume_units = trim($this->volume_units);
1090
1091 // set unit not defined
1092 if (is_numeric($this->length_units)) {
1093 $this->width_units = $this->length_units; // Not used yet
1094 }
1095 if (is_numeric($this->length_units)) {
1096 $this->height_units = $this->length_units; // Not used yet
1097 }
1098
1099 // Automated compute surface and volume if not filled
1100 if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
1101 $this->surface = $this->length * $this->width;
1102 $this->surface_units = measuring_units_squared($this->length_units);
1103 }
1104 if (empty($this->volume) && !empty($this->surface) && !empty($this->height) && $this->length_units == $this->height_units) {
1105 $this->volume = $this->surface * $this->height;
1106 $this->volume_units = measuring_units_cubed($this->height_units);
1107 }
1108
1109 if (empty($this->tva_tx)) {
1110 $this->tva_tx = 0;
1111 }
1112 if (empty($this->tva_npr)) {
1113 $this->tva_npr = 0;
1114 }
1115 if (empty($this->localtax1_tx)) {
1116 $this->localtax1_tx = 0;
1117 }
1118 if (empty($this->localtax2_tx)) {
1119 $this->localtax2_tx = 0;
1120 }
1121 if (empty($this->localtax1_type)) {
1122 $this->localtax1_type = '0';
1123 }
1124 if (empty($this->localtax2_type)) {
1125 $this->localtax2_type = '0';
1126 }
1127 if (empty($this->status)) {
1128 $this->status = 0;
1129 }
1130 if (empty($this->status_buy)) {
1131 $this->status_buy = 0;
1132 }
1133
1134 if (empty($this->country_id)) {
1135 $this->country_id = 0;
1136 }
1137
1138 if (empty($this->state_id)) {
1139 $this->state_id = 0;
1140 }
1141
1142 // Barcode value
1143 $this->barcode = trim($this->barcode);
1144
1145 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1146 $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
1147 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1148 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1149 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1150 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1151
1152
1153 $this->db->begin();
1154
1155 $result = 0;
1156 // Check name is required and codes are ok or unique. If error, this->errors[] is filled
1157 if ($action != 'add') {
1158 $result = $this->verify(); // We don't check when update called during a create because verify was already done
1159 } else {
1160 // we can continue
1161 $result = 0;
1162 }
1163
1164 if ($result >= 0) {
1165 // $this->oldcopy should have been set by the caller of update (here properties were already modified)
1166 if (empty($this->oldcopy)) {
1167 $this->oldcopy = dol_clone($this, 1);
1168 }
1169 // Test if batch management is activated on existing product
1170 // If yes, we create missing entries into product_batch
1171 if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
1172 //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
1173 $valueforundefinedlot = '000000';
1174 if (getDolGlobalString('STOCK_DEFAULT_BATCH')) {
1175 $valueforundefinedlot = $conf->global->STOCK_DEFAULT_BATCH;
1176 }
1177
1178 dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
1179
1180 $this->load_stock();
1181 foreach ($this->stock_warehouse as $idW => $ObjW) { // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
1182 $qty_batch = 0;
1183 foreach ($ObjW->detail_batch as $detail) { // Each lines of detail in product_batch of the current $ObjW = product_stock
1184 if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
1185 // We discard this line, we will create it later
1186 $sqlclean = "DELETE FROM ".$this->db->prefix()."product_batch WHERE batch in('Undefined', '".$this->db->escape($valueforundefinedlot)."') AND fk_product_stock = ".((int) $ObjW->id);
1187 $result = $this->db->query($sqlclean);
1188 if (!$result) {
1189 dol_print_error($this->db);
1190 exit;
1191 }
1192 continue;
1193 }
1194
1195 $qty_batch += $detail->qty;
1196 }
1197 // Quantities in batch details are not same as stock quantity,
1198 // so we add a default batch record to complete and get same qty in parent and child table
1199 if ($ObjW->real != $qty_batch) {
1200 $ObjBatch = new Productbatch($this->db);
1201 $ObjBatch->batch = $valueforundefinedlot;
1202 $ObjBatch->qty = ($ObjW->real - $qty_batch);
1203 $ObjBatch->fk_product_stock = $ObjW->id;
1204
1205 if ($ObjBatch->create($user, 1) < 0) {
1206 $error++;
1207 $this->errors = $ObjBatch->errors;
1208 } else {
1209 // we also add lot record if not exist
1210 $ObjLot = new Productlot($this->db);
1211 if ($ObjLot->fetch(0, $this->id, $valueforundefinedlot) == 0) {
1212 $ObjLot->fk_product = $this->id;
1213 $ObjLot->entity = $this->entity;
1214 $ObjLot->fk_user_creat = $user->id;
1215 $ObjLot->batch = $valueforundefinedlot;
1216 if ($ObjLot->create($user, true) < 0) {
1217 $error++;
1218 $this->errors = $ObjLot->errors;
1219 }
1220 }
1221 }
1222 }
1223 }
1224 }
1225
1226 // For automatic creation
1227 if ($this->barcode == -1) {
1228 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1229 }
1230
1231 $sql = "UPDATE ".$this->db->prefix()."product";
1232 $sql .= " SET label = '".$this->db->escape($this->label)."'";
1233
1234 if ($updatetype && ($this->isProduct() || $this->isService())) {
1235 $sql .= ", fk_product_type = ".((int) $this->type);
1236 }
1237
1238 $sql .= ", ref = '".$this->db->escape($this->ref)."'";
1239 $sql .= ", ref_ext = ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1240 $sql .= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
1241 $sql .= ", tva_tx = ".((float) $this->tva_tx);
1242 $sql .= ", recuperableonly = ".((int) $this->tva_npr);
1243 $sql .= ", localtax1_tx = ".((float) $this->localtax1_tx);
1244 $sql .= ", localtax2_tx = ".((float) $this->localtax2_tx);
1245 $sql .= ", localtax1_type = ".($this->localtax1_type != '' ? "'".$this->db->escape($this->localtax1_type)."'" : "'0'");
1246 $sql .= ", localtax2_type = ".($this->localtax2_type != '' ? "'".$this->db->escape($this->localtax2_type)."'" : "'0'");
1247
1248 $sql .= ", barcode = ".(empty($this->barcode) ? "null" : "'".$this->db->escape($this->barcode)."'");
1249 $sql .= ", fk_barcode_type = ".(empty($this->barcode_type) ? "null" : $this->db->escape($this->barcode_type));
1250
1251 $sql .= ", tosell = ".(int) $this->status;
1252 $sql .= ", tobuy = ".(int) $this->status_buy;
1253 $sql .= ", tobatch = ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
1254 $sql .= ", batch_mask = '".$this->db->escape($this->batch_mask)."'";
1255
1256 $sql .= ", finished = ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? "null" : (int) $this->finished);
1257 $sql .= ", fk_default_bom = ".((!isset($this->fk_default_bom) || $this->fk_default_bom < 0 || $this->fk_default_bom == '') ? "null" : (int) $this->fk_default_bom);
1258 $sql .= ", net_measure = ".($this->net_measure != '' ? "'".$this->db->escape($this->net_measure)."'" : 'null');
1259 $sql .= ", net_measure_units = ".($this->net_measure_units != '' ? "'".$this->db->escape($this->net_measure_units)."'" : 'null');
1260 $sql .= ", weight = ".($this->weight != '' ? "'".$this->db->escape($this->weight)."'" : 'null');
1261 $sql .= ", weight_units = ".($this->weight_units != '' ? "'".$this->db->escape($this->weight_units)."'" : 'null');
1262 $sql .= ", length = ".($this->length != '' ? "'".$this->db->escape($this->length)."'" : 'null');
1263 $sql .= ", length_units = ".($this->length_units != '' ? "'".$this->db->escape($this->length_units)."'" : 'null');
1264 $sql .= ", width= ".($this->width != '' ? "'".$this->db->escape($this->width)."'" : 'null');
1265 $sql .= ", width_units = ".($this->width_units != '' ? "'".$this->db->escape($this->width_units)."'" : 'null');
1266 $sql .= ", height = ".($this->height != '' ? "'".$this->db->escape($this->height)."'" : 'null');
1267 $sql .= ", height_units = ".($this->height_units != '' ? "'".$this->db->escape($this->height_units)."'" : 'null');
1268 $sql .= ", surface = ".($this->surface != '' ? "'".$this->db->escape($this->surface)."'" : 'null');
1269 $sql .= ", surface_units = ".($this->surface_units != '' ? "'".$this->db->escape($this->surface_units)."'" : 'null');
1270 $sql .= ", volume = ".($this->volume != '' ? "'".$this->db->escape($this->volume)."'" : 'null');
1271 $sql .= ", volume_units = ".($this->volume_units != '' ? "'".$this->db->escape($this->volume_units)."'" : 'null');
1272 $sql .= ", fk_default_warehouse = ".($this->fk_default_warehouse > 0 ? ((int) $this->fk_default_warehouse) : 'null');
1273 $sql .= ", fk_default_workstation = ".($this->fk_default_workstation > 0 ? ((int) $this->fk_default_workstation) : 'null');
1274 $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (float) $this->seuil_stock_alerte : 'null');
1275 $sql .= ", description = '".$this->db->escape($this->description)."'";
1276 $sql .= ", url = ".($this->url ? "'".$this->db->escape($this->url)."'" : 'null');
1277 $sql .= ", customcode = '".$this->db->escape($this->customcode)."'";
1278 $sql .= ", fk_country = ".($this->country_id > 0 ? (int) $this->country_id : 'null');
1279 $sql .= ", fk_state = ".($this->state_id > 0 ? (int) $this->state_id : 'null');
1280 $sql .= ", lifetime = ".($this->lifetime > 0 ? (int) $this->lifetime : 'null');
1281 $sql .= ", qc_frequency = ".($this->qc_frequency > 0 ? (int) $this->qc_frequency : 'null');
1282 $sql .= ", note = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : 'null');
1283 $sql .= ", note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : 'null');
1284 $sql .= ", duration = '".$this->db->escape($this->duration_value.$this->duration_unit)."'";
1285 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1286 $sql .= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy) . "'";
1287 $sql .= ", accountancy_code_buy_intra = '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1288 $sql .= ", accountancy_code_buy_export = '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1289 $sql .= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell) . "'";
1290 $sql .= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1291 $sql .= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1292 }
1293 $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (float) $this->desiredstock : "null");
1294 $sql .= ", cost_price = ".($this->cost_price != '' ? $this->db->escape($this->cost_price) : 'null');
1295 $sql .= ", fk_unit= ".(!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
1296 $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1);
1297 $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
1298 $sql .= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL');
1299 $sql .= ", mandatory_period = ".($this->mandatory_period);
1300 // stock field is not here because it is a denormalized value from product_stock.
1301 $sql .= " WHERE rowid = ".((int) $id);
1302
1303 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1304
1305 $resql = $this->db->query($sql);
1306 if ($resql) {
1307 $this->id = $id;
1308
1309 // Multilangs
1310 if (getDolGlobalInt('MAIN_MULTILANGS')) {
1311 if ($this->setMultiLangs($user) < 0) {
1312 return -2;
1313 }
1314 }
1315
1316 $action = 'update';
1317
1318 // update accountancy for this entity
1319 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1320 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " . ((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1321
1322 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1323 $sql .= " fk_product";
1324 $sql .= ", entity";
1325 $sql .= ", accountancy_code_buy";
1326 $sql .= ", accountancy_code_buy_intra";
1327 $sql .= ", accountancy_code_buy_export";
1328 $sql .= ", accountancy_code_sell";
1329 $sql .= ", accountancy_code_sell_intra";
1330 $sql .= ", accountancy_code_sell_export";
1331 $sql .= ") VALUES (";
1332 $sql .= $this->id;
1333 $sql .= ", " . $conf->entity;
1334 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1335 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1336 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1337 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1338 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1339 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1340 $sql .= ")";
1341 $result = $this->db->query($sql);
1342 if (!$result) {
1343 $error++;
1344 $this->error = 'ErrorFailedToUpdateAccountancyForEntity';
1345 }
1346 }
1347
1348 if (!$this->hasbatch() && $this->oldcopy->hasbatch()) {
1349 // Selection of all product stock mouvements that contains batchs
1350 $sql = 'SELECT pb.qty, ps.fk_entrepot, pb.batch FROM '.MAIN_DB_PREFIX.'product_batch as pb';
1351 $sql.= ' INNER JOIN '.MAIN_DB_PREFIX.'product_stock as ps ON (ps.rowid = pb.fk_product_stock)';
1352 $sql.= ' WHERE ps.fk_product = '.(int) $this->id;
1353
1354 $resql = $this->db->query($sql);
1355 if ($resql) {
1356 $inventorycode = dol_print_date(dol_now(), '%Y%m%d%H%M%S');
1357
1358 while ($obj = $this->db->fetch_object($resql)) {
1359 $value = $obj->qty;
1360 $fk_entrepot = $obj->fk_entrepot;
1361 $price = 0;
1362 $dlc = '';
1363 $dluo = '';
1364 $batch = $obj->batch;
1365
1366 // To know how to revert stockMouvement (add or remove)
1367 $addOremove = $value > 0 ? 1 : 0; // 1 if remove, 0 if add
1368 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1369 $res = $this->correct_stock_batch($user, $fk_entrepot, abs($value), $addOremove, $label, $price, $dlc, $dluo, $batch, $inventorycode, '', null, 0, null, true);
1370
1371 if ($res > 0) {
1372 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1373 $res = $this->correct_stock($user, $fk_entrepot, abs($value), (int) empty($addOremove), $label, $price, $inventorycode, '', null, 0);
1374 if ($res < 0) {
1375 $error++;
1376 }
1377 } else {
1378 $error++;
1379 }
1380 }
1381 }
1382 }
1383
1384 // Actions on extra fields
1385 if (!$error) {
1386 $result = $this->insertExtraFields();
1387 if ($result < 0) {
1388 $error++;
1389 }
1390 }
1391
1392 if (!$error && !$notrigger) {
1393 // Call trigger
1394 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1395 if ($result < 0) {
1396 $error++;
1397 }
1398 // End call triggers
1399 }
1400
1401 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
1402 // We remove directory
1403 if ($conf->product->dir_output) {
1404 $olddir = $conf->product->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
1405 $newdir = $conf->product->dir_output."/".dol_sanitizeFileName($this->ref);
1406 if (file_exists($olddir)) {
1407 //include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1408 //$res = dol_move($olddir, $newdir);
1409 // do not use dol_move with directory
1410 $res = @rename($olddir, $newdir);
1411 if (!$res) {
1412 $langs->load("errors");
1413 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
1414 $error++;
1415 }
1416 }
1417 }
1418 }
1419
1420 if (!$error) {
1421 if (isModEnabled('variants')) {
1422 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1423
1424 $comb = new ProductCombination($this->db);
1425
1426 foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1427 $currcomb->updateProperties($this, $user);
1428 }
1429 }
1430
1431 $this->db->commit();
1432 return 1;
1433 } else {
1434 $this->db->rollback();
1435 return -$error;
1436 }
1437 } else {
1438 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1439 $langs->load("errors");
1440 if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1441 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1442 } else {
1443 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1444 }
1445 $this->errors[] = $this->error;
1446 $this->db->rollback();
1447 return -1;
1448 } else {
1449 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1450 $this->errors[] = $this->error;
1451 $this->db->rollback();
1452 return -2;
1453 }
1454 }
1455 } else {
1456 $this->db->rollback();
1457 dol_syslog(get_class($this)."::Update fails verify ".join(',', $this->errors), LOG_WARNING);
1458 return -3;
1459 }
1460 }
1461
1469 public function delete(User $user, $notrigger = 0)
1470 {
1471 global $conf, $langs;
1472 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1473
1474 $error = 0;
1475
1476 // Check parameters
1477 if (empty($this->id)) {
1478 $this->error = "Object must be fetched before calling delete";
1479 return -1;
1480 }
1481 if (($this->type == Product::TYPE_PRODUCT && !$user->hasRight('produit', 'supprimer')) || ($this->type == Product::TYPE_SERVICE && !$user->hasRight('service', 'supprimer'))) {
1482 $this->error = "ErrorForbidden";
1483 return 0;
1484 }
1485
1486 $objectisused = $this->isObjectUsed($this->id);
1487 if (empty($objectisused)) {
1488 $this->db->begin();
1489
1490 if (!$error && empty($notrigger)) {
1491 // Call trigger
1492 $result = $this->call_trigger('PRODUCT_DELETE', $user);
1493 if ($result < 0) {
1494 $error++;
1495 }
1496 // End call triggers
1497 }
1498
1499 // Delete from product_batch on product delete
1500 if (!$error) {
1501 $sql = "DELETE FROM ".$this->db->prefix().'product_batch';
1502 $sql .= " WHERE fk_product_stock IN (";
1503 $sql .= "SELECT rowid FROM ".$this->db->prefix().'product_stock';
1504 $sql .= " WHERE fk_product = ".((int) $this->id).")";
1505
1506 $result = $this->db->query($sql);
1507 if (!$result) {
1508 $error++;
1509 $this->errors[] = $this->db->lasterror();
1510 }
1511 }
1512
1513 // Delete all child tables
1514 if (!$error) {
1515 $elements = array('product_fournisseur_price', 'product_price', 'product_lang', 'categorie_product', 'product_stock', 'product_customer_price', 'product_lot'); // product_batch is done before
1516 foreach ($elements as $table) {
1517 if (!$error) {
1518 $sql = "DELETE FROM ".$this->db->prefix().$table;
1519 $sql .= " WHERE fk_product = ".(int) $this->id;
1520
1521 $result = $this->db->query($sql);
1522 if (!$result) {
1523 $error++;
1524 $this->errors[] = $this->db->lasterror();
1525 }
1526 }
1527 }
1528 }
1529
1530 if (!$error) {
1531 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1532 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1533
1534 //If it is a parent product, then we remove the association with child products
1535 $prodcomb = new ProductCombination($this->db);
1536
1537 if ($prodcomb->deleteByFkProductParent($user, $this->id) < 0) {
1538 $error++;
1539 $this->errors[] = 'Error deleting combinations';
1540 }
1541
1542 //We also check if it is a child product
1543 if (!$error && ($prodcomb->fetchByFkProductChild($this->id) > 0) && ($prodcomb->delete($user) < 0)) {
1544 $error++;
1545 $this->errors[] = 'Error deleting child combination';
1546 }
1547 }
1548
1549 // Delete from product_association
1550 if (!$error) {
1551 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
1552 $sql .= " WHERE fk_product_pere = ".(int) $this->id." OR fk_product_fils = ".(int) $this->id;
1553
1554 $result = $this->db->query($sql);
1555 if (!$result) {
1556 $error++;
1557 $this->errors[] = $this->db->lasterror();
1558 }
1559 }
1560
1561 // Remove extrafields
1562 if (!$error) {
1563 $result = $this->deleteExtraFields();
1564 if ($result < 0) {
1565 $error++;
1566 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1567 }
1568 }
1569
1570 // Delete product
1571 if (!$error) {
1572 $sqlz = "DELETE FROM ".$this->db->prefix()."product";
1573 $sqlz .= " WHERE rowid = ".(int) $this->id;
1574
1575 $resultz = $this->db->query($sqlz);
1576 if (!$resultz) {
1577 $error++;
1578 $this->errors[] = $this->db->lasterror();
1579 }
1580 }
1581
1582 // Delete record into ECM index and physically
1583 if (!$error) {
1584 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1585 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1586 if (!$res) {
1587 $error++;
1588 }
1589 }
1590
1591 if (!$error) {
1592 // We remove directory
1593 $ref = dol_sanitizeFileName($this->ref);
1594 if ($conf->product->dir_output) {
1595 $dir = $conf->product->dir_output."/".$ref;
1596 if (file_exists($dir)) {
1598 if (!$res) {
1599 $this->errors[] = 'ErrorFailToDeleteDir';
1600 $error++;
1601 }
1602 }
1603 }
1604 }
1605
1606 if (!$error) {
1607 $this->db->commit();
1608 return 1;
1609 } else {
1610 foreach ($this->errors as $errmsg) {
1611 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1612 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1613 }
1614 $this->db->rollback();
1615 return -$error;
1616 }
1617 } else {
1618 $this->error = "ErrorRecordIsUsedCantDelete";
1619 return 0;
1620 }
1621 }
1622
1629 public function setMultiLangs($user)
1630 {
1631 global $conf, $langs;
1632
1633 $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1634 $current_lang = $langs->getDefaultLang();
1635
1636 foreach ($langs_available as $key => $value) {
1637 if ($key == $current_lang) {
1638 $sql = "SELECT rowid";
1639 $sql .= " FROM ".$this->db->prefix()."product_lang";
1640 $sql .= " WHERE fk_product = ".((int) $this->id);
1641 $sql .= " AND lang = '".$this->db->escape($key)."'";
1642
1643 $result = $this->db->query($sql);
1644
1645 if ($this->db->num_rows($result)) { // if there is already a description line for this language
1646 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1647 $sql2 .= " SET ";
1648 $sql2 .= " label='".$this->db->escape($this->label)."',";
1649 $sql2 .= " description='".$this->db->escape($this->description)."'";
1650 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1651 $sql2 .= ", note='".$this->db->escape($this->other)."'";
1652 }
1653 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1654 } else {
1655 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1656 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1657 $sql2 .= ", note";
1658 }
1659 $sql2 .= ")";
1660 $sql2 .= " VALUES(".$this->id.",'".$this->db->escape($key)."','".$this->db->escape($this->label)."',";
1661 $sql2 .= " '".$this->db->escape($this->description)."'";
1662 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1663 $sql2 .= ", '".$this->db->escape($this->other)."'";
1664 }
1665 $sql2 .= ")";
1666 }
1667 dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
1668 if (!$this->db->query($sql2)) {
1669 $this->error = $this->db->lasterror();
1670 return -1;
1671 }
1672 } elseif (isset($this->multilangs[$key])) {
1673 if (empty($this->multilangs["$key"]["label"])) {
1674 $this->errors[] = $key . ' : ' . $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Label"));
1675 return -1;
1676 }
1677
1678 $sql = "SELECT rowid";
1679 $sql .= " FROM ".$this->db->prefix()."product_lang";
1680 $sql .= " WHERE fk_product = ".((int) $this->id);
1681 $sql .= " AND lang = '".$this->db->escape($key)."'";
1682
1683 $result = $this->db->query($sql);
1684
1685 if ($this->db->num_rows($result)) { // if there is already a description line for this language
1686 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1687 $sql2 .= " SET ";
1688 $sql2 .= " label = '".$this->db->escape($this->multilangs["$key"]["label"])."',";
1689 $sql2 .= " description = '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1690 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1691 $sql2 .= ", note = '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1692 }
1693 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1694 } else {
1695 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1696 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1697 $sql2 .= ", note";
1698 }
1699 $sql2 .= ")";
1700 $sql2 .= " VALUES(".$this->id.",'".$this->db->escape($key)."','".$this->db->escape($this->multilangs["$key"]["label"])."',";
1701 $sql2 .= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1702 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1703 $sql2 .= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1704 }
1705 $sql2 .= ")";
1706 }
1707
1708 // We do not save if main fields are empty
1709 if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
1710 if (!$this->db->query($sql2)) {
1711 $this->error = $this->db->lasterror();
1712 return -1;
1713 }
1714 }
1715 } else {
1716 // language is not current language and we didn't provide a multilang description for this language
1717 }
1718 }
1719
1720 // Call trigger
1721 $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
1722 if ($result < 0) {
1723 $this->error = $this->db->lasterror();
1724 return -1;
1725 }
1726 // End call triggers
1727
1728 return 1;
1729 }
1730
1739 public function delMultiLangs($langtodelete, $user)
1740 {
1741 $sql = "DELETE FROM ".$this->db->prefix()."product_lang";
1742 $sql .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
1743
1744 dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
1745 $result = $this->db->query($sql);
1746 if ($result) {
1747 // Call trigger
1748 $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
1749 if ($result < 0) {
1750 $this->error = $this->db->lasterror();
1751 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1752 return -1;
1753 }
1754 // End call triggers
1755 return 1;
1756 } else {
1757 $this->error = $this->db->lasterror();
1758 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1759 return -1;
1760 }
1761 }
1762
1771 public function setAccountancyCode($type, $value)
1772 {
1773 global $user, $langs, $conf;
1774
1775 $error = 0;
1776
1777 $this->db->begin();
1778
1779 if ($type == 'buy') {
1780 $field = 'accountancy_code_buy';
1781 } elseif ($type == 'buy_intra') {
1782 $field = 'accountancy_code_buy_intra';
1783 } elseif ($type == 'buy_export') {
1784 $field = 'accountancy_code_buy_export';
1785 } elseif ($type == 'sell') {
1786 $field = 'accountancy_code_sell';
1787 } elseif ($type == 'sell_intra') {
1788 $field = 'accountancy_code_sell_intra';
1789 } elseif ($type == 'sell_export') {
1790 $field = 'accountancy_code_sell_export';
1791 } else {
1792 return -1;
1793 }
1794
1795 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ";
1796 $sql .= "$field = '".$this->db->escape($value)."'";
1797 $sql .= " WHERE rowid = ".((int) $this->id);
1798
1799 dol_syslog(__METHOD__, LOG_DEBUG);
1800 $resql = $this->db->query($sql);
1801
1802 if ($resql) {
1803 // Call trigger
1804 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1805 if ($result < 0) {
1806 $error++;
1807 }
1808 // End call triggers
1809
1810 if ($error) {
1811 $this->db->rollback();
1812 return -1;
1813 }
1814
1815 $this->$field = $value;
1816
1817 $this->db->commit();
1818 return 1;
1819 } else {
1820 $this->error = $this->db->lasterror();
1821 $this->db->rollback();
1822 return -1;
1823 }
1824 }
1825
1831 public function getMultiLangs()
1832 {
1833 global $langs;
1834
1835 $current_lang = $langs->getDefaultLang();
1836
1837 $sql = "SELECT lang, label, description, note as other";
1838 $sql .= " FROM ".$this->db->prefix()."product_lang";
1839 $sql .= " WHERE fk_product = ".((int) $this->id);
1840
1841 $result = $this->db->query($sql);
1842 if ($result) {
1843 while ($obj = $this->db->fetch_object($result)) {
1844 //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
1845 if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
1846 $this->label = $obj->label;
1847 $this->description = $obj->description;
1848 $this->other = $obj->other;
1849 }
1850 $this->multilangs["$obj->lang"]["label"] = $obj->label;
1851 $this->multilangs["$obj->lang"]["description"] = $obj->description;
1852 $this->multilangs["$obj->lang"]["other"] = $obj->other;
1853 }
1854 return 1;
1855 } else {
1856 $this->error = "Error: ".$this->db->lasterror()." - ".$sql;
1857 return -1;
1858 }
1859 }
1860
1867 private function getArrayForPriceCompare($level = 0)
1868 {
1869 $testExit = array('multiprices','multiprices_ttc','multiprices_base_type','multiprices_min','multiprices_min_ttc','multiprices_tva_tx','multiprices_recuperableonly');
1870
1871 foreach ($testExit as $field) {
1872 if (!isset($this->$field)) {
1873 return array();
1874 }
1875 $tmparray = $this->$field;
1876 if (!isset($tmparray[$level])) {
1877 return array();
1878 }
1879 }
1880
1881 $lastPrice = array(
1882 'level' => $level ? $level : 1,
1883 'multiprices' => (float) $this->multiprices[$level],
1884 'multiprices_ttc' => (float) $this->multiprices_ttc[$level],
1885 'multiprices_base_type' => $this->multiprices_base_type[$level],
1886 'multiprices_min' => (float) $this->multiprices_min[$level],
1887 'multiprices_min_ttc' => (float) $this->multiprices_min_ttc[$level],
1888 'multiprices_tva_tx' => (float) $this->multiprices_tva_tx[$level],
1889 'multiprices_recuperableonly' => (float) $this->multiprices_recuperableonly[$level],
1890 );
1891
1892 return $lastPrice;
1893 }
1894
1895
1896 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1904 private function _log_price($user, $level = 0)
1905 {
1906 // phpcs:enable
1907 global $conf;
1908
1909 $now = dol_now();
1910
1911 // Clean parameters
1912 if (empty($this->price_by_qty)) {
1913 $this->price_by_qty = 0;
1914 }
1915
1916 // Add new price
1917 $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,";
1918 $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
1919 $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).",";
1920 $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');
1921 $sql .= ")";
1922
1923 dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
1924 $resql = $this->db->query($sql);
1925 if (!$resql) {
1926 $this->error = $this->db->lasterror();
1927 dol_print_error($this->db);
1928 return -1;
1929 } else {
1930 return 1;
1931 }
1932 }
1933
1934
1935 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1943 public function log_price_delete($user, $rowid)
1944 {
1945 // phpcs:enable
1946 $sql = "DELETE FROM ".$this->db->prefix()."product_price_by_qty";
1947 $sql .= " WHERE fk_product_price = ".((int) $rowid);
1948 $resql = $this->db->query($sql);
1949
1950 $sql = "DELETE FROM ".$this->db->prefix()."product_price";
1951 $sql .= " WHERE rowid=".((int) $rowid);
1952 $resql = $this->db->query($sql);
1953 if ($resql) {
1954 return 1;
1955 } else {
1956 $this->error = $this->db->lasterror();
1957 return -1;
1958 }
1959 }
1960
1961
1971 public function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp = 0)
1972 {
1973 global $conf, $hookmanager, $action;
1974
1975 // Call hook if any
1976 if (is_object($hookmanager)) {
1977 $parameters = array('thirdparty_seller'=>$thirdparty_seller, 'thirdparty_buyer' => $thirdparty_buyer, 'pqp' => $pqp);
1978 // Note that $action and $object may have been modified by some hooks
1979 $reshook = $hookmanager->executeHooks('getSellPrice', $parameters, $this, $action);
1980 if ($reshook > 0) {
1981 return $hookmanager->resArray;
1982 }
1983 }
1984
1985 // Update if prices fields are defined
1986 $tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id);
1987 $tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id);
1988 if (empty($tva_tx)) {
1989 $tva_npr = 0;
1990 }
1991
1992 $pu_ht = $this->price;
1993 $pu_ttc = $this->price_ttc;
1994 $price_min = $this->price_min;
1995 $price_base_type = $this->price_base_type;
1996
1997 // If price per segment
1998 if (getDolGlobalString('PRODUIT_MULTIPRICES') && !empty($thirdparty_buyer->price_level)) {
1999 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
2000 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
2001 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
2002 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2003 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
2004 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2005 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2006 }
2007 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2008 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2009 }
2010 if (empty($tva_tx)) {
2011 $tva_npr = 0;
2012 }
2013 }
2014 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
2015 // If price per customer
2016 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2017
2018 $prodcustprice = new ProductCustomerPrice($this->db);
2019
2020 $filter = array('t.fk_product' => $this->id, 't.fk_soc' => $thirdparty_buyer->id);
2021
2022 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2023 if ($result) {
2024 if (count($prodcustprice->lines) > 0) {
2025 $pu_ht = price($prodcustprice->lines[0]->price);
2026 $price_min = price($prodcustprice->lines[0]->price_min);
2027 $pu_ttc = price($prodcustprice->lines[0]->price_ttc);
2028 $price_base_type = $prodcustprice->lines[0]->price_base_type;
2029 $tva_tx = $prodcustprice->lines[0]->tva_tx;
2030 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2031 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
2032 }
2033 $tva_npr = $prodcustprice->lines[0]->recuperableonly;
2034 if (empty($tva_tx)) {
2035 $tva_npr = 0;
2036 }
2037 }
2038 }
2039 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY')) {
2040 // If price per quantity
2041 if ($this->prices_by_qty[0]) {
2042 // yes, this product has some prices per quantity
2043 // Search price into product_price_by_qty from $this->id
2044 foreach ($this->prices_by_qty_list[0] as $priceforthequantityarray) {
2045 if ($priceforthequantityarray['rowid'] != $pqp) {
2046 continue;
2047 }
2048 // We found the price
2049 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2050 $pu_ht = $priceforthequantityarray['unitprice'];
2051 } else {
2052 $pu_ttc = $priceforthequantityarray['unitprice'];
2053 }
2054 break;
2055 }
2056 }
2057 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
2058 // If price per quantity and customer
2059 if ($this->prices_by_qty[$thirdparty_buyer->price_level]) {
2060 // yes, this product has some prices per quantity
2061 // Search price into product_price_by_qty from $this->id
2062 foreach ($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray) {
2063 if ($priceforthequantityarray['rowid'] != $pqp) {
2064 continue;
2065 }
2066 // We found the price
2067 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2068 $pu_ht = $priceforthequantityarray['unitprice'];
2069 } else {
2070 $pu_ttc = $priceforthequantityarray['unitprice'];
2071 }
2072 break;
2073 }
2074 }
2075 }
2076
2077 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);
2078 }
2079
2080 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2094 public function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
2095 {
2096 // phpcs:enable
2097 global $conf;
2098 $result = 0;
2099
2100 // 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)
2101 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2102 $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,";
2103 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2104 $sql .= " pfp.packaging";
2105 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2106 $sql .= " WHERE pfp.rowid = ".((int) $prodfournprice);
2107 if ($qty > 0) {
2108 $sql .= " AND pfp.quantity <= ".((float) $qty);
2109 }
2110 $sql .= " ORDER BY pfp.quantity DESC";
2111
2112 dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
2113 $resql = $this->db->query($sql);
2114 if ($resql) {
2115 $obj = $this->db->fetch_object($resql);
2116 if ($obj && $obj->quantity > 0) { // If we found a supplier prices from the id of supplier price
2117 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2118 $prod_supplier = new ProductFournisseur($this->db);
2119 $prod_supplier->product_fourn_price_id = $obj->rowid;
2120 $prod_supplier->id = $obj->fk_product;
2121 $prod_supplier->fourn_qty = $obj->quantity;
2122 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2123 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2124
2125 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2126 $priceparser = new PriceParser($this->db);
2127 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2128 if ($price_result >= 0) {
2129 $obj->price = $price_result;
2130 }
2131 }
2132 $this->product_fourn_price_id = $obj->rowid;
2133 $this->buyprice = $obj->price; // deprecated
2134 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
2135 $this->fourn_price_base_type = 'HT'; // Price base type
2136 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2137 $this->ref_fourn = $obj->ref_supplier; // deprecated
2138 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2139 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2140 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2141 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2142 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2143 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2144 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2145 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2146 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2147 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2148 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2149 $this->packaging = $obj->packaging;
2150 }
2151 $result = $obj->fk_product;
2152 return $result;
2153 } else { // If not found
2154 // 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.
2155 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2156 $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,";
2157 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2158 $sql .= " pfp.packaging";
2159 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2160 $sql .= " WHERE 1 = 1";
2161 if ($product_id > 0) {
2162 $sql .= " AND pfp.fk_product = ".((int) $product_id);
2163 }
2164 if ($fourn_ref != 'none') {
2165 $sql .= " AND pfp.ref_fourn = '".$this->db->escape($fourn_ref)."'";
2166 }
2167 if ($fk_soc > 0) {
2168 $sql .= " AND pfp.fk_soc = ".((int) $fk_soc);
2169 }
2170 if ($qty > 0) {
2171 $sql .= " AND pfp.quantity <= ".((float) $qty);
2172 }
2173 $sql .= " ORDER BY pfp.quantity DESC";
2174 $sql .= " LIMIT 1";
2175
2176 dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
2177 $resql = $this->db->query($sql);
2178 if ($resql) {
2179 $obj = $this->db->fetch_object($resql);
2180 if ($obj && $obj->quantity > 0) { // If found
2181 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2182 $prod_supplier = new ProductFournisseur($this->db);
2183 $prod_supplier->product_fourn_price_id = $obj->rowid;
2184 $prod_supplier->id = $obj->fk_product;
2185 $prod_supplier->fourn_qty = $obj->quantity;
2186 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2187 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2188
2189 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2190 $priceparser = new PriceParser($this->db);
2191 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2192 if ($result >= 0) {
2193 $obj->price = $price_result;
2194 }
2195 }
2196 $this->product_fourn_price_id = $obj->rowid;
2197 $this->buyprice = $obj->price; // deprecated
2198 $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
2199 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
2200 $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
2201 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2202 $this->ref_fourn = $obj->ref_supplier; // deprecated
2203 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2204 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2205 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2206 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2207 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2208 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2209 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2210 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2211 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2212 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2213 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2214 $this->packaging = $obj->packaging;
2215 }
2216 $result = $obj->fk_product;
2217 return $result;
2218 } else {
2219 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é.
2220 }
2221 } else {
2222 $this->error = $this->db->lasterror();
2223 return -3;
2224 }
2225 }
2226 } else {
2227 $this->error = $this->db->lasterror();
2228 return -2;
2229 }
2230 }
2231
2232
2250 public function updatePrice($newprice, $newpricebase, $user, $newvat = '', $newminprice = 0, $level = 0, $newnpr = 0, $newpbq = 0, $ignore_autogen = 0, $localtaxes_array = array(), $newdefaultvatcode = '', $notrigger = 0)
2251 {
2252 global $conf, $langs;
2253
2254 $lastPriceData = $this->getArrayForPriceCompare($level); // temporary store current price before update
2255
2256 $id = $this->id;
2257
2258 dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
2259
2260 // Clean parameters
2261 if (empty($this->tva_tx)) {
2262 $this->tva_tx = 0;
2263 }
2264 if (empty($newnpr)) {
2265 $newnpr = 0;
2266 }
2267 if (empty($newminprice)) {
2268 $newminprice = 0;
2269 }
2270 if (empty($newminprice)) {
2271 $newminprice = 0;
2272 }
2273
2274 // Check parameters
2275 if ($newvat == '') {
2276 $newvat = $this->tva_tx;
2277 }
2278
2279 // If multiprices are enabled, then we check if the current product is subject to price autogeneration
2280 // Price will be modified ONLY when the first one is the one that is being modified
2281 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
2282 return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
2283 }
2284
2285 if (!empty($newminprice) && ($newminprice > $newprice)) {
2286 $this->error = 'ErrorPriceCantBeLowerThanMinPrice';
2287 return -1;
2288 }
2289
2290 if ($newprice !== '' || $newprice === 0) {
2291 if ($newpricebase == 'TTC') {
2292 $price_ttc = price2num($newprice, 'MU');
2293 $price = price2num($newprice) / (1 + ($newvat / 100));
2294 $price = price2num($price, 'MU');
2295
2296 if ($newminprice != '' || $newminprice == 0) {
2297 $price_min_ttc = price2num($newminprice, 'MU');
2298 $price_min = price2num($newminprice) / (1 + ($newvat / 100));
2299 $price_min = price2num($price_min, 'MU');
2300 } else {
2301 $price_min = 0;
2302 $price_min_ttc = 0;
2303 }
2304 } else {
2305 $price = price2num($newprice, 'MU');
2306 $price_ttc = ($newnpr != 1) ? (float) price2num($newprice) * (1 + ($newvat / 100)) : $price;
2307 $price_ttc = price2num($price_ttc, 'MU');
2308
2309 if ($newminprice !== '' || $newminprice === 0) {
2310 $price_min = price2num($newminprice, 'MU');
2311 $price_min_ttc = price2num($newminprice) * (1 + ($newvat / 100));
2312 $price_min_ttc = price2num($price_min_ttc, 'MU');
2313 //print 'X'.$newminprice.'-'.$price_min;
2314 } else {
2315 $price_min = 0;
2316 $price_min_ttc = 0;
2317 }
2318 }
2319 //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
2320
2321 if (count($localtaxes_array) > 0) {
2322 $localtaxtype1 = $localtaxes_array['0'];
2323 $localtax1 = $localtaxes_array['1'];
2324 $localtaxtype2 = $localtaxes_array['2'];
2325 $localtax2 = $localtaxes_array['3'];
2326 } else {
2327 // if array empty, we try to use the vat code
2328 if (!empty($newdefaultvatcode)) {
2329 global $mysoc;
2330 // Get record from code
2331 $sql = "SELECT t.rowid, t.code, t.recuperableonly as tva_npr, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
2332 $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
2333 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$this->db->escape($mysoc->country_code)."'";
2334 $sql .= " AND t.taux = ".((float) $newdefaultvatcode)." AND t.active = 1";
2335 $sql .= " AND t.code = '".$this->db->escape($newdefaultvatcode)."'";
2336 $resql = $this->db->query($sql);
2337 if ($resql) {
2338 $obj = $this->db->fetch_object($resql);
2339 if ($obj) {
2340 $npr = $obj->tva_npr;
2341 $localtax1 = $obj->localtax1;
2342 $localtax2 = $obj->localtax2;
2343 $localtaxtype1 = $obj->localtax1_type;
2344 $localtaxtype2 = $obj->localtax2_type;
2345 }
2346 }
2347 } else {
2348 // old method. deprecated because we can't retrieve type
2349 $localtaxtype1 = '0';
2350 $localtax1 = get_localtax($newvat, 1);
2351 $localtaxtype2 = '0';
2352 $localtax2 = get_localtax($newvat, 2);
2353 }
2354 }
2355 if (empty($localtax1)) {
2356 $localtax1 = 0; // If = '' then = 0
2357 }
2358 if (empty($localtax2)) {
2359 $localtax2 = 0; // If = '' then = 0
2360 }
2361
2362 $this->db->begin();
2363
2364 // Ne pas mettre de quote sur les numeriques decimaux.
2365 // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
2366 $sql = "UPDATE ".$this->db->prefix()."product SET";
2367 $sql .= " price_base_type = '".$this->db->escape($newpricebase)."',";
2368 $sql .= " price = ".(float) $price.",";
2369 $sql .= " price_ttc = ".(float) $price_ttc.",";
2370 $sql .= " price_min = ".(float) $price_min.",";
2371 $sql .= " price_min_ttc = ".(float) $price_min_ttc.",";
2372 $sql .= " localtax1_tx = ".($localtax1 >= 0 ? (float) $localtax1 : 'NULL').",";
2373 $sql .= " localtax2_tx = ".($localtax2 >= 0 ? (float) $localtax2 : 'NULL').",";
2374 $sql .= " localtax1_type = ".($localtaxtype1 != '' ? "'".$this->db->escape($localtaxtype1)."'" : "'0'").",";
2375 $sql .= " localtax2_type = ".($localtaxtype2 != '' ? "'".$this->db->escape($localtaxtype2)."'" : "'0'").",";
2376 $sql .= " default_vat_code = ".($newdefaultvatcode ? "'".$this->db->escape($newdefaultvatcode)."'" : "null").",";
2377 $sql .= " tva_tx = ".(float) price2num($newvat).",";
2378 $sql .= " recuperableonly = '".$this->db->escape($newnpr)."'";
2379 $sql .= " WHERE rowid = ".((int) $id);
2380
2381 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2382 $resql = $this->db->query($sql);
2383 if ($resql) {
2384 $this->multiprices[$level] = $price;
2385 $this->multiprices_ttc[$level] = $price_ttc;
2386 $this->multiprices_min[$level] = $price_min;
2387 $this->multiprices_min_ttc[$level] = $price_min_ttc;
2388 $this->multiprices_base_type[$level] = $newpricebase;
2389 $this->multiprices_default_vat_code[$level] = $newdefaultvatcode;
2390 $this->multiprices_tva_tx[$level] = $newvat;
2391 $this->multiprices_recuperableonly[$level] = $newnpr;
2392
2393 $this->price = $price;
2394 $this->price_ttc = $price_ttc;
2395 $this->price_min = $price_min;
2396 $this->price_min_ttc = $price_min_ttc;
2397 $this->price_base_type = $newpricebase;
2398 $this->default_vat_code = $newdefaultvatcode;
2399 $this->tva_tx = $newvat;
2400 $this->tva_npr = $newnpr;
2401 //Local taxes
2402 $this->localtax1_tx = $localtax1;
2403 $this->localtax2_tx = $localtax2;
2404 $this->localtax1_type = $localtaxtype1;
2405 $this->localtax2_type = $localtaxtype2;
2406
2407 // Price by quantity
2408 $this->price_by_qty = $newpbq;
2409
2410 // check if price have really change before log
2411 $newPriceData = $this->getArrayForPriceCompare($level);
2412 if (!empty(array_diff_assoc($newPriceData, $lastPriceData)) || !getDolGlobalString('PRODUIT_MULTIPRICES')) {
2413 $this->_log_price($user, $level); // Save price for level into table product_price
2414 }
2415
2416 $this->level = $level; // Store level of price edited for trigger
2417
2418 // Call trigger
2419 if (!$notrigger) {
2420 $result = $this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
2421 if ($result < 0) {
2422 $this->db->rollback();
2423 return -1;
2424 }
2425 }
2426 // End call triggers
2427
2428 $this->db->commit();
2429 } else {
2430 $this->db->rollback();
2431 $this->error = $this->db->lasterror();
2432 return -1;
2433 }
2434 }
2435
2436 return 1;
2437 }
2438
2446 public function setPriceExpression($expression_id)
2447 {
2448 global $user;
2449
2450 $this->fk_price_expression = $expression_id;
2451
2452 return $this->update($this->id, $user);
2453 }
2454
2467 public function fetch($id = '', $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0, $ignore_price_load = 0, $ignore_lang_load = 0)
2468 {
2469 include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2470
2471 global $langs, $conf;
2472
2473 dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
2474
2475 // Check parameters
2476 if (!$id && !$ref && !$ref_ext && !$barcode) {
2477 $this->error = 'ErrorWrongParameters';
2478 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2479 return -1;
2480 }
2481
2482 $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,";
2483 $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,";
2484 $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,";
2485 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
2486 $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,";
2487 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2488 $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,";
2489 } else {
2490 $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,";
2491 }
2492
2493 //For MultiCompany
2494 //PMP per entity & Stocks Sharings stock_reel includes only stocks shared with this entity
2495 $separatedEntityPMP = false; // Set to true to get the AWP from table llx_product_perentity instead of field 'pmp' into llx_product.
2496 $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.
2497 $visibleWarehousesEntities = $conf->entity;
2498 if (getDolGlobalString('MULTICOMPANY_PRODUCT_SHARING_ENABLED')) {
2499 if (getDolGlobalString('MULTICOMPANY_PMP_PER_ENTITY_ENABLED')) {
2500 $checkPMPPerEntity = $this->db->query("SELECT pmp FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = ".((int) $id)." AND entity = ".(int) $conf->entity);
2501 if ($this->db->num_rows($checkPMPPerEntity)>0) {
2502 $separatedEntityPMP = true;
2503 }
2504 }
2505 global $mc;
2506 $separatedStock = true;
2507 if (isset($mc->sharings['stock']) && !empty($mc->sharings['stock'])) {
2508 $visibleWarehousesEntities .= "," . implode(",", $mc->sharings['stock']);
2509 }
2510 }
2511 if ($separatedEntityPMP) {
2512 $sql .= " ppe.pmp,";
2513 } else {
2514 $sql .= " p.pmp,";
2515 }
2516 $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.batch_mask, p.fk_unit,";
2517 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf,";
2518 if ($separatedStock) {
2519 $sql .= " SUM(sp.reel) as stock";
2520 } else {
2521 $sql .= " p.stock";
2522 }
2523 $sql .= " FROM ".$this->db->prefix()."product as p";
2524 if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') || $separatedEntityPMP) {
2525 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
2526 }
2527 if ($separatedStock) {
2528 $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)."))";
2529 }
2530
2531 if ($id) {
2532 $sql .= " WHERE p.rowid = ".((int) $id);
2533 } else {
2534 $sql .= " WHERE p.entity IN (".getEntity($this->element).")";
2535 if ($ref) {
2536 $sql .= " AND p.ref = '".$this->db->escape($ref)."'";
2537 } elseif ($ref_ext) {
2538 $sql .= " AND p.ref_ext = '".$this->db->escape($ref_ext)."'";
2539 } elseif ($barcode) {
2540 $sql .= " AND p.barcode = '".$this->db->escape($barcode)."'";
2541 }
2542 }
2543 if ($separatedStock) {
2544 $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,";
2545 $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,";
2546 $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,";
2547 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
2548 $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,";
2549 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2550 $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,";
2551 } else {
2552 $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,";
2553 }
2554 if ($separatedEntityPMP) {
2555 $sql .= " ppe.pmp,";
2556 } else {
2557 $sql .= " p.pmp,";
2558 }
2559 $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.batch_mask, p.fk_unit,";
2560 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf";
2561 if (!$separatedStock) {
2562 $sql .= ", p.stock";
2563 }
2564 }
2565
2566 $resql = $this->db->query($sql);
2567 if ($resql) {
2568 unset($this->oldcopy);
2569
2570 if ($this->db->num_rows($resql) > 0) {
2571 $obj = $this->db->fetch_object($resql);
2572
2573 $this->id = $obj->rowid;
2574 $this->ref = $obj->ref;
2575 $this->ref_ext = $obj->ref_ext;
2576 $this->label = $obj->label;
2577 $this->description = $obj->description;
2578 $this->url = $obj->url;
2579 $this->note_public = $obj->note_public;
2580 $this->note_private = $obj->note_private;
2581 $this->note = $obj->note_private; // deprecated
2582
2583 $this->type = $obj->fk_product_type;
2584 $this->status = $obj->tosell;
2585 $this->status_buy = $obj->tobuy;
2586 $this->status_batch = $obj->tobatch;
2587 $this->batch_mask = $obj->batch_mask;
2588
2589 $this->customcode = $obj->customcode;
2590 $this->country_id = $obj->fk_country;
2591 $this->country_code = getCountry($this->country_id, 2, $this->db);
2592 $this->state_id = $obj->fk_state;
2593 $this->lifetime = $obj->lifetime;
2594 $this->qc_frequency = $obj->qc_frequency;
2595 $this->price = $obj->price;
2596 $this->price_ttc = $obj->price_ttc;
2597 $this->price_min = $obj->price_min;
2598 $this->price_min_ttc = $obj->price_min_ttc;
2599 $this->price_base_type = $obj->price_base_type;
2600 $this->cost_price = isset($obj->cost_price) ? (float) $obj->cost_price : null;
2601 $this->default_vat_code = $obj->default_vat_code;
2602 $this->tva_tx = $obj->tva_tx;
2604 $this->tva_npr = $obj->tva_npr;
2606 $this->localtax1_tx = $obj->localtax1_tx;
2607 $this->localtax2_tx = $obj->localtax2_tx;
2608 $this->localtax1_type = $obj->localtax1_type;
2609 $this->localtax2_type = $obj->localtax2_type;
2610
2611 $this->finished = $obj->finished;
2612 $this->fk_default_bom = $obj->fk_default_bom;
2613
2614 $this->duration = $obj->duration;
2615 $this->duration_value = $obj->duration ? (int) (substr($obj->duration, 0, dol_strlen($obj->duration) - 1)) : 0;
2616 $this->duration_unit = $obj->duration ? substr($obj->duration, -1) : null;
2617 $this->canvas = $obj->canvas;
2618 $this->net_measure = $obj->net_measure;
2619 $this->net_measure_units = $obj->net_measure_units;
2620 $this->weight = $obj->weight;
2621 $this->weight_units = $obj->weight_units;
2622 $this->length = $obj->length;
2623 $this->length_units = $obj->length_units;
2624 $this->width = $obj->width;
2625 $this->width_units = $obj->width_units;
2626 $this->height = $obj->height;
2627 $this->height_units = $obj->height_units;
2628
2629 $this->surface = $obj->surface;
2630 $this->surface_units = $obj->surface_units;
2631 $this->volume = $obj->volume;
2632 $this->volume_units = $obj->volume_units;
2633 $this->barcode = $obj->barcode;
2634 $this->barcode_type = $obj->fk_barcode_type;
2635
2636 $this->accountancy_code_buy = $obj->accountancy_code_buy;
2637 $this->accountancy_code_buy_intra = $obj->accountancy_code_buy_intra;
2638 $this->accountancy_code_buy_export = $obj->accountancy_code_buy_export;
2639 $this->accountancy_code_sell = $obj->accountancy_code_sell;
2640 $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
2641 $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
2642
2643 $this->fk_default_warehouse = $obj->fk_default_warehouse;
2644 $this->fk_default_workstation = $obj->fk_default_workstation;
2645 $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
2646 $this->desiredstock = $obj->desiredstock;
2647 $this->stock_reel = $obj->stock;
2648 $this->pmp = $obj->pmp;
2649
2650 $this->date_creation = $obj->datec;
2651 $this->date_modification = $obj->tms;
2652 $this->import_key = $obj->import_key;
2653 $this->entity = $obj->entity;
2654
2655 $this->ref_ext = $obj->ref_ext;
2656 $this->fk_price_expression = $obj->fk_price_expression;
2657 $this->fk_unit = $obj->fk_unit;
2658 $this->price_autogen = $obj->price_autogen;
2659 $this->model_pdf = $obj->model_pdf;
2660
2661 $this->mandatory_period = $obj->mandatory_period;
2662
2663 $this->db->free($resql);
2664
2665 // fetch optionals attributes and labels
2666 $this->fetch_optionals();
2667
2668 // Multilangs
2669 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($ignore_lang_load)) {
2670 $this->getMultiLangs();
2671 }
2672
2673 // Load multiprices array
2674 if (getDolGlobalString('PRODUIT_MULTIPRICES') && empty($ignore_price_load)) { // prices per segment
2675 for ($i = 1; $i <= getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT'); $i++) {
2676 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2677 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2678 $sql .= " FROM ".$this->db->prefix()."product_price";
2679 $sql .= " WHERE entity IN (".getEntity('productprice').")";
2680 $sql .= " AND price_level=".((int) $i);
2681 $sql .= " AND fk_product = ".((int) $this->id);
2682 $sql .= " ORDER BY date_price DESC, rowid DESC"; // Get the most recent line
2683 $sql .= " LIMIT 1"; // Only the first one
2684 $resql = $this->db->query($sql);
2685 if ($resql) {
2686 $result = $this->db->fetch_array($resql);
2687
2688 $this->multiprices[$i] = $result ? $result["price"] : null;
2689 $this->multiprices_ttc[$i] = $result ? $result["price_ttc"] : null;
2690 $this->multiprices_min[$i] = $result ? $result["price_min"] : null;
2691 $this->multiprices_min_ttc[$i] = $result ? $result["price_min_ttc"] : null;
2692 $this->multiprices_base_type[$i] = $result ? $result["price_base_type"] : null;
2693 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2694 $this->multiprices_tva_tx[$i] = $result ? $result["tva_tx"].($result ? ' ('.$result['default_vat_code'].')' : '') : null;
2695 $this->multiprices_recuperableonly[$i] = $result ? $result["recuperableonly"] : null;
2696
2697 // Price by quantity
2698 /*
2699 $this->prices_by_qty[$i]=$result["price_by_qty"];
2700 $this->prices_by_qty_id[$i]=$result["rowid"];
2701 // Récuperation de la liste des prix selon qty si flag positionné
2702 if ($this->prices_by_qty[$i] == 1)
2703 {
2704 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2705 $sql.= " FROM ".$this->db->prefix()."product_price_by_qty";
2706 $sql.= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2707 $sql.= " ORDER BY quantity ASC";
2708 $resultat=array();
2709 $resql = $this->db->query($sql);
2710 if ($resql)
2711 {
2712 $ii=0;
2713 while ($result= $this->db->fetch_array($resql)) {
2714 $resultat[$ii]=array();
2715 $resultat[$ii]["rowid"]=$result["rowid"];
2716 $resultat[$ii]["price"]= $result["price"];
2717 $resultat[$ii]["unitprice"]= $result["unitprice"];
2718 $resultat[$ii]["quantity"]= $result["quantity"];
2719 $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2720 $resultat[$ii]["remise"]= $result["remise"]; // deprecated
2721 $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2722 $ii++;
2723 }
2724 $this->prices_by_qty_list[$i]=$resultat;
2725 }
2726 else
2727 {
2728 dol_print_error($this->db);
2729 return -1;
2730 }
2731 }*/
2732 } else {
2733 $this->error = $this->db->lasterror;
2734 return -1;
2735 }
2736 }
2737 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') && empty($ignore_price_load)) { // prices per customers
2738 // Nothing loaded by default. List may be very long.
2739 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') && empty($ignore_price_load)) { // prices per quantity
2740 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2741 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
2742 $sql .= " FROM ".$this->db->prefix()."product_price";
2743 $sql .= " WHERE fk_product = ".((int) $this->id);
2744 $sql .= " ORDER BY date_price DESC, rowid DESC";
2745 $sql .= " LIMIT 1";
2746 $resql = $this->db->query($sql);
2747 if ($resql) {
2748 $result = $this->db->fetch_array($resql);
2749
2750 // Price by quantity
2751 $this->prices_by_qty[0] = $result["price_by_qty"];
2752 $this->prices_by_qty_id[0] = $result["rowid"];
2753 // Récuperation de la liste des prix selon qty si flag positionné
2754 if ($this->prices_by_qty[0] == 1) {
2755 $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
2756 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2757 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[0]);
2758 $sql .= " ORDER BY quantity ASC";
2759 $resultat = array();
2760 $resql = $this->db->query($sql);
2761 if ($resql) {
2762 $ii = 0;
2763 while ($result = $this->db->fetch_array($resql)) {
2764 $resultat[$ii] = array();
2765 $resultat[$ii]["rowid"] = $result["rowid"];
2766 $resultat[$ii]["price"] = $result["price"];
2767 $resultat[$ii]["unitprice"] = $result["unitprice"];
2768 $resultat[$ii]["quantity"] = $result["quantity"];
2769 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2770 //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
2771 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2772 $ii++;
2773 }
2774 $this->prices_by_qty_list[0] = $resultat;
2775 } else {
2776 $this->error = $this->db->lasterror;
2777 return -1;
2778 }
2779 }
2780 } else {
2781 $this->error = $this->db->lasterror;
2782 return -1;
2783 }
2784 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') && empty($ignore_price_load)) { // prices per customer and quantity
2785 for ($i = 1; $i <= getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT'); $i++) {
2786 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2787 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2788 $sql .= " FROM ".$this->db->prefix()."product_price";
2789 $sql .= " WHERE entity IN (".getEntity('productprice').")";
2790 $sql .= " AND price_level=".((int) $i);
2791 $sql .= " AND fk_product = ".((int) $this->id);
2792 $sql .= " ORDER BY date_price DESC, rowid DESC";
2793 $sql .= " LIMIT 1";
2794 $resql = $this->db->query($sql);
2795 if ($resql) {
2796 $result = $this->db->fetch_array($resql);
2797
2798 $this->multiprices[$i] = (!empty($result["price"]) ? $result["price"] : 0);
2799 $this->multiprices_ttc[$i] = (!empty($result["price_ttc"]) ? $result["price_ttc"] : 0);
2800 $this->multiprices_min[$i] = (!empty($result["price_min"]) ? $result["price_min"] : 0);
2801 $this->multiprices_min_ttc[$i] = (!empty($result["price_min_ttc"]) ? $result["price_min_ttc"] : 0);
2802 $this->multiprices_base_type[$i] = (!empty($result["price_base_type"]) ? $result["price_base_type"] : '');
2803 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2804 $this->multiprices_tva_tx[$i] = (!empty($result["tva_tx"]) ? $result["tva_tx"] : 0); // TODO Add ' ('.$result['default_vat_code'].')'
2805 $this->multiprices_recuperableonly[$i] = (!empty($result["recuperableonly"]) ? $result["recuperableonly"] : 0);
2806
2807 // Price by quantity
2808 $this->prices_by_qty[$i] = (!empty($result["price_by_qty"]) ? $result["price_by_qty"] : 0);
2809 $this->prices_by_qty_id[$i] = (!empty($result["rowid"]) ? $result["rowid"] : 0);
2810 // Récuperation de la liste des prix selon qty si flag positionné
2811 if ($this->prices_by_qty[$i] == 1) {
2812 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2813 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2814 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2815 $sql .= " ORDER BY quantity ASC";
2816 $resultat = array();
2817 $resql = $this->db->query($sql);
2818 if ($resql) {
2819 $ii = 0;
2820 while ($result = $this->db->fetch_array($resql)) {
2821 $resultat[$ii] = array();
2822 $resultat[$ii]["rowid"] = $result["rowid"];
2823 $resultat[$ii]["price"] = $result["price"];
2824 $resultat[$ii]["unitprice"] = $result["unitprice"];
2825 $resultat[$ii]["quantity"] = $result["quantity"];
2826 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2827 $resultat[$ii]["remise"] = $result["remise"]; // deprecated
2828 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2829 $ii++;
2830 }
2831 $this->prices_by_qty_list[$i] = $resultat;
2832 } else {
2833 $this->error = $this->db->lasterror;
2834 return -1;
2835 }
2836 }
2837 } else {
2838 $this->error = $this->db->lasterror;
2839 return -1;
2840 }
2841 }
2842 }
2843
2844 if (isModEnabled('dynamicprices') && !empty($this->fk_price_expression) && empty($ignore_expression)) {
2845 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2846 $priceparser = new PriceParser($this->db);
2847 $price_result = $priceparser->parseProduct($this);
2848 if ($price_result >= 0) {
2849 $this->price = $price_result;
2850 // Calculate the VAT
2851 $this->price_ttc = price2num($this->price) * (1 + ($this->tva_tx / 100));
2852 $this->price_ttc = price2num($this->price_ttc, 'MU');
2853 }
2854 }
2855
2856 // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
2857 // Instead we just init the stock_warehouse array
2858 $this->stock_warehouse = array();
2859
2860 return 1;
2861 } else {
2862 return 0;
2863 }
2864 } else {
2865 $this->error = $this->db->lasterror();
2866 return -1;
2867 }
2868 }
2869
2870 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2877 public function load_stats_mo($socid = 0)
2878 {
2879 // phpcs:enable
2880 global $user, $hookmanager, $action;
2881
2882 $error = 0;
2883
2884 foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
2885 $this->stats_mo['customers_'.$role] = 0;
2886 $this->stats_mo['nb_'.$role] = 0;
2887 $this->stats_mo['qty_'.$role] = 0;
2888
2889 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2890 $sql .= " SUM(mp.qty) as qty";
2891 $sql .= " FROM ".$this->db->prefix()."mrp_mo as c";
2892 $sql .= " INNER JOIN ".$this->db->prefix()."mrp_production as mp ON mp.fk_mo=c.rowid";
2893 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
2894 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc=c.fk_soc AND sc.fk_user = ".((int) $user->id);
2895 }
2896 $sql .= " WHERE ";
2897 $sql .= " c.entity IN (".getEntity('mo').")";
2898
2899 $sql .= " AND mp.fk_product = ".((int) $this->id);
2900 $sql .= " AND mp.role ='".$this->db->escape($role)."'";
2901 if ($socid > 0) {
2902 $sql .= " AND c.fk_soc = ".((int) $socid);
2903 }
2904
2905 $result = $this->db->query($sql);
2906 if ($result) {
2907 $obj = $this->db->fetch_object($result);
2908 $this->stats_mo['customers_'.$role] = $obj->nb_customers ? $obj->nb_customers : 0;
2909 $this->stats_mo['nb_'.$role] = $obj->nb ? $obj->nb : 0;
2910 $this->stats_mo['qty_'.$role] = $obj->qty ? price2num($obj->qty, 'MS') : 0; // qty may be a float due to the SUM()
2911 } else {
2912 $this->error = $this->db->error();
2913 $error++;
2914 }
2915 }
2916
2917 if (!empty($error)) {
2918 return -1;
2919 }
2920
2921 $parameters = array('socid' => $socid);
2922 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
2923 if ($reshook > 0) {
2924 $this->stats_mo = $hookmanager->resArray['stats_mo'];
2925 }
2926
2927 return 1;
2928 }
2929
2930 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2937 public function load_stats_bom($socid = 0)
2938 {
2939 // phpcs:enable
2940 global $user, $hookmanager, $action;
2941
2942 $error = 0;
2943
2944 $this->stats_bom['nb_toproduce'] = 0;
2945 $this->stats_bom['nb_toconsume'] = 0;
2946 $this->stats_bom['qty_toproduce'] = 0;
2947 $this->stats_bom['qty_toconsume'] = 0;
2948
2949 $sql = "SELECT COUNT(DISTINCT b.rowid) as nb_toproduce,";
2950 $sql .= " SUM(b.qty) as qty_toproduce";
2951 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
2952 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
2953 $sql .= " WHERE ";
2954 $sql .= " b.entity IN (".getEntity('bom').")";
2955 $sql .= " AND b.fk_product =".((int) $this->id);
2956 $sql .= " GROUP BY b.rowid";
2957
2958 $result = $this->db->query($sql);
2959 if ($result) {
2960 $obj = $this->db->fetch_object($result);
2961 $this->stats_bom['nb_toproduce'] = !empty($obj->nb_toproduce) ? $obj->nb_toproduce : 0;
2962 $this->stats_bom['qty_toproduce'] = !empty($obj->qty_toproduce) ? price2num($obj->qty_toproduce) : 0;
2963 } else {
2964 $this->error = $this->db->error();
2965 $error++;
2966 }
2967
2968 $sql = "SELECT COUNT(DISTINCT bl.rowid) as nb_toconsume,";
2969 $sql .= " SUM(bl.qty) as qty_toconsume";
2970 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
2971 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
2972 $sql .= " WHERE ";
2973 $sql .= " b.entity IN (".getEntity('bom').")";
2974 $sql .= " AND bl.fk_product =".((int) $this->id);
2975
2976 $result = $this->db->query($sql);
2977 if ($result) {
2978 $obj = $this->db->fetch_object($result);
2979 $this->stats_bom['nb_toconsume'] = !empty($obj->nb_toconsume) ? $obj->nb_toconsume : 0;
2980 $this->stats_bom['qty_toconsume'] = !empty($obj->qty_toconsume) ? price2num($obj->qty_toconsume) : 0;
2981 } else {
2982 $this->error = $this->db->error();
2983 $error++;
2984 }
2985
2986 if (!empty($error)) {
2987 return -1;
2988 }
2989
2990 $parameters = array('socid' => $socid);
2991 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
2992 if ($reshook > 0) {
2993 $this->stats_bom = $hookmanager->resArray['stats_bom'];
2994 }
2995
2996 return 1;
2997 }
2998
2999 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3006 public function load_stats_propale($socid = 0)
3007 {
3008 // phpcs:enable
3009 global $conf, $user, $hookmanager, $action;
3010
3011 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
3012 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3013 $sql .= " FROM ".$this->db->prefix()."propaldet as pd";
3014 $sql .= ", ".$this->db->prefix()."propal as p";
3015 $sql .= ", ".$this->db->prefix()."societe as s";
3016 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3017 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3018 }
3019 $sql .= " WHERE p.rowid = pd.fk_propal";
3020 $sql .= " AND p.fk_soc = s.rowid";
3021 $sql .= " AND p.entity IN (".getEntity('propal').")";
3022 $sql .= " AND pd.fk_product = ".((int) $this->id);
3023 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3024 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3025 }
3026 //$sql.= " AND pr.fk_statut != 0";
3027 if ($socid > 0) {
3028 $sql .= " AND p.fk_soc = ".((int) $socid);
3029 }
3030
3031 $result = $this->db->query($sql);
3032 if ($result) {
3033 $obj = $this->db->fetch_object($result);
3034 $this->stats_propale['customers'] = $obj->nb_customers;
3035 $this->stats_propale['nb'] = $obj->nb;
3036 $this->stats_propale['rows'] = $obj->nb_rows;
3037 $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
3038
3039 // if it's a virtual product, maybe it is in proposal by extension
3040 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3041 $TFather = $this->getFather();
3042 if (is_array($TFather) && !empty($TFather)) {
3043 foreach ($TFather as &$fatherData) {
3044 $pFather = new Product($this->db);
3045 $pFather->id = $fatherData['id'];
3046 $qtyCoef = $fatherData['qty'];
3047
3048 if ($fatherData['incdec']) {
3049 $pFather->load_stats_propale($socid);
3050
3051 $this->stats_propale['customers'] += $pFather->stats_propale['customers'];
3052 $this->stats_propale['nb'] += $pFather->stats_propale['nb'];
3053 $this->stats_propale['rows'] += $pFather->stats_propale['rows'];
3054 $this->stats_propale['qty'] += $pFather->stats_propale['qty'] * $qtyCoef;
3055 }
3056 }
3057 }
3058 }
3059
3060 $parameters = array('socid' => $socid);
3061 $reshook = $hookmanager->executeHooks('loadStatsCustomerProposal', $parameters, $this, $action);
3062 if ($reshook > 0) {
3063 $this->stats_propale = $hookmanager->resArray['stats_propale'];
3064 }
3065
3066 return 1;
3067 } else {
3068 $this->error = $this->db->error();
3069 return -1;
3070 }
3071 }
3072
3073
3074 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3081 public function load_stats_proposal_supplier($socid = 0)
3082 {
3083 // phpcs:enable
3084 global $conf, $user, $hookmanager, $action;
3085
3086 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
3087 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3088 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as pd";
3089 $sql .= ", ".$this->db->prefix()."supplier_proposal as p";
3090 $sql .= ", ".$this->db->prefix()."societe as s";
3091 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3092 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3093 }
3094 $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
3095 $sql .= " AND p.fk_soc = s.rowid";
3096 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
3097 $sql .= " AND pd.fk_product = ".((int) $this->id);
3098 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3099 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3100 }
3101 //$sql.= " AND pr.fk_statut != 0";
3102 if ($socid > 0) {
3103 $sql .= " AND p.fk_soc = ".((int) $socid);
3104 }
3105
3106 $result = $this->db->query($sql);
3107 if ($result) {
3108 $obj = $this->db->fetch_object($result);
3109 $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
3110 $this->stats_proposal_supplier['nb'] = $obj->nb;
3111 $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
3112 $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
3113
3114 $parameters = array('socid' => $socid);
3115 $reshook = $hookmanager->executeHooks('loadStatsSupplierProposal', $parameters, $this, $action);
3116 if ($reshook > 0) {
3117 $this->stats_proposal_supplier = $hookmanager->resArray['stats_proposal_supplier'];
3118 }
3119
3120 return 1;
3121 } else {
3122 $this->error = $this->db->error();
3123 return -1;
3124 }
3125 }
3126
3127
3128 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3137 public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
3138 {
3139 // phpcs:enable
3140 global $conf, $user, $hookmanager, $action;
3141
3142 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3143 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3144 $sql .= " FROM ".$this->db->prefix()."commandedet as cd";
3145 $sql .= ", ".$this->db->prefix()."commande as c";
3146 $sql .= ", ".$this->db->prefix()."societe as s";
3147 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3148 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3149 }
3150 $sql .= " WHERE c.rowid = cd.fk_commande";
3151 $sql .= " AND c.fk_soc = s.rowid";
3152 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'commande').")";
3153 $sql .= " AND cd.fk_product = ".((int) $this->id);
3154 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3155 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3156 }
3157 if ($socid > 0) {
3158 $sql .= " AND c.fk_soc = ".((int) $socid);
3159 }
3160 if ($filtrestatut != '') {
3161 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3162 }
3163
3164 $result = $this->db->query($sql);
3165 if ($result) {
3166 $obj = $this->db->fetch_object($result);
3167 $this->stats_commande['customers'] = $obj->nb_customers;
3168 $this->stats_commande['nb'] = $obj->nb;
3169 $this->stats_commande['rows'] = $obj->nb_rows;
3170 $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
3171
3172 // if it's a virtual product, maybe it is in order by extension
3173 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3174 $TFather = $this->getFather();
3175 if (is_array($TFather) && !empty($TFather)) {
3176 foreach ($TFather as &$fatherData) {
3177 $pFather = new Product($this->db);
3178 $pFather->id = $fatherData['id'];
3179 $qtyCoef = $fatherData['qty'];
3180
3181 if ($fatherData['incdec']) {
3182 $pFather->load_stats_commande($socid, $filtrestatut);
3183
3184 $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
3185 $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
3186 $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
3187 $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
3188 }
3189 }
3190 }
3191 }
3192
3193 // If stock decrease is on invoice validation, the theorical stock continue to
3194 // count the orders lines in theoretical stock when some are already removed by invoice validation.
3195 if ($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
3196 if (getDolGlobalString('DECREASE_ONLY_UNINVOICEDPRODUCTS')) {
3197 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation but only if order not yet invoice.
3198 $adeduire = 0;
3199 $sql = "SELECT SUM(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3200 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3201 $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'))";
3202 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_source = c.rowid";
3203 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND c.facture = 0 AND fd.fk_product = ".((int) $this->id);
3204
3205 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3206 $resql = $this->db->query($sql);
3207 if ($resql) {
3208 if ($this->db->num_rows($resql) > 0) {
3209 $obj = $this->db->fetch_object($resql);
3210 $adeduire += $obj->count;
3211 }
3212 }
3213
3214 $this->stats_commande['qty'] -= $adeduire;
3215 } else {
3216 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is off, we make a compensation with lines of invoices linked to the order
3217 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3218
3219 // For every order having invoice already validated we need to decrease stock cause it's in physical stock
3220 $adeduire = 0;
3221 $sql = "SELECT sum(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet as fd ";
3222 $sql .= " JOIN ".MAIN_DB_PREFIX."facture as f ON fd.fk_facture = f.rowid";
3223 $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'))";
3224 $sql .= " JOIN ".MAIN_DB_PREFIX."commande as c ON el.fk_source = c.rowid";
3225 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product = ".((int) $this->id);
3226
3227 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3228 $resql = $this->db->query($sql);
3229 if ($resql) {
3230 if ($this->db->num_rows($resql) > 0) {
3231 $obj = $this->db->fetch_object($resql);
3232 $adeduire += $obj->count;
3233 }
3234 } else {
3235 $this->error = $this->db->error();
3236 return -1;
3237 }
3238
3239 $this->stats_commande['qty'] -= $adeduire;
3240 }
3241 }
3242
3243 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3244 $reshook = $hookmanager->executeHooks('loadStatsCustomerOrder', $parameters, $this, $action);
3245 if ($reshook > 0) {
3246 $this->stats_commande = $hookmanager->resArray['stats_commande'];
3247 }
3248 return 1;
3249 } else {
3250 $this->error = $this->db->error();
3251 return -1;
3252 }
3253 }
3254
3255 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3265 public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3266 {
3267 // phpcs:enable
3268 global $conf, $user, $hookmanager, $action;
3269
3270 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
3271 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3272 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as cd";
3273 $sql .= ", ".$this->db->prefix()."commande_fournisseur as c";
3274 $sql .= ", ".$this->db->prefix()."societe as s";
3275 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3276 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3277 }
3278 $sql .= " WHERE c.rowid = cd.fk_commande";
3279 $sql .= " AND c.fk_soc = s.rowid";
3280 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3281 $sql .= " AND cd.fk_product = ".((int) $this->id);
3282 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3283 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3284 }
3285 if ($socid > 0) {
3286 $sql .= " AND c.fk_soc = ".((int) $socid);
3287 }
3288 if ($filtrestatut != '') {
3289 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
3290 }
3291 if (!empty($dateofvirtualstock)) {
3292 $sql .= " AND c.date_livraison <= '".$this->db->idate($dateofvirtualstock)."'";
3293 }
3294
3295 $result = $this->db->query($sql);
3296 if ($result) {
3297 $obj = $this->db->fetch_object($result);
3298 $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
3299 $this->stats_commande_fournisseur['nb'] = $obj->nb;
3300 $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
3301 $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3302
3303 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3304 $reshook = $hookmanager->executeHooks('loadStatsSupplierOrder', $parameters, $this, $action);
3305 if ($reshook > 0) {
3306 $this->stats_commande_fournisseur = $hookmanager->resArray['stats_commande_fournisseur'];
3307 }
3308
3309 return 1;
3310 } else {
3311 $this->error = $this->db->error().' sql='.$sql;
3312 return -1;
3313 }
3314 }
3315
3316 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3326 public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $filterShipmentStatus = '')
3327 {
3328 // phpcs:enable
3329 global $conf, $user, $hookmanager, $action;
3330
3331 $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
3332 $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
3333 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
3334 $sql .= ", ".$this->db->prefix()."commandedet as cd";
3335 $sql .= ", ".$this->db->prefix()."commande as c";
3336 $sql .= ", ".$this->db->prefix()."expedition as e";
3337 $sql .= ", ".$this->db->prefix()."societe as s";
3338 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3339 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3340 }
3341 $sql .= " WHERE e.rowid = ed.fk_expedition";
3342 $sql .= " AND c.rowid = cd.fk_commande";
3343 $sql .= " AND e.fk_soc = s.rowid";
3344 $sql .= " AND e.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'expedition').")";
3345 $sql .= " AND ed.fk_origin_line = cd.rowid";
3346 $sql .= " AND cd.fk_product = ".((int) $this->id);
3347 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3348 $sql .= " AND e.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3349 }
3350 if ($socid > 0) {
3351 $sql .= " AND e.fk_soc = ".((int) $socid);
3352 }
3353 if ($filtrestatut != '') {
3354 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3355 }
3356 if (!empty($filterShipmentStatus)) {
3357 $sql .= " AND e.fk_statut IN (".$this->db->sanitize($filterShipmentStatus).")";
3358 }
3359
3360 $result = $this->db->query($sql);
3361 if ($result) {
3362 $obj = $this->db->fetch_object($result);
3363 $this->stats_expedition['customers'] = $obj->nb_customers;
3364 $this->stats_expedition['nb'] = $obj->nb;
3365 $this->stats_expedition['rows'] = $obj->nb_rows;
3366 $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
3367
3368 // if it's a virtual product, maybe it is in sending by extension
3369 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3370 $TFather = $this->getFather();
3371 if (is_array($TFather) && !empty($TFather)) {
3372 foreach ($TFather as &$fatherData) {
3373 $pFather = new Product($this->db);
3374 $pFather->id = $fatherData['id'];
3375 $qtyCoef = $fatherData['qty'];
3376
3377 if ($fatherData['incdec']) {
3378 $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
3379
3380 $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
3381 $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
3382 $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
3383 $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
3384 }
3385 }
3386 }
3387 }
3388
3389 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock, 'filterShipmentStatus' => $filterShipmentStatus);
3390 $reshook = $hookmanager->executeHooks('loadStatsSending', $parameters, $this, $action);
3391 if ($reshook > 0) {
3392 $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
3393 }
3394
3395 return 1;
3396 } else {
3397 $this->error = $this->db->error();
3398 return -1;
3399 }
3400 }
3401
3402 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3412 public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3413 {
3414 // phpcs:enable
3415 global $conf, $user, $hookmanager, $action;
3416
3417 $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_suppliers, COUNT(DISTINCT cf.rowid) as nb,";
3418 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3419 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur_dispatch as fd";
3420 $sql .= ", ".$this->db->prefix()."commande_fournisseur as cf";
3421 $sql .= ", ".$this->db->prefix()."societe as s";
3422 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3423 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3424 }
3425 $sql .= " WHERE cf.rowid = fd.fk_commande";
3426 $sql .= " AND cf.fk_soc = s.rowid";
3427 $sql .= " AND cf.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3428 $sql .= " AND fd.fk_product = ".((int) $this->id);
3429 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3430 $sql .= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3431 }
3432 if ($socid > 0) {
3433 $sql .= " AND cf.fk_soc = ".((int) $socid);
3434 }
3435 if ($filtrestatut != '') {
3436 $sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3437 }
3438 if (!empty($dateofvirtualstock)) {
3439 $sql .= " AND fd.datec <= '".$this->db->idate($dateofvirtualstock)."'";
3440 }
3441
3442 $result = $this->db->query($sql);
3443 if ($result) {
3444 $obj = $this->db->fetch_object($result);
3445 $this->stats_reception['suppliers'] = $obj->nb_suppliers;
3446 $this->stats_reception['nb'] = $obj->nb;
3447 $this->stats_reception['rows'] = $obj->nb_rows;
3448 $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
3449
3450 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3451 $reshook = $hookmanager->executeHooks('loadStatsReception', $parameters, $this, $action);
3452 if ($reshook > 0) {
3453 $this->stats_reception = $hookmanager->resArray['stats_reception'];
3454 }
3455
3456 return 1;
3457 } else {
3458 $this->error = $this->db->error();
3459 return -1;
3460 }
3461 }
3462
3463 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3473 public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3474 {
3475 // phpcs:enable
3476 global $conf, $user, $hookmanager, $action;
3477
3478 $serviceStockIsEnabled = isModEnabled("service") && getDolGlobalString('STOCK_SUPPORTS_SERVICES');
3479
3480 $sql = "SELECT COUNT(DISTINCT m.fk_soc) as nb_customers, COUNT(DISTINCT m.rowid) as nb,";
3481 $sql .= " COUNT(mp.rowid) as nb_rows, SUM(mp.qty) as qty, role";
3482 $sql .= " FROM ".$this->db->prefix()."mrp_production as mp";
3483 $sql .= ", ".$this->db->prefix()."mrp_mo as m";
3484 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON s.rowid = m.fk_soc";
3485 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3486 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3487 }
3488 $sql .= " WHERE m.rowid = mp.fk_mo";
3489 $sql .= " AND m.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'mrp').")";
3490 $sql .= " AND mp.fk_product = ".((int) $this->id);
3491 $sql .= " AND (mp.disable_stock_change IN (0) OR mp.disable_stock_change IS NULL)";
3492 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3493 $sql .= " AND m.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3494 }
3495 if ($socid > 0) {
3496 $sql .= " AND m.fk_soc = ".((int) $socid);
3497 }
3498 if ($filtrestatut != '') {
3499 $sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
3500 }
3501 if (!empty($dateofvirtualstock)) {
3502 $sql .= " AND m.date_valid <= '".$this->db->idate($dateofvirtualstock)."'"; // better date to code ? end of production ?
3503 }
3504 if (!$serviceStockIsEnabled) {
3505 $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))";
3506 }
3507 $sql .= " GROUP BY role";
3508
3509 $this->stats_mrptoconsume['customers'] = 0;
3510 $this->stats_mrptoconsume['nb'] = 0;
3511 $this->stats_mrptoconsume['rows'] = 0;
3512 $this->stats_mrptoconsume['qty'] = 0;
3513 $this->stats_mrptoproduce['customers'] = 0;
3514 $this->stats_mrptoproduce['nb'] = 0;
3515 $this->stats_mrptoproduce['rows'] = 0;
3516 $this->stats_mrptoproduce['qty'] = 0;
3517
3518 $result = $this->db->query($sql);
3519 if ($result) {
3520 while ($obj = $this->db->fetch_object($result)) {
3521 if ($obj->role == 'toconsume') {
3522 $this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3523 $this->stats_mrptoconsume['nb'] += $obj->nb;
3524 $this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3525 $this->stats_mrptoconsume['qty'] += ($obj->qty ? $obj->qty : 0);
3526 }
3527 if ($obj->role == 'consumed') {
3528 //$this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3529 //$this->stats_mrptoconsume['nb'] += $obj->nb;
3530 //$this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3531 $this->stats_mrptoconsume['qty'] -= ($obj->qty ? $obj->qty : 0);
3532 }
3533 if ($obj->role == 'toproduce') {
3534 $this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3535 $this->stats_mrptoproduce['nb'] += $obj->nb;
3536 $this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3537 $this->stats_mrptoproduce['qty'] += ($obj->qty ? $obj->qty : 0);
3538 }
3539 if ($obj->role == 'produced') {
3540 //$this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3541 //$this->stats_mrptoproduce['nb'] += $obj->nb;
3542 //$this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3543 $this->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3544 }
3545 }
3546
3547 // Clean data
3548 if ($this->stats_mrptoconsume['qty'] < 0) {
3549 $this->stats_mrptoconsume['qty'] = 0;
3550 }
3551 if ($this->stats_mrptoproduce['qty'] < 0) {
3552 $this->stats_mrptoproduce['qty'] = 0;
3553 }
3554
3555 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3556 $reshook = $hookmanager->executeHooks('loadStatsInProduction', $parameters, $this, $action);
3557 if ($reshook > 0) {
3558 $this->stats_mrptoproduce = $hookmanager->resArray['stats_mrptoproduce'];
3559 }
3560
3561 return 1;
3562 } else {
3563 $this->error = $this->db->error();
3564 return -1;
3565 }
3566 }
3567
3568 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3575 public function load_stats_contrat($socid = 0)
3576 {
3577 // phpcs:enable
3578 global $conf, $user, $hookmanager, $action;
3579
3580 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3581 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3582 $sql .= " FROM ".$this->db->prefix()."contratdet as cd";
3583 $sql .= ", ".$this->db->prefix()."contrat as c";
3584 $sql .= ", ".$this->db->prefix()."societe as s";
3585 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3586 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3587 }
3588 $sql .= " WHERE c.rowid = cd.fk_contrat";
3589 $sql .= " AND c.fk_soc = s.rowid";
3590 $sql .= " AND c.entity IN (".getEntity('contract').")";
3591 $sql .= " AND cd.fk_product = ".((int) $this->id);
3592 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3593 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3594 }
3595 //$sql.= " AND c.statut != 0";
3596 if ($socid > 0) {
3597 $sql .= " AND c.fk_soc = ".((int) $socid);
3598 }
3599
3600 $result = $this->db->query($sql);
3601 if ($result) {
3602 $obj = $this->db->fetch_object($result);
3603 $this->stats_contrat['customers'] = $obj->nb_customers;
3604 $this->stats_contrat['nb'] = $obj->nb;
3605 $this->stats_contrat['rows'] = $obj->nb_rows;
3606 $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
3607
3608 // if it's a virtual product, maybe it is in contract by extension
3609 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3610 $TFather = $this->getFather();
3611 if (is_array($TFather) && !empty($TFather)) {
3612 foreach ($TFather as &$fatherData) {
3613 $pFather = new Product($this->db);
3614 $pFather->id = $fatherData['id'];
3615 $qtyCoef = $fatherData['qty'];
3616
3617 if ($fatherData['incdec']) {
3618 $pFather->load_stats_contrat($socid);
3619
3620 $this->stats_contrat['customers'] += $pFather->stats_contrat['customers'];
3621 $this->stats_contrat['nb'] += $pFather->stats_contrat['nb'];
3622 $this->stats_contrat['rows'] += $pFather->stats_contrat['rows'];
3623 $this->stats_contrat['qty'] += $pFather->stats_contrat['qty'] * $qtyCoef;
3624 }
3625 }
3626 }
3627 }
3628
3629 $parameters = array('socid' => $socid);
3630 $reshook = $hookmanager->executeHooks('loadStatsContract', $parameters, $this, $action);
3631 if ($reshook > 0) {
3632 $this->stats_contrat = $hookmanager->resArray['stats_contrat'];
3633 }
3634
3635 return 1;
3636 } else {
3637 $this->error = $this->db->error().' sql='.$sql;
3638 return -1;
3639 }
3640 }
3641
3642 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3649 public function load_stats_facture($socid = 0)
3650 {
3651 // phpcs:enable
3652 global $conf, $user, $hookmanager, $action;
3653
3654 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3655 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(".$this->db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
3656 $sql .= " FROM ".$this->db->prefix()."facturedet as fd";
3657 $sql .= ", ".$this->db->prefix()."facture as f";
3658 $sql .= ", ".$this->db->prefix()."societe as s";
3659 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3660 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3661 }
3662 $sql .= " WHERE f.rowid = fd.fk_facture";
3663 $sql .= " AND f.fk_soc = s.rowid";
3664 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3665 $sql .= " AND fd.fk_product = ".((int) $this->id);
3666 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3667 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3668 }
3669 //$sql.= " AND f.fk_statut != 0";
3670 if ($socid > 0) {
3671 $sql .= " AND f.fk_soc = ".((int) $socid);
3672 }
3673
3674 $result = $this->db->query($sql);
3675 if ($result) {
3676 $obj = $this->db->fetch_object($result);
3677 $this->stats_facture['customers'] = $obj->nb_customers;
3678 $this->stats_facture['nb'] = $obj->nb;
3679 $this->stats_facture['rows'] = $obj->nb_rows;
3680 $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
3681
3682 // if it's a virtual product, maybe it is in invoice by extension
3683 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3684 $TFather = $this->getFather();
3685 if (is_array($TFather) && !empty($TFather)) {
3686 foreach ($TFather as &$fatherData) {
3687 $pFather = new Product($this->db);
3688 $pFather->id = $fatherData['id'];
3689 $qtyCoef = $fatherData['qty'];
3690
3691 if ($fatherData['incdec']) {
3692 $pFather->load_stats_facture($socid);
3693
3694 $this->stats_facture['customers'] += $pFather->stats_facture['customers'];
3695 $this->stats_facture['nb'] += $pFather->stats_facture['nb'];
3696 $this->stats_facture['rows'] += $pFather->stats_facture['rows'];
3697 $this->stats_facture['qty'] += $pFather->stats_facture['qty'] * $qtyCoef;
3698 }
3699 }
3700 }
3701 }
3702
3703 $parameters = array('socid' => $socid);
3704 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoice', $parameters, $this, $action);
3705 if ($reshook > 0) {
3706 $this->stats_facture = $hookmanager->resArray['stats_facture'];
3707 }
3708
3709 return 1;
3710 } else {
3711 $this->error = $this->db->error();
3712 return -1;
3713 }
3714 }
3715
3716
3717 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3724 public function load_stats_facturerec($socid = 0)
3725 {
3726 // phpcs:enable
3727 global $conf, $user, $hookmanager, $action;
3728
3729 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3730 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3731 $sql .= " FROM ".MAIN_DB_PREFIX."facturedet_rec as fd";
3732 $sql .= ", ".MAIN_DB_PREFIX."facture_rec as f";
3733 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
3734 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3735 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3736 }
3737 $sql .= " WHERE f.rowid = fd.fk_facture";
3738 $sql .= " AND f.fk_soc = s.rowid";
3739 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3740 $sql .= " AND fd.fk_product = ".((int) $this->id);
3741 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3742 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3743 }
3744 //$sql.= " AND f.fk_statut != 0";
3745 if ($socid > 0) {
3746 $sql .= " AND f.fk_soc = ".((int) $socid);
3747 }
3748
3749 $result = $this->db->query($sql);
3750 if ($result) {
3751 $obj = $this->db->fetch_object($result);
3752 $this->stats_facturerec['customers'] = $obj->nb_customers;
3753 $this->stats_facturerec['nb'] = $obj->nb;
3754 $this->stats_facturerec['rows'] = $obj->nb_rows;
3755 $this->stats_facturerec['qty'] = $obj->qty ? $obj->qty : 0;
3756
3757 // if it's a virtual product, maybe it is in invoice by extension
3758 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3759 $TFather = $this->getFather();
3760 if (is_array($TFather) && !empty($TFather)) {
3761 foreach ($TFather as &$fatherData) {
3762 $pFather = new Product($this->db);
3763 $pFather->id = $fatherData['id'];
3764 $qtyCoef = $fatherData['qty'];
3765
3766 if ($fatherData['incdec']) {
3767 $pFather->load_stats_facture($socid);
3768
3769 $this->stats_facturerec['customers'] += $pFather->stats_facturerec['customers'];
3770 $this->stats_facturerec['nb'] += $pFather->stats_facturerec['nb'];
3771 $this->stats_facturerec['rows'] += $pFather->stats_facturerec['rows'];
3772 $this->stats_facturerec['qty'] += $pFather->stats_facturerec['qty'] * $qtyCoef;
3773 }
3774 }
3775 }
3776 }
3777
3778 $parameters = array('socid' => $socid);
3779 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoiceRec', $parameters, $this, $action);
3780 if ($reshook > 0) {
3781 $this->stats_facturerec = $hookmanager->resArray['stats_facturerec'];
3782 }
3783
3784 return 1;
3785 } else {
3786 $this->error = $this->db->error();
3787 return -1;
3788 }
3789 }
3790
3791 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3798 public function load_stats_facture_fournisseur($socid = 0)
3799 {
3800 // phpcs:enable
3801 global $conf, $user, $hookmanager, $action;
3802
3803 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
3804 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3805 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as fd";
3806 $sql .= ", ".$this->db->prefix()."facture_fourn as f";
3807 $sql .= ", ".$this->db->prefix()."societe as s";
3808 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3809 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3810 }
3811 $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
3812 $sql .= " AND f.fk_soc = s.rowid";
3813 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
3814 $sql .= " AND fd.fk_product = ".((int) $this->id);
3815 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3816 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3817 }
3818 //$sql.= " AND f.fk_statut != 0";
3819 if ($socid > 0) {
3820 $sql .= " AND f.fk_soc = ".((int) $socid);
3821 }
3822
3823 $result = $this->db->query($sql);
3824 if ($result) {
3825 $obj = $this->db->fetch_object($result);
3826 $this->stats_facture_fournisseur['suppliers'] = $obj->nb_suppliers;
3827 $this->stats_facture_fournisseur['nb'] = $obj->nb;
3828 $this->stats_facture_fournisseur['rows'] = $obj->nb_rows;
3829 $this->stats_facture_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3830
3831 $parameters = array('socid' => $socid);
3832 $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoice', $parameters, $this, $action);
3833 if ($reshook > 0) {
3834 $this->stats_facture_fournisseur = $hookmanager->resArray['stats_facture_fournisseur'];
3835 }
3836
3837 return 1;
3838 } else {
3839 $this->error = $this->db->error();
3840 return -1;
3841 }
3842 }
3843
3844 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3853 private function _get_stats($sql, $mode, $year = 0)
3854 {
3855 // phpcs:enable
3856 $tab = array();
3857
3858 $resql = $this->db->query($sql);
3859 if ($resql) {
3860 $num = $this->db->num_rows($resql);
3861 $i = 0;
3862 while ($i < $num) {
3863 $arr = $this->db->fetch_array($resql);
3864 $keyfortab = (string) $arr[1];
3865 if ($year == -1) {
3866 $keyfortab = substr($keyfortab, -2);
3867 }
3868
3869 if ($mode == 'byunit') {
3870 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[0]; // 1st field
3871 } elseif ($mode == 'bynumber') {
3872 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3873 } elseif ($mode == 'byamount') {
3874 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3875 } else {
3876 // Bad value for $mode
3877 return -1;
3878 }
3879 $i++;
3880 }
3881 } else {
3882 $this->error = $this->db->error().' sql='.$sql;
3883 return -1;
3884 }
3885
3886 if (empty($year)) {
3887 $year = strftime('%Y', time());
3888 $month = strftime('%m', time());
3889 } elseif ($year == -1) {
3890 $year = '';
3891 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3892 } else {
3893 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3894 }
3895
3896 $result = array();
3897
3898 for ($j = 0; $j < 12; $j++) {
3899 // $ids is 'D', 'N', 'O', 'S', ... (First letter of month in user language)
3900 $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, 1970), "%b"), 1, 'right', 'UTF-8', 1));
3901
3902 //print $idx.'-'.$year.'-'.$month.'<br>';
3903 $result[$j] = array($idx, isset($tab[$year.$month]) ? $tab[$year.$month] : 0);
3904 // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
3905
3906 $month = "0".($month - 1);
3907 if (dol_strlen($month) == 3) {
3908 $month = substr($month, 1);
3909 }
3910 if ($month == 0) {
3911 $month = 12;
3912 $year = $year - 1;
3913 }
3914 }
3915
3916 return array_reverse($result);
3917 }
3918
3919
3920 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3931 public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3932 {
3933 // phpcs:enable
3934 global $conf;
3935 global $user;
3936
3937 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
3938 if ($mode == 'bynumber') {
3939 $sql .= ", count(DISTINCT f.rowid)";
3940 }
3941 $sql .= ", sum(d.total_ht) as total_ht";
3942 $sql .= " FROM ".$this->db->prefix()."facturedet as d, ".$this->db->prefix()."facture as f, ".$this->db->prefix()."societe as s";
3943 if ($filteronproducttype >= 0) {
3944 $sql .= ", ".$this->db->prefix()."product as p";
3945 }
3946 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3947 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3948 }
3949 $sql .= " WHERE f.rowid = d.fk_facture";
3950 if ($this->id > 0) {
3951 $sql .= " AND d.fk_product = ".((int) $this->id);
3952 } else {
3953 $sql .= " AND d.fk_product > 0";
3954 }
3955 if ($filteronproducttype >= 0) {
3956 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
3957 }
3958 $sql .= " AND f.fk_soc = s.rowid";
3959 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3960 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3961 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3962 }
3963 if ($socid > 0) {
3964 $sql .= " AND f.fk_soc = $socid";
3965 }
3966 $sql .= $morefilter;
3967 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
3968 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
3969
3970 return $this->_get_stats($sql, $mode, $year);
3971 }
3972
3973
3974 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3985 public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3986 {
3987 // phpcs:enable
3988 global $conf;
3989 global $user;
3990
3991 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
3992 if ($mode == 'bynumber') {
3993 $sql .= ", count(DISTINCT f.rowid)";
3994 }
3995 $sql .= ", sum(d.total_ht) as total_ht";
3996 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as d, ".$this->db->prefix()."facture_fourn as f, ".$this->db->prefix()."societe as s";
3997 if ($filteronproducttype >= 0) {
3998 $sql .= ", ".$this->db->prefix()."product as p";
3999 }
4000 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4001 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4002 }
4003 $sql .= " WHERE f.rowid = d.fk_facture_fourn";
4004 if ($this->id > 0) {
4005 $sql .= " AND d.fk_product = ".((int) $this->id);
4006 } else {
4007 $sql .= " AND d.fk_product > 0";
4008 }
4009 if ($filteronproducttype >= 0) {
4010 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4011 }
4012 $sql .= " AND f.fk_soc = s.rowid";
4013 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4014 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4015 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4016 }
4017 if ($socid > 0) {
4018 $sql .= " AND f.fk_soc = $socid";
4019 }
4020 $sql .= $morefilter;
4021 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4022 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4023
4024 return $this->_get_stats($sql, $mode, $year);
4025 }
4026
4027 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4038 public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4039 {
4040 // phpcs:enable
4041 global $conf, $user;
4042
4043 $sql = "SELECT sum(d.qty) as qty, date_format(p.datep, '%Y%m')";
4044 if ($mode == 'bynumber') {
4045 $sql .= ", count(DISTINCT p.rowid)";
4046 }
4047 $sql .= ", sum(d.total_ht) as total_ht";
4048 $sql .= " FROM ".$this->db->prefix()."propaldet as d, ".$this->db->prefix()."propal as p, ".$this->db->prefix()."societe as s";
4049 if ($filteronproducttype >= 0) {
4050 $sql .= ", ".$this->db->prefix()."product as prod";
4051 }
4052 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4053 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4054 }
4055 $sql .= " WHERE p.rowid = d.fk_propal";
4056 if ($this->id > 0) {
4057 $sql .= " AND d.fk_product = ".((int) $this->id);
4058 } else {
4059 $sql .= " AND d.fk_product > 0";
4060 }
4061 if ($filteronproducttype >= 0) {
4062 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4063 }
4064 $sql .= " AND p.fk_soc = s.rowid";
4065 $sql .= " AND p.entity IN (".getEntity('propal').")";
4066 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4067 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4068 }
4069 if ($socid > 0) {
4070 $sql .= " AND p.fk_soc = ".((int) $socid);
4071 }
4072 $sql .= $morefilter;
4073 $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
4074 $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
4075
4076 return $this->_get_stats($sql, $mode, $year);
4077 }
4078
4079 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4090 public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4091 {
4092 // phpcs:enable
4093 global $conf;
4094 global $user;
4095
4096 $sql = "SELECT sum(d.qty) as qty, date_format(p.date_valid, '%Y%m')";
4097 if ($mode == 'bynumber') {
4098 $sql .= ", count(DISTINCT p.rowid)";
4099 }
4100 $sql .= ", sum(d.total_ht) as total_ht";
4101 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as d, ".$this->db->prefix()."supplier_proposal as p, ".$this->db->prefix()."societe as s";
4102 if ($filteronproducttype >= 0) {
4103 $sql .= ", ".$this->db->prefix()."product as prod";
4104 }
4105 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4106 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4107 }
4108 $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
4109 if ($this->id > 0) {
4110 $sql .= " AND d.fk_product = ".((int) $this->id);
4111 } else {
4112 $sql .= " AND d.fk_product > 0";
4113 }
4114 if ($filteronproducttype >= 0) {
4115 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4116 }
4117 $sql .= " AND p.fk_soc = s.rowid";
4118 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
4119 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4120 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4121 }
4122 if ($socid > 0) {
4123 $sql .= " AND p.fk_soc = ".((int) $socid);
4124 }
4125 $sql .= $morefilter;
4126 $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
4127 $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
4128
4129 return $this->_get_stats($sql, $mode, $year);
4130 }
4131
4132 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4143 public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4144 {
4145 // phpcs:enable
4146 global $conf, $user;
4147
4148 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4149 if ($mode == 'bynumber') {
4150 $sql .= ", count(DISTINCT c.rowid)";
4151 }
4152 $sql .= ", sum(d.total_ht) as total_ht";
4153 $sql .= " FROM ".$this->db->prefix()."commandedet as d, ".$this->db->prefix()."commande as c, ".$this->db->prefix()."societe as s";
4154 if ($filteronproducttype >= 0) {
4155 $sql .= ", ".$this->db->prefix()."product as p";
4156 }
4157 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4158 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4159 }
4160 $sql .= " WHERE c.rowid = d.fk_commande";
4161 if ($this->id > 0) {
4162 $sql .= " AND d.fk_product = ".((int) $this->id);
4163 } else {
4164 $sql .= " AND d.fk_product > 0";
4165 }
4166 if ($filteronproducttype >= 0) {
4167 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4168 }
4169 $sql .= " AND c.fk_soc = s.rowid";
4170 $sql .= " AND c.entity IN (".getEntity('commande').")";
4171 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4172 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4173 }
4174 if ($socid > 0) {
4175 $sql .= " AND c.fk_soc = ".((int) $socid);
4176 }
4177 $sql .= $morefilter;
4178 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4179 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4180
4181 return $this->_get_stats($sql, $mode, $year);
4182 }
4183
4184 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4195 public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4196 {
4197 // phpcs:enable
4198 global $conf, $user;
4199
4200 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4201 if ($mode == 'bynumber') {
4202 $sql .= ", count(DISTINCT c.rowid)";
4203 }
4204 $sql .= ", sum(d.total_ht) as total_ht";
4205 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as d, ".$this->db->prefix()."commande_fournisseur as c, ".$this->db->prefix()."societe as s";
4206 if ($filteronproducttype >= 0) {
4207 $sql .= ", ".$this->db->prefix()."product as p";
4208 }
4209 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4210 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4211 }
4212 $sql .= " WHERE c.rowid = d.fk_commande";
4213 if ($this->id > 0) {
4214 $sql .= " AND d.fk_product = ".((int) $this->id);
4215 } else {
4216 $sql .= " AND d.fk_product > 0";
4217 }
4218 if ($filteronproducttype >= 0) {
4219 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4220 }
4221 $sql .= " AND c.fk_soc = s.rowid";
4222 $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
4223 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4224 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4225 }
4226 if ($socid > 0) {
4227 $sql .= " AND c.fk_soc = ".((int) $socid);
4228 }
4229 $sql .= $morefilter;
4230 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4231 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4232
4233 return $this->_get_stats($sql, $mode, $year);
4234 }
4235
4236 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4247 public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4248 {
4249 // phpcs:enable
4250 global $conf, $user;
4251
4252 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_contrat, '%Y%m')";
4253 if ($mode == 'bynumber') {
4254 $sql .= ", count(DISTINCT c.rowid)";
4255 }
4256 $sql .= ", sum(d.total_ht) as total_ht";
4257 $sql .= " FROM ".$this->db->prefix()."contratdet as d, ".$this->db->prefix()."contrat as c, ".$this->db->prefix()."societe as s";
4258 if ($filteronproducttype >= 0) {
4259 $sql .= ", ".$this->db->prefix()."product as p";
4260 }
4261 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4262 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4263 }
4264
4265 $sql .= " WHERE c.entity IN (".getEntity('contract').")";
4266 $sql .= " AND c.rowid = d.fk_contrat";
4267
4268 if ($this->id > 0) {
4269 $sql .= " AND d.fk_product = ".((int) $this->id);
4270 } else {
4271 $sql .= " AND d.fk_product > 0";
4272 }
4273 if ($filteronproducttype >= 0) {
4274 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4275 }
4276 $sql .= " AND c.fk_soc = s.rowid";
4277
4278 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4279 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4280 }
4281 if ($socid > 0) {
4282 $sql .= " AND c.fk_soc = ".((int) $socid);
4283 }
4284 $sql .= $morefilter;
4285 $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
4286 $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
4287
4288 return $this->_get_stats($sql, $mode, $year);
4289 }
4290
4291 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4302 public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4303 {
4304 // phpcs:enable
4305 global $conf, $user;
4306
4307 $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
4308 if ($mode == 'bynumber') {
4309 $sql .= ", count(DISTINCT d.rowid)";
4310 }
4311 $sql .= " FROM ".$this->db->prefix()."mrp_mo as d LEFT JOIN ".$this->db->prefix()."societe as s ON d.fk_soc = s.rowid";
4312 if ($filteronproducttype >= 0) {
4313 $sql .= ", ".$this->db->prefix()."product as p";
4314 }
4315 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4316 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4317 }
4318
4319 $sql .= " WHERE d.entity IN (".getEntity('mo').")";
4320 $sql .= " AND d.status > 0";
4321
4322 if ($this->id > 0) {
4323 $sql .= " AND d.fk_product = ".((int) $this->id);
4324 } else {
4325 $sql .= " AND d.fk_product > 0";
4326 }
4327 if ($filteronproducttype >= 0) {
4328 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4329 }
4330
4331 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4332 $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4333 }
4334 if ($socid > 0) {
4335 $sql .= " AND d.fk_soc = ".((int) $socid);
4336 }
4337 $sql .= $morefilter;
4338 $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
4339 $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
4340
4341 return $this->_get_stats($sql, $mode, $year);
4342 }
4343
4344 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4355 public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4356 {
4357 global $user;
4358
4359 // phpcs:enable
4360 // Clean parameters
4361 if (!is_numeric($id_pere)) {
4362 $id_pere = 0;
4363 }
4364 if (!is_numeric($id_fils)) {
4365 $id_fils = 0;
4366 }
4367 if (!is_numeric($incdec)) {
4368 $incdec = 0;
4369 }
4370
4371 $result = $this->del_sousproduit($id_pere, $id_fils);
4372 if ($result < 0) {
4373 return $result;
4374 }
4375
4376 // Check not already father of id_pere (to avoid father -> child -> father links)
4377 $sql = "SELECT fk_product_pere from ".$this->db->prefix()."product_association";
4378 $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere);
4379 if (!$this->db->query($sql)) {
4380 dol_print_error($this->db);
4381 return -1;
4382 } else {
4383 //Selection of the highest row
4384 $sql = "SELECT MAX(rang) as max_rank FROM ".$this->db->prefix()."product_association";
4385 $sql .= " WHERE fk_product_pere = ".((int) $id_pere);
4386 $resql = $this->db->query($sql);
4387 if ($resql) {
4388 $obj = $this->db->fetch_object($resql);
4389 $rank = $obj->max_rank + 1;
4390 //Addition of a product with the highest rank +1
4391 $sql = "INSERT INTO ".$this->db->prefix()."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)";
4392 $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".price2num($qty, 'MS').", ".price2num($incdec, 'MS').", ".((int) $rank).")";
4393 if (! $this->db->query($sql)) {
4394 dol_print_error($this->db);
4395 return -1;
4396 } else {
4397 if (!$notrigger) {
4398 // Call trigger
4399 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_ADD', $user);
4400 if ($result < 0) {
4401 $this->error = $this->db->lasterror();
4402 dol_syslog(get_class($this).'::addSubproduct error='.$this->error, LOG_ERR);
4403 return -1;
4404 }
4405 }
4406 // End call triggers
4407
4408 return 1;
4409 }
4410 } else {
4411 dol_print_error($this->db);
4412 return -1;
4413 }
4414 }
4415 }
4416
4417 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4428 public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4429 {
4430 global $user;
4431
4432 // phpcs:enable
4433 // Clean parameters
4434 if (!is_numeric($id_pere)) {
4435 $id_pere = 0;
4436 }
4437 if (!is_numeric($id_fils)) {
4438 $id_fils = 0;
4439 }
4440 if (!is_numeric($incdec)) {
4441 $incdec = 1;
4442 }
4443 if (!is_numeric($qty)) {
4444 $qty = 1;
4445 }
4446
4447 $sql = 'UPDATE '.$this->db->prefix().'product_association SET ';
4448 $sql .= 'qty = '.price2num($qty, 'MS');
4449 $sql .= ',incdec = '.price2num($incdec, 'MS');
4450 $sql .= ' WHERE fk_product_pere = '.((int) $id_pere).' AND fk_product_fils = '.((int) $id_fils);
4451
4452 if (!$this->db->query($sql)) {
4453 dol_print_error($this->db);
4454 return -1;
4455 } else {
4456 if (!$notrigger) {
4457 // Call trigger
4458 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_UPDATE', $user);
4459 if ($result < 0) {
4460 $this->error = $this->db->lasterror();
4461 dol_syslog(get_class($this).'::updateSubproduct error='.$this->error, LOG_ERR);
4462 return -1;
4463 }
4464 // End call triggers
4465 }
4466
4467 return 1;
4468 }
4469 }
4470
4471 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4480 public function del_sousproduit($fk_parent, $fk_child, $notrigger = 0)
4481 {
4482 global $user;
4483
4484 // phpcs:enable
4485 if (!is_numeric($fk_parent)) {
4486 $fk_parent = 0;
4487 }
4488 if (!is_numeric($fk_child)) {
4489 $fk_child = 0;
4490 }
4491
4492 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
4493 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4494 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4495
4496 dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
4497 if (!$this->db->query($sql)) {
4498 dol_print_error($this->db);
4499 return -1;
4500 }
4501
4502 // Updated ranks so that none are missing
4503 $sqlrank = "SELECT rowid, rang FROM ".$this->db->prefix()."product_association";
4504 $sqlrank.= " WHERE fk_product_pere = ".((int) $fk_parent);
4505 $sqlrank.= " ORDER BY rang";
4506 $resqlrank = $this->db->query($sqlrank);
4507 if ($resqlrank) {
4508 $cpt = 0;
4509 while ($objrank = $this->db->fetch_object($resqlrank)) {
4510 $cpt++;
4511 $sql = "UPDATE ".$this->db->prefix()."product_association";
4512 $sql.= " SET rang = ".((int) $cpt);
4513 $sql.= " WHERE rowid = ".((int) $objrank->rowid);
4514 if (! $this->db->query($sql)) {
4515 dol_print_error($this->db);
4516 return -1;
4517 }
4518 }
4519 }
4520
4521 if (!$notrigger) {
4522 // Call trigger
4523 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_DELETE', $user);
4524 if ($result < 0) {
4525 $this->error = $this->db->lasterror();
4526 dol_syslog(get_class($this).'::delSubproduct error='.$this->error, LOG_ERR);
4527 return -1;
4528 }
4529 // End call triggers
4530 }
4531
4532 return 1;
4533 }
4534
4535 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4543 public function is_sousproduit($fk_parent, $fk_child)
4544 {
4545 // phpcs:enable
4546 $sql = "SELECT fk_product_pere, qty, incdec";
4547 $sql .= " FROM ".$this->db->prefix()."product_association";
4548 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4549 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4550
4551 $result = $this->db->query($sql);
4552 if ($result) {
4553 $num = $this->db->num_rows($result);
4554
4555 if ($num > 0) {
4556 $obj = $this->db->fetch_object($result);
4557
4558 $this->is_sousproduit_qty = $obj->qty;
4559 $this->is_sousproduit_incdec = $obj->incdec;
4560
4561 return true;
4562 } else {
4563 return false;
4564 }
4565 } else {
4566 dol_print_error($this->db);
4567 return -1;
4568 }
4569 }
4570
4571
4572 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4583 public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
4584 {
4585 // phpcs:enable
4586 global $conf;
4587
4588 $now = dol_now();
4589
4590 dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
4591
4592 // Clean parameters
4593 $quantity = price2num($quantity, 'MS');
4594
4595 if ($ref_fourn) {
4596 $sql = "SELECT rowid, fk_product";
4597 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4598 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4599 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4600 $sql .= " AND fk_product <> ".((int) $this->id);
4601 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4602
4603 $resql = $this->db->query($sql);
4604 if ($resql) {
4605 $obj = $this->db->fetch_object($resql);
4606 if ($obj) {
4607 // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
4608 $this->product_id_already_linked = $obj->fk_product;
4609 return -3;
4610 }
4611 $this->db->free($resql);
4612 }
4613 }
4614
4615 $sql = "SELECT rowid";
4616 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4617 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4618 if ($ref_fourn) {
4619 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4620 } else {
4621 $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
4622 }
4623 $sql .= " AND quantity = ".((float) $quantity);
4624 $sql .= " AND fk_product = ".((int) $this->id);
4625 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4626
4627 $resql = $this->db->query($sql);
4628 if ($resql) {
4629 $obj = $this->db->fetch_object($resql);
4630
4631 // The reference supplier does not exist, we create it for this product.
4632 if (empty($obj)) {
4633 $sql = "INSERT INTO ".$this->db->prefix()."product_fournisseur_price(";
4634 $sql .= "datec";
4635 $sql .= ", entity";
4636 $sql .= ", fk_product";
4637 $sql .= ", fk_soc";
4638 $sql .= ", ref_fourn";
4639 $sql .= ", quantity";
4640 $sql .= ", fk_user";
4641 $sql .= ", tva_tx";
4642 $sql .= ") VALUES (";
4643 $sql .= "'".$this->db->idate($now)."'";
4644 $sql .= ", ".$conf->entity;
4645 $sql .= ", ".$this->id;
4646 $sql .= ", ".$id_fourn;
4647 $sql .= ", '".$this->db->escape($ref_fourn)."'";
4648 $sql .= ", ".$quantity;
4649 $sql .= ", ".$user->id;
4650 $sql .= ", 0";
4651 $sql .= ")";
4652
4653 if ($this->db->query($sql)) {
4654 $this->product_fourn_price_id = $this->db->last_insert_id($this->db->prefix()."product_fournisseur_price");
4655 return 1;
4656 } else {
4657 $this->error = $this->db->lasterror();
4658 return -1;
4659 }
4660 } else {
4661 // If the supplier price already exists for this product and quantity
4662 $this->product_fourn_price_id = $obj->rowid;
4663 return 0;
4664 }
4665 } else {
4666 $this->error = $this->db->lasterror();
4667 return -2;
4668 }
4669 }
4670
4671
4672 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4678 public function list_suppliers()
4679 {
4680 // phpcs:enable
4681 global $conf;
4682
4683 $list = array();
4684
4685 $sql = "SELECT DISTINCT p.fk_soc";
4686 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as p";
4687 $sql .= " WHERE p.fk_product = ".((int) $this->id);
4688 $sql .= " AND p.entity = ".((int) $conf->entity);
4689
4690 $result = $this->db->query($sql);
4691 if ($result) {
4692 $num = $this->db->num_rows($result);
4693 $i = 0;
4694 while ($i < $num) {
4695 $obj = $this->db->fetch_object($result);
4696 $list[$i] = $obj->fk_soc;
4697 $i++;
4698 }
4699 }
4700
4701 return $list;
4702 }
4703
4704 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4712 public function clone_price($fromId, $toId)
4713 {
4714 global $conf, $user;
4715
4716 $now = dol_now();
4717
4718 $this->db->begin();
4719
4720 // prices
4721 $sql = "INSERT INTO ".$this->db->prefix()."product_price (";
4722 $sql .= " entity";
4723 $sql .= ", fk_product";
4724 $sql .= ", date_price";
4725 $sql .= ", price_level";
4726 $sql .= ", price";
4727 $sql .= ", price_ttc";
4728 $sql .= ", price_min";
4729 $sql .= ", price_min_ttc";
4730 $sql .= ", price_base_type";
4731 $sql .= ", default_vat_code";
4732 $sql .= ", tva_tx";
4733 $sql .= ", recuperableonly";
4734 $sql .= ", localtax1_tx";
4735 $sql .= ", localtax1_type";
4736 $sql .= ", localtax2_tx";
4737 $sql .= ", localtax2_type";
4738 $sql .= ", fk_user_author";
4739 $sql .= ", tosell";
4740 $sql .= ", price_by_qty";
4741 $sql .= ", fk_price_expression";
4742 $sql .= ", fk_multicurrency";
4743 $sql .= ", multicurrency_code";
4744 $sql .= ", multicurrency_tx";
4745 $sql .= ", multicurrency_price";
4746 $sql .= ", multicurrency_price_ttc";
4747 $sql .= ")";
4748 $sql .= " SELECT";
4749 $sql .= " entity";
4750 $sql .= ", ".$toId;
4751 $sql .= ", '".$this->db->idate($now)."'";
4752 $sql .= ", price_level";
4753 $sql .= ", price";
4754 $sql .= ", price_ttc";
4755 $sql .= ", price_min";
4756 $sql .= ", price_min_ttc";
4757 $sql .= ", price_base_type";
4758 $sql .= ", default_vat_code";
4759 $sql .= ", tva_tx";
4760 $sql .= ", recuperableonly";
4761 $sql .= ", localtax1_tx";
4762 $sql .= ", localtax1_type";
4763 $sql .= ", localtax2_tx";
4764 $sql .= ", localtax2_type";
4765 $sql .= ", ".$user->id;
4766 $sql .= ", tosell";
4767 $sql .= ", price_by_qty";
4768 $sql .= ", fk_price_expression";
4769 $sql .= ", fk_multicurrency";
4770 $sql .= ", multicurrency_code";
4771 $sql .= ", multicurrency_tx";
4772 $sql .= ", multicurrency_price";
4773 $sql .= ", multicurrency_price_ttc";
4774 $sql .= " FROM ".$this->db->prefix()."product_price ps";
4775 $sql .= " WHERE fk_product = ".((int) $fromId);
4776 $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)";
4777 $sql .= " ORDER BY date_price DESC";
4778
4779 dol_syslog(__METHOD__, LOG_DEBUG);
4780 $resql = $this->db->query($sql);
4781 if (!$resql) {
4782 $this->db->rollback();
4783 return -1;
4784 }
4785
4786 $this->db->commit();
4787 return 1;
4788 }
4789
4790 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4798 public function clone_associations($fromId, $toId)
4799 {
4800 // phpcs:enable
4801 $this->db->begin();
4802
4803 $sql = 'INSERT INTO '.$this->db->prefix().'product_association (fk_product_pere, fk_product_fils, qty, incdec)';
4804 $sql .= " SELECT ".$toId.", fk_product_fils, qty, incdec FROM ".$this->db->prefix()."product_association";
4805 $sql .= " WHERE fk_product_pere = ".((int) $fromId);
4806
4807 dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
4808 if (!$this->db->query($sql)) {
4809 $this->db->rollback();
4810 return -1;
4811 }
4812
4813 $this->db->commit();
4814 return 1;
4815 }
4816
4817 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4825 public function clone_fournisseurs($fromId, $toId)
4826 {
4827 // phpcs:enable
4828 $this->db->begin();
4829
4830 $now = dol_now();
4831
4832 // les fournisseurs
4833 /*$sql = "INSERT ".$this->db->prefix()."product_fournisseur ("
4834 . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
4835 . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
4836 . " FROM ".$this->db->prefix()."product_fournisseur"
4837 . " WHERE fk_product = ".((int) $fromId);
4838
4839 if ( ! $this->db->query($sql ) )
4840 {
4841 $this->db->rollback();
4842 return -1;
4843 }*/
4844
4845 // les prix de fournisseurs.
4846 $sql = "INSERT ".$this->db->prefix()."product_fournisseur_price (";
4847 $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user, tva_tx)";
4848 $sql .= " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, price, quantity, fk_user, tva_tx";
4849 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4850 $sql .= " WHERE fk_product = ".((int) $fromId);
4851
4852 dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
4853 $resql = $this->db->query($sql);
4854 if (!$resql) {
4855 $this->db->rollback();
4856 return -1;
4857 } else {
4858 $this->db->commit();
4859 return 1;
4860 }
4861 }
4862
4863 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4876 public function fetch_prod_arbo($prod, $compl_path = '', $multiply = 1, $level = 1, $id_parent = 0, $ignore_stock_load = 0)
4877 {
4878 // phpcs:enable
4879 global $conf, $langs;
4880
4881 $tmpproduct = null;
4882 //var_dump($prod);
4883 foreach ($prod as $id_product => $desc_pere) { // $id_product is 0 (first call starting with root top) or an id of a sub_product
4884 if (is_array($desc_pere)) { // If desc_pere is an array, this means it's a child
4885 $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
4886 $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
4887 $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
4888 $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
4889 $incdec = (!empty($desc_pere[4]) ? $desc_pere[4] : 0);
4890
4891 if ($multiply < 1) {
4892 $multiply = 1;
4893 }
4894
4895 //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
4896 if (is_null($tmpproduct)) {
4897 $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
4898 }
4899 $tmpproduct->fetch($id); // Load product to get ->ref
4900
4901 if (empty($ignore_stock_load) && ($tmpproduct->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
4902 $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
4903 }
4904
4905 $this->res[] = array(
4906 'id'=>$id, // Id product
4907 'id_parent'=>$id_parent,
4908 'ref'=>$tmpproduct->ref, // Ref product
4909 'nb'=>$nb, // Nb of units that compose parent product
4910 'nb_total'=>$nb * $multiply, // Nb of units for all nb of product
4911 'stock'=>$tmpproduct->stock_reel, // Stock
4912 'stock_alert'=>$tmpproduct->seuil_stock_alerte, // Stock alert
4913 'label'=>$label,
4914 'fullpath'=>$compl_path.$label, // Label
4915 'type'=>$type, // Nb of units that compose parent product
4916 'desiredstock'=>$tmpproduct->desiredstock,
4917 'level'=>$level,
4918 'incdec'=>$incdec,
4919 'entity'=>$tmpproduct->entity
4920 );
4921
4922 // Recursive call if there is childs to child
4923 if (isset($desc_pere['childs']) && is_array($desc_pere['childs'])) {
4924 //print 'YYY We go down for '.$desc_pere[3]." -> \n";
4925 $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1] * $multiply, $level + 1, $id, $ignore_stock_load);
4926 }
4927 }
4928 }
4929 }
4930
4931 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4940 public function get_arbo_each_prod($multiply = 1, $ignore_stock_load = 0)
4941 {
4942 // phpcs:enable
4943 $this->res = array();
4944 if (isset($this->sousprods) && is_array($this->sousprods)) {
4945 foreach ($this->sousprods as $prod_name => $desc_product) {
4946 if (is_array($desc_product)) {
4947 $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id, $ignore_stock_load); // This set $this->res
4948 }
4949 }
4950 }
4951 //var_dump($res);
4952 return $this->res;
4953 }
4954
4962 public function hasFatherOrChild($mode = 0)
4963 {
4964 $nb = 0;
4965
4966 $sql = "SELECT COUNT(pa.rowid) as nb";
4967 $sql .= " FROM ".$this->db->prefix()."product_association as pa";
4968 if ($mode == 0) {
4969 $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id)." OR pa.fk_product_pere = ".((int) $this->id);
4970 } elseif ($mode == -1) {
4971 $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)
4972 } elseif ($mode == 1) {
4973 $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)
4974 }
4975
4976 $resql = $this->db->query($sql);
4977 if ($resql) {
4978 $obj = $this->db->fetch_object($resql);
4979 if ($obj) {
4980 $nb = $obj->nb;
4981 }
4982 } else {
4983 return -1;
4984 }
4985
4986 return $nb;
4987 }
4988
4994 public function hasVariants()
4995 {
4996 $nb = 0;
4997 $sql = "SELECT count(rowid) as nb FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_parent = ".((int) $this->id);
4998 $sql .= " AND entity IN (".getEntity('product').")";
4999
5000 $resql = $this->db->query($sql);
5001 if ($resql) {
5002 $obj = $this->db->fetch_object($resql);
5003 if ($obj) {
5004 $nb = $obj->nb;
5005 }
5006 }
5007
5008 return $nb;
5009 }
5010
5011
5017 public function isVariant()
5018 {
5019 global $conf;
5020 if (isModEnabled('variants')) {
5021 $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_child = ".((int) $this->id)." AND entity IN (".getEntity('product').")";
5022
5023 $query = $this->db->query($sql);
5024
5025 if ($query) {
5026 if (!$this->db->num_rows($query)) {
5027 return false;
5028 }
5029 return true;
5030 } else {
5031 dol_print_error($this->db);
5032 return -1;
5033 }
5034 } else {
5035 return false;
5036 }
5037 }
5038
5045 public function getFather()
5046 {
5047 $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";
5048 $sql .= ", p.tosell as status, p.tobuy as status_buy";
5049 $sql .= " FROM ".$this->db->prefix()."product_association as pa,";
5050 $sql .= " ".$this->db->prefix()."product as p";
5051 $sql .= " WHERE p.rowid = pa.fk_product_pere";
5052 $sql .= " AND pa.fk_product_fils = ".((int) $this->id);
5053
5054 $res = $this->db->query($sql);
5055 if ($res) {
5056 $prods = array();
5057 while ($record = $this->db->fetch_array($res)) {
5058 // $record['id'] = $record['rowid'] = id of father
5059 $prods[$record['id']]['id'] = $record['rowid'];
5060 $prods[$record['id']]['ref'] = $record['ref'];
5061 $prods[$record['id']]['label'] = $record['label'];
5062 $prods[$record['id']]['qty'] = $record['qty'];
5063 $prods[$record['id']]['incdec'] = $record['incdec'];
5064 $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
5065 $prods[$record['id']]['entity'] = $record['entity'];
5066 $prods[$record['id']]['status'] = $record['status'];
5067 $prods[$record['id']]['status_buy'] = $record['status_buy'];
5068 }
5069 return $prods;
5070 } else {
5071 dol_print_error($this->db);
5072 return -1;
5073 }
5074 }
5075
5076
5086 public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array())
5087 {
5088 if (empty($id)) {
5089 return array();
5090 }
5091
5092 $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
5093 $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
5094 $sql .= " pa.rowid as fk_association, pa.rang";
5095 $sql .= " FROM ".$this->db->prefix()."product as p,";
5096 $sql .= " ".$this->db->prefix()."product_association as pa";
5097 $sql .= " WHERE p.rowid = pa.fk_product_fils";
5098 $sql .= " AND pa.fk_product_pere = ".((int) $id);
5099 $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
5100 $sql.= " ORDER BY pa.rang";
5101
5102 dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG);
5103
5104 // Protection against infinite loop
5105 if ($level > 30) {
5106 return array();
5107 }
5108
5109 $res = $this->db->query($sql);
5110 if ($res) {
5111 $prods = array();
5112 if ($this->db->num_rows($res) > 0) {
5113 $parents[] = $id;
5114 }
5115
5116 while ($rec = $this->db->fetch_array($res)) {
5117 if (in_array($rec['id'], $parents)) {
5118 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);
5119 continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5120 }
5121
5122 $prods[$rec['rowid']] = array(
5123 0=>$rec['rowid'],
5124 1=>$rec['qty'],
5125 2=>$rec['fk_product_type'],
5126 3=>$this->db->escape($rec['label']),
5127 4=>$rec['incdec'],
5128 5=>$rec['ref'],
5129 6=>$rec['fk_association'],
5130 7=>$rec['rang']
5131 );
5132 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5133 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5134 if (empty($firstlevelonly)) {
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 // $weBillOrderOrShipmentReception is set to 'order' or 'shipmentreception'. it will be used to know how to make virtual stock
5845 // calculation when we have a stock increase or decrease on billing. Do we have to count orders to bill or shipment/reception to bill ?
5846 $weBillOrderOrShipmentReception = getDolGlobalString('STOCK_DO_WE_BILL_ORDER_OR_SHIPMENTECEPTION_FOR_VIRTUALSTOCK', 'order');
5847
5848 // Stock decrease mode
5849 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
5850 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
5851 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER')) {
5852 if (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER_INCLUDE_DRAFT')) { // By default, draft means "does not exist", so we do not include them by default, except if option is on
5853 $tmpnewprod = dol_clone($this, 1);
5854 $result = $tmpnewprod->load_stats_commande(0, '0', 1); // Get qty in draft orders
5855 $this->stock_theorique += $tmpnewprod->stats_commande['qty'];
5856 }
5857 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'order') {
5858 $this->stock_theorique -= $stock_commande_client;
5859 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
5860 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
5861 }
5862
5863 // Stock Increase mode
5864 if (getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION') || getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
5865 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5866 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) { // This option is similar to STOCK_CALCULATE_ON_RECEPTION_CLOSE but when module Reception is not enabled
5867 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5868 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) { // Warning: stock change "on approval", not on validation !
5869 if (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER_INCLUDE_DRAFT')) { // By default, draft means "does not exist", so we do not include them by default, except if option is on
5870 $tmpnewprod = dol_clone($this, 1);
5871 $result = $tmpnewprod->load_stats_commande_fournisseur(0, '0', 1); // Get qty in draft orders
5872 $this->stock_theorique += $this->stats_commande_fournisseur['qty'];
5873 }
5874 $this->stock_theorique -= $stock_reception_fournisseur;
5875 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'order') {
5876 $this->stock_theorique += $stock_commande_fournisseur;
5877 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
5878 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5879 }
5880
5881 $parameters = array('id'=>$this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
5882 // Note that $action and $object may have been modified by some hooks
5883 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
5884 if ($reshook > 0) {
5885 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
5886 } elseif ($reshook == 0 && isset($hookmanager->resArray['stock_stats_hook'])) {
5887 $this->stock_theorique += $hookmanager->resArray['stock_stats_hook'];
5888 }
5889
5890 return 1;
5891 }
5892
5893
5901 public function loadBatchInfo($batch)
5902 {
5903 $result = array();
5904
5905 $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";
5906 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
5907 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
5908 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
5909 $resql = $this->db->query($sql);
5910 if ($resql) {
5911 $num = $this->db->num_rows($resql);
5912 $i = 0;
5913 while ($i < $num) {
5914 $obj = $this->db->fetch_object($resql);
5915 $result[] = array('batch'=>$batch, 'eatby'=>$this->db->jdate($obj->eatby), 'sellby'=>$this->db->jdate($obj->sellby), 'qty'=>$obj->qty);
5916 $i++;
5917 }
5918 return $result;
5919 } else {
5920 dol_print_error($this->db);
5921 $this->db->rollback();
5922 return array();
5923 }
5924 }
5925
5926 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5934 public function add_photo($sdir, $file)
5935 {
5936 // phpcs:enable
5937 global $conf;
5938
5939 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5940
5941 $result = 0;
5942
5943 $dir = $sdir;
5944 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5945 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
5946 } else {
5947 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
5948 }
5949
5950 dol_mkdir($dir);
5951
5952 $dir_osencoded = $dir;
5953
5954 if (is_dir($dir_osencoded)) {
5955 $originImage = $dir.'/'.$file['name'];
5956
5957 // Cree fichier en taille origine
5958 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
5959
5960 if (file_exists(dol_osencode($originImage))) {
5961 // Create thumbs
5962 $this->addThumbs($originImage);
5963 }
5964 }
5965
5966 if (is_numeric($result) && $result > 0) {
5967 return 1;
5968 } else {
5969 return -1;
5970 }
5971 }
5972
5973 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5980 public function is_photo_available($sdir)
5981 {
5982 // phpcs:enable
5983 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5984 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5985
5986 global $conf;
5987
5988 $dir = $sdir;
5989 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5990 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
5991 } else {
5992 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
5993 }
5994
5995 $nbphoto = 0;
5996
5997 $dir_osencoded = dol_osencode($dir);
5998 if (file_exists($dir_osencoded)) {
5999 $handle = opendir($dir_osencoded);
6000 if (is_resource($handle)) {
6001 while (($file = readdir($handle)) !== false) {
6002 if (!utf8_check($file)) {
6003 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
6004 }
6005 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6006 return true;
6007 }
6008 }
6009 }
6010 }
6011 return false;
6012 }
6013
6014 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6022 public function liste_photos($dir, $nbmax = 0)
6023 {
6024 // phpcs:enable
6025 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6026 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6027
6028 $nbphoto = 0;
6029 $tabobj = array();
6030
6031 $dir_osencoded = dol_osencode($dir);
6032 $handle = @opendir($dir_osencoded);
6033 if (is_resource($handle)) {
6034 while (($file = readdir($handle)) !== false) {
6035 if (!utf8_check($file)) {
6036 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // readdir returns ISO
6037 }
6038 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6039 $nbphoto++;
6040
6041 // We forge name of thumb.
6042 $photo = $file;
6043 $photo_vignette = '';
6044 $regs = array();
6045 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
6046 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
6047 }
6048
6049 $dirthumb = $dir.'thumbs/';
6050
6051 // Objet
6052 $obj = array();
6053 $obj['photo'] = $photo;
6054 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
6055 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
6056 } else {
6057 $obj['photo_vignette'] = "";
6058 }
6059
6060 $tabobj[$nbphoto - 1] = $obj;
6061
6062 // Do we have to continue with next photo ?
6063 if ($nbmax && $nbphoto >= $nbmax) {
6064 break;
6065 }
6066 }
6067 }
6068
6069 closedir($handle);
6070 }
6071
6072 return $tabobj;
6073 }
6074
6075 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6082 public function delete_photo($file)
6083 {
6084 // phpcs:enable
6085 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6086 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6087
6088 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
6089 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
6090 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
6091
6092 // On efface l'image d'origine
6093 dol_delete_file($file, 0, 0, 0, $this); // For triggers
6094
6095 // Si elle existe, on efface la vignette
6096 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
6097 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
6098 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6099 dol_delete_file($dirthumb.$photo_vignette);
6100 }
6101
6102 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
6103 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6104 dol_delete_file($dirthumb.$photo_vignette);
6105 }
6106 }
6107 }
6108
6109 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6116 public function get_image_size($file)
6117 {
6118 // phpcs:enable
6119 $file_osencoded = dol_osencode($file);
6120 $infoImg = getimagesize($file_osencoded); // Get information on image
6121 $this->imgWidth = $infoImg[0]; // Largeur de l'image
6122 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
6123 }
6124
6125 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6131 public function load_state_board()
6132 {
6133 // phpcs:enable
6134 global $hookmanager;
6135
6136 $this->nb = array();
6137
6138 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6139 $sql .= " FROM ".$this->db->prefix()."product as p";
6140 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6141 // Add where from hooks
6142 if (is_object($hookmanager)) {
6143 $parameters = array();
6144 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6145 $sql .= $hookmanager->resPrint;
6146 }
6147 $sql .= ' GROUP BY fk_product_type';
6148
6149 $resql = $this->db->query($sql);
6150 if ($resql) {
6151 while ($obj = $this->db->fetch_object($resql)) {
6152 if ($obj->fk_product_type == 1) {
6153 $this->nb["services"] = $obj->nb;
6154 } else {
6155 $this->nb["products"] = $obj->nb;
6156 }
6157 }
6158 $this->db->free($resql);
6159 return 1;
6160 } else {
6161 dol_print_error($this->db);
6162 $this->error = $this->db->error();
6163 return -1;
6164 }
6165 }
6166
6172 public function isProduct()
6173 {
6174 return ($this->type == Product::TYPE_PRODUCT ? true : false);
6175 }
6176
6182 public function isService()
6183 {
6184 return ($this->type == Product::TYPE_SERVICE ? true : false);
6185 }
6186
6192 public function isStockManaged()
6193 {
6194 return ($this->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'));
6195 }
6196
6202 public function isMandatoryPeriod()
6203 {
6204 return ($this->mandatory_period == 1 ? true : false);
6205 }
6206
6212 public function hasbatch()
6213 {
6214 return ($this->status_batch > 0 ? true : false);
6215 }
6216
6217
6218 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6227 public function get_barcode($object, $type = '')
6228 {
6229 // phpcs:enable
6230 global $conf;
6231
6232 $result = '';
6233 if (getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
6234 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6235 foreach ($dirsociete as $dirroot) {
6236 $res = dol_include_once($dirroot . getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM').'.php');
6237 if ($res) {
6238 break;
6239 }
6240 }
6241 $var = $conf->global->BARCODE_PRODUCT_ADDON_NUM;
6242 $mod = new $var();
6243
6244 $result = $mod->getNextValue($object, $type);
6245
6246 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6247 }
6248 return $result;
6249 }
6250
6258 public function initAsSpecimen()
6259 {
6260 $now = dol_now();
6261
6262 // Initialize parameters
6263 $this->specimen = 1;
6264 $this->id = 0;
6265 $this->ref = 'PRODUCT_SPEC';
6266 $this->label = 'PRODUCT SPECIMEN';
6267 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
6268 $this->specimen = 1;
6269 $this->country_id = 1;
6270 $this->status = 1;
6271 $this->status_buy = 1;
6272 $this->tobatch = 0;
6273 $this->note_private = 'This is a comment (private)';
6274 $this->note_public = 'This is a comment (public)';
6275 $this->date_creation = $now;
6276 $this->date_modification = $now;
6277
6278 $this->weight = 4;
6279 $this->weight_units = 3;
6280
6281 $this->length = 5;
6282 $this->length_units = 1;
6283 $this->width = 6;
6284 $this->width_units = 0;
6285 $this->height = null;
6286 $this->height_units = null;
6287
6288 $this->surface = 30;
6289 $this->surface_units = 0;
6290 $this->volume = 300;
6291 $this->volume_units = 0;
6292
6293 $this->barcode = -1; // Create barcode automatically
6294 }
6295
6302 public function getLabelOfUnit($type = 'long')
6303 {
6304 global $langs;
6305
6306 if (!$this->fk_unit) {
6307 return '';
6308 }
6309
6310 $langs->load('products');
6311
6312 $label_type = 'label';
6313 if ($type == 'short') {
6314 $label_type = 'short_label';
6315 }
6316
6317 $sql = "SELECT ".$label_type.", code from ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
6318
6319 $resql = $this->db->query($sql);
6320 if ($resql && $this->db->num_rows($resql) > 0) {
6321 $res = $this->db->fetch_array($resql);
6322 $label = ($label_type == 'short_label' ? $res[$label_type] : 'unit'.$res['code']);
6323 $this->db->free($resql);
6324 return $label;
6325 } else {
6326 $this->error = $this->db->error();
6327 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
6328 return -1;
6329 }
6330 }
6331
6332 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6338 public function min_recommended_price()
6339 {
6340 // phpcs:enable
6341 global $conf;
6342
6343 $maxpricesupplier = 0;
6344
6345 if (getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE')) {
6346 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6347 $product_fourn = new ProductFournisseur($this->db);
6348 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
6349
6350 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
6351 foreach ($product_fourn_list as $productfourn) {
6352 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
6353 $maxpricesupplier = $productfourn->fourn_unitprice;
6354 }
6355 }
6356
6357 $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
6358 }
6359 }
6360
6361 return $maxpricesupplier;
6362 }
6363
6364
6375 public function setCategories($categories)
6376 {
6377 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
6378 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
6379 }
6380
6389 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
6390 {
6391 $tables = array(
6392 'product_customer_price',
6393 'product_customer_price_log'
6394 );
6395
6396 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
6397 }
6398
6410 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
6411 {
6412 global $conf;
6413
6414 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
6415 $query = $this->db->query($sql);
6416
6417 $rules = array();
6418
6419 while ($result = $this->db->fetch_object($query)) {
6420 $rules[$result->level] = $result;
6421 }
6422
6423 //Because prices can be based on other level's prices, we temporarily store them
6424 $prices = array(
6425 1 => $baseprice
6426 );
6427
6428 $nbofproducts = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
6429 for ($i = 1; $i <= $nbofproducts; $i++) {
6430 $price = $baseprice;
6431 $price_min = $baseprice;
6432
6433 //We have to make sure it does exist and it is > 0
6434 //First price level only allows changing min_price
6435 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
6436 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
6437 }
6438
6439 $prices[$i] = $price;
6440
6441 //We have to make sure it does exist and it is > 0
6442 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
6443 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
6444 }
6445
6446 //Little check to make sure the price is modified before triggering generation
6447 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
6448 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
6449
6450 if ($check_amount && $check_type) {
6451 continue;
6452 }
6453
6454 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
6455 return -1;
6456 }
6457 }
6458
6459 return 1;
6460 }
6461
6467 public function getRights()
6468 {
6469 global $user;
6470
6471 if ($this->isProduct()) {
6472 return $user->rights->produit;
6473 } else {
6474 return $user->rights->service;
6475 }
6476 }
6477
6484 public function info($id)
6485 {
6486 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
6487 $sql .= " p.fk_user_author, p.fk_user_modif";
6488 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
6489 $sql .= " WHERE p.rowid = ".((int) $id);
6490
6491 $result = $this->db->query($sql);
6492 if ($result) {
6493 if ($this->db->num_rows($result)) {
6494 $obj = $this->db->fetch_object($result);
6495
6496 $this->id = $obj->rowid;
6497 $this->ref = $obj->ref;
6498
6499 $this->user_creation_id = $obj->fk_user_author;
6500 $this->user_modification_id = $obj->fk_user_modif;
6501
6502 $this->date_creation = $this->db->jdate($obj->date_creation);
6503 $this->date_modification = $this->db->jdate($obj->date_modification);
6504 }
6505
6506 $this->db->free($result);
6507 } else {
6508 dol_print_error($this->db);
6509 }
6510 }
6511
6512
6517 public function getProductDurationHours()
6518 {
6519 global $langs;
6520
6521 if (empty($this->duration_value)) {
6522 $this->errors[]='ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
6523 return -1;
6524 }
6525
6526 if ($this->duration_unit == 'i') {
6527 $prodDurationHours = 1. / 60;
6528 }
6529 if ($this->duration_unit == 'h') {
6530 $prodDurationHours = 1.;
6531 }
6532 if ($this->duration_unit == 'd') {
6533 $prodDurationHours = 24.;
6534 }
6535 if ($this->duration_unit == 'w') {
6536 $prodDurationHours = 24. * 7;
6537 }
6538 if ($this->duration_unit == 'm') {
6539 $prodDurationHours = 24. * 30;
6540 }
6541 if ($this->duration_unit == 'y') {
6542 $prodDurationHours = 24. * 365;
6543 }
6544 $prodDurationHours *= $this->duration_value;
6545
6546 return $prodDurationHours;
6547 }
6548
6549
6557 public function getKanbanView($option = '', $arraydata = null)
6558 {
6559 global $langs,$conf;
6560
6561 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6562
6563 $return = '<div class="box-flex-item box-flex-grow-zero">';
6564 $return .= '<div class="info-box info-box-sm">';
6565 $return .= '<div class="info-box-img">';
6566 $label = '';
6567 if ($this->is_photo_available($conf->product->multidir_output[$this->entity])) {
6568 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
6569 $return .= $label;
6570 } else {
6571 if ($this->type == Product::TYPE_PRODUCT) {
6572 $label .= img_picto('', 'product');
6573 } elseif ($this->type == Product::TYPE_SERVICE) {
6574 $label .= img_picto('', 'service');
6575 }
6576 $return .= $label;
6577 }
6578 $return .= '</div>';
6579 $return .= '<div class="info-box-content">';
6580 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
6581 if ($selected >= 0) {
6582 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
6583 }
6584 if (property_exists($this, 'label')) {
6585 $return .= '<br><span class="info-box-label opacitymedium">'.$this->label.'</span>';
6586 }
6587 if (property_exists($this, 'price') && property_exists($this, 'price_ttc')) {
6588 if ($this->price_base_type == 'TTC') {
6589 $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
6590 } else {
6591 if ($this->status) {
6592 $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
6593 }
6594 }
6595 }
6596 $br = 1;
6597 if (property_exists($this, 'stock_reel') && $this->isProduct()) {
6598 $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>';
6599 $br = 0;
6600 }
6601 if (method_exists($this, 'getLibStatut')) {
6602 if ($br) {
6603 $return .= '<br><div class="info-box-status inline-block valignmiddle">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6604 } else {
6605 $return .= '<div class="info-box-status inline-block valignmiddle marginleftonly paddingleft">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6606 }
6607 }
6608 $return .= '</div>';
6609 $return .= '</div>';
6610 $return .= '</div>';
6611 return $return;
6612 }
6613}
6614
6620{
6621 public $picto = 'service';
6622}
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.
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