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 $produit_multiprices_limit = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
2676 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
2677 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2678 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2679 $sql .= " FROM ".$this->db->prefix()."product_price";
2680 $sql .= " WHERE entity IN (".getEntity('productprice').")";
2681 $sql .= " AND price_level=".((int) $i);
2682 $sql .= " AND fk_product = ".((int) $this->id);
2683 $sql .= " ORDER BY date_price DESC, rowid DESC"; // Get the most recent line
2684 $sql .= " LIMIT 1"; // Only the first one
2685 $resql = $this->db->query($sql);
2686 if ($resql) {
2687 $result = $this->db->fetch_array($resql);
2688
2689 $this->multiprices[$i] = $result ? $result["price"] : null;
2690 $this->multiprices_ttc[$i] = $result ? $result["price_ttc"] : null;
2691 $this->multiprices_min[$i] = $result ? $result["price_min"] : null;
2692 $this->multiprices_min_ttc[$i] = $result ? $result["price_min_ttc"] : null;
2693 $this->multiprices_base_type[$i] = $result ? $result["price_base_type"] : null;
2694 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2695 $this->multiprices_tva_tx[$i] = $result ? $result["tva_tx"].($result ? ' ('.$result['default_vat_code'].')' : '') : null;
2696 $this->multiprices_recuperableonly[$i] = $result ? $result["recuperableonly"] : null;
2697
2698 // Price by quantity
2699 /*
2700 $this->prices_by_qty[$i]=$result["price_by_qty"];
2701 $this->prices_by_qty_id[$i]=$result["rowid"];
2702 // Récuperation de la liste des prix selon qty si flag positionné
2703 if ($this->prices_by_qty[$i] == 1)
2704 {
2705 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2706 $sql.= " FROM ".$this->db->prefix()."product_price_by_qty";
2707 $sql.= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2708 $sql.= " ORDER BY quantity ASC";
2709 $resultat=array();
2710 $resql = $this->db->query($sql);
2711 if ($resql)
2712 {
2713 $ii=0;
2714 while ($result= $this->db->fetch_array($resql)) {
2715 $resultat[$ii]=array();
2716 $resultat[$ii]["rowid"]=$result["rowid"];
2717 $resultat[$ii]["price"]= $result["price"];
2718 $resultat[$ii]["unitprice"]= $result["unitprice"];
2719 $resultat[$ii]["quantity"]= $result["quantity"];
2720 $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2721 $resultat[$ii]["remise"]= $result["remise"]; // deprecated
2722 $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2723 $ii++;
2724 }
2725 $this->prices_by_qty_list[$i]=$resultat;
2726 }
2727 else
2728 {
2729 dol_print_error($this->db);
2730 return -1;
2731 }
2732 }*/
2733 } else {
2734 $this->error = $this->db->lasterror;
2735 return -1;
2736 }
2737 }
2738 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') && empty($ignore_price_load)) { // prices per customers
2739 // Nothing loaded by default. List may be very long.
2740 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') && empty($ignore_price_load)) { // prices per quantity
2741 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2742 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
2743 $sql .= " FROM ".$this->db->prefix()."product_price";
2744 $sql .= " WHERE fk_product = ".((int) $this->id);
2745 $sql .= " ORDER BY date_price DESC, rowid DESC";
2746 $sql .= " LIMIT 1";
2747 $resql = $this->db->query($sql);
2748 if ($resql) {
2749 $result = $this->db->fetch_array($resql);
2750
2751 // Price by quantity
2752 $this->prices_by_qty[0] = $result["price_by_qty"];
2753 $this->prices_by_qty_id[0] = $result["rowid"];
2754 // Récuperation de la liste des prix selon qty si flag positionné
2755 if ($this->prices_by_qty[0] == 1) {
2756 $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
2757 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2758 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[0]);
2759 $sql .= " ORDER BY quantity ASC";
2760 $resultat = array();
2761 $resql = $this->db->query($sql);
2762 if ($resql) {
2763 $ii = 0;
2764 while ($result = $this->db->fetch_array($resql)) {
2765 $resultat[$ii] = array();
2766 $resultat[$ii]["rowid"] = $result["rowid"];
2767 $resultat[$ii]["price"] = $result["price"];
2768 $resultat[$ii]["unitprice"] = $result["unitprice"];
2769 $resultat[$ii]["quantity"] = $result["quantity"];
2770 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2771 //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
2772 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2773 $ii++;
2774 }
2775 $this->prices_by_qty_list[0] = $resultat;
2776 } else {
2777 $this->error = $this->db->lasterror;
2778 return -1;
2779 }
2780 }
2781 } else {
2782 $this->error = $this->db->lasterror;
2783 return -1;
2784 }
2785 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') && empty($ignore_price_load)) { // prices per customer and quantity
2786 $produit_multiprices_limit = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
2787 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
2788 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2789 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2790 $sql .= " FROM ".$this->db->prefix()."product_price";
2791 $sql .= " WHERE entity IN (".getEntity('productprice').")";
2792 $sql .= " AND price_level=".((int) $i);
2793 $sql .= " AND fk_product = ".((int) $this->id);
2794 $sql .= " ORDER BY date_price DESC, rowid DESC";
2795 $sql .= " LIMIT 1";
2796 $resql = $this->db->query($sql);
2797 if ($resql) {
2798 $result = $this->db->fetch_array($resql);
2799
2800 $this->multiprices[$i] = (!empty($result["price"]) ? $result["price"] : 0);
2801 $this->multiprices_ttc[$i] = (!empty($result["price_ttc"]) ? $result["price_ttc"] : 0);
2802 $this->multiprices_min[$i] = (!empty($result["price_min"]) ? $result["price_min"] : 0);
2803 $this->multiprices_min_ttc[$i] = (!empty($result["price_min_ttc"]) ? $result["price_min_ttc"] : 0);
2804 $this->multiprices_base_type[$i] = (!empty($result["price_base_type"]) ? $result["price_base_type"] : '');
2805 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2806 $this->multiprices_tva_tx[$i] = (!empty($result["tva_tx"]) ? $result["tva_tx"] : 0); // TODO Add ' ('.$result['default_vat_code'].')'
2807 $this->multiprices_recuperableonly[$i] = (!empty($result["recuperableonly"]) ? $result["recuperableonly"] : 0);
2808
2809 // Price by quantity
2810 $this->prices_by_qty[$i] = (!empty($result["price_by_qty"]) ? $result["price_by_qty"] : 0);
2811 $this->prices_by_qty_id[$i] = (!empty($result["rowid"]) ? $result["rowid"] : 0);
2812 // Récuperation de la liste des prix selon qty si flag positionné
2813 if ($this->prices_by_qty[$i] == 1) {
2814 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2815 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2816 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2817 $sql .= " ORDER BY quantity ASC";
2818 $resultat = array();
2819 $resql = $this->db->query($sql);
2820 if ($resql) {
2821 $ii = 0;
2822 while ($result = $this->db->fetch_array($resql)) {
2823 $resultat[$ii] = array();
2824 $resultat[$ii]["rowid"] = $result["rowid"];
2825 $resultat[$ii]["price"] = $result["price"];
2826 $resultat[$ii]["unitprice"] = $result["unitprice"];
2827 $resultat[$ii]["quantity"] = $result["quantity"];
2828 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2829 $resultat[$ii]["remise"] = $result["remise"]; // deprecated
2830 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2831 $ii++;
2832 }
2833 $this->prices_by_qty_list[$i] = $resultat;
2834 } else {
2835 $this->error = $this->db->lasterror;
2836 return -1;
2837 }
2838 }
2839 } else {
2840 $this->error = $this->db->lasterror;
2841 return -1;
2842 }
2843 }
2844 }
2845
2846 if (isModEnabled('dynamicprices') && !empty($this->fk_price_expression) && empty($ignore_expression)) {
2847 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2848 $priceparser = new PriceParser($this->db);
2849 $price_result = $priceparser->parseProduct($this);
2850 if ($price_result >= 0) {
2851 $this->price = $price_result;
2852 // Calculate the VAT
2853 $this->price_ttc = price2num($this->price) * (1 + ($this->tva_tx / 100));
2854 $this->price_ttc = price2num($this->price_ttc, 'MU');
2855 }
2856 }
2857
2858 // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
2859 // Instead we just init the stock_warehouse array
2860 $this->stock_warehouse = array();
2861
2862 return 1;
2863 } else {
2864 return 0;
2865 }
2866 } else {
2867 $this->error = $this->db->lasterror();
2868 return -1;
2869 }
2870 }
2871
2872 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2879 public function load_stats_mo($socid = 0)
2880 {
2881 // phpcs:enable
2882 global $user, $hookmanager, $action;
2883
2884 $error = 0;
2885
2886 foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
2887 $this->stats_mo['customers_'.$role] = 0;
2888 $this->stats_mo['nb_'.$role] = 0;
2889 $this->stats_mo['qty_'.$role] = 0;
2890
2891 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2892 $sql .= " SUM(mp.qty) as qty";
2893 $sql .= " FROM ".$this->db->prefix()."mrp_mo as c";
2894 $sql .= " INNER JOIN ".$this->db->prefix()."mrp_production as mp ON mp.fk_mo=c.rowid";
2895 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
2896 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc=c.fk_soc AND sc.fk_user = ".((int) $user->id);
2897 }
2898 $sql .= " WHERE ";
2899 $sql .= " c.entity IN (".getEntity('mo').")";
2900
2901 $sql .= " AND mp.fk_product = ".((int) $this->id);
2902 $sql .= " AND mp.role ='".$this->db->escape($role)."'";
2903 if ($socid > 0) {
2904 $sql .= " AND c.fk_soc = ".((int) $socid);
2905 }
2906
2907 $result = $this->db->query($sql);
2908 if ($result) {
2909 $obj = $this->db->fetch_object($result);
2910 $this->stats_mo['customers_'.$role] = $obj->nb_customers ? $obj->nb_customers : 0;
2911 $this->stats_mo['nb_'.$role] = $obj->nb ? $obj->nb : 0;
2912 $this->stats_mo['qty_'.$role] = $obj->qty ? price2num($obj->qty, 'MS') : 0; // qty may be a float due to the SUM()
2913 } else {
2914 $this->error = $this->db->error();
2915 $error++;
2916 }
2917 }
2918
2919 if (!empty($error)) {
2920 return -1;
2921 }
2922
2923 $parameters = array('socid' => $socid);
2924 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
2925 if ($reshook > 0) {
2926 $this->stats_mo = $hookmanager->resArray['stats_mo'];
2927 }
2928
2929 return 1;
2930 }
2931
2932 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2939 public function load_stats_bom($socid = 0)
2940 {
2941 // phpcs:enable
2942 global $user, $hookmanager, $action;
2943
2944 $error = 0;
2945
2946 $this->stats_bom['nb_toproduce'] = 0;
2947 $this->stats_bom['nb_toconsume'] = 0;
2948 $this->stats_bom['qty_toproduce'] = 0;
2949 $this->stats_bom['qty_toconsume'] = 0;
2950
2951 $sql = "SELECT COUNT(DISTINCT b.rowid) as nb_toproduce,";
2952 $sql .= " SUM(b.qty) as qty_toproduce";
2953 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
2954 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
2955 $sql .= " WHERE ";
2956 $sql .= " b.entity IN (".getEntity('bom').")";
2957 $sql .= " AND b.fk_product =".((int) $this->id);
2958 $sql .= " GROUP BY b.rowid";
2959
2960 $result = $this->db->query($sql);
2961 if ($result) {
2962 $obj = $this->db->fetch_object($result);
2963 $this->stats_bom['nb_toproduce'] = !empty($obj->nb_toproduce) ? $obj->nb_toproduce : 0;
2964 $this->stats_bom['qty_toproduce'] = !empty($obj->qty_toproduce) ? price2num($obj->qty_toproduce) : 0;
2965 } else {
2966 $this->error = $this->db->error();
2967 $error++;
2968 }
2969
2970 $sql = "SELECT COUNT(DISTINCT bl.rowid) as nb_toconsume,";
2971 $sql .= " SUM(bl.qty) as qty_toconsume";
2972 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
2973 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
2974 $sql .= " WHERE ";
2975 $sql .= " b.entity IN (".getEntity('bom').")";
2976 $sql .= " AND bl.fk_product =".((int) $this->id);
2977
2978 $result = $this->db->query($sql);
2979 if ($result) {
2980 $obj = $this->db->fetch_object($result);
2981 $this->stats_bom['nb_toconsume'] = !empty($obj->nb_toconsume) ? $obj->nb_toconsume : 0;
2982 $this->stats_bom['qty_toconsume'] = !empty($obj->qty_toconsume) ? price2num($obj->qty_toconsume) : 0;
2983 } else {
2984 $this->error = $this->db->error();
2985 $error++;
2986 }
2987
2988 if (!empty($error)) {
2989 return -1;
2990 }
2991
2992 $parameters = array('socid' => $socid);
2993 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
2994 if ($reshook > 0) {
2995 $this->stats_bom = $hookmanager->resArray['stats_bom'];
2996 }
2997
2998 return 1;
2999 }
3000
3001 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3008 public function load_stats_propale($socid = 0)
3009 {
3010 // phpcs:enable
3011 global $conf, $user, $hookmanager, $action;
3012
3013 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
3014 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3015 $sql .= " FROM ".$this->db->prefix()."propaldet as pd";
3016 $sql .= ", ".$this->db->prefix()."propal as p";
3017 $sql .= ", ".$this->db->prefix()."societe as s";
3018 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3019 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3020 }
3021 $sql .= " WHERE p.rowid = pd.fk_propal";
3022 $sql .= " AND p.fk_soc = s.rowid";
3023 $sql .= " AND p.entity IN (".getEntity('propal').")";
3024 $sql .= " AND pd.fk_product = ".((int) $this->id);
3025 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3026 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3027 }
3028 //$sql.= " AND pr.fk_statut != 0";
3029 if ($socid > 0) {
3030 $sql .= " AND p.fk_soc = ".((int) $socid);
3031 }
3032
3033 $result = $this->db->query($sql);
3034 if ($result) {
3035 $obj = $this->db->fetch_object($result);
3036 $this->stats_propale['customers'] = $obj->nb_customers;
3037 $this->stats_propale['nb'] = $obj->nb;
3038 $this->stats_propale['rows'] = $obj->nb_rows;
3039 $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
3040
3041 // if it's a virtual product, maybe it is in proposal by extension
3042 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3043 $TFather = $this->getFather();
3044 if (is_array($TFather) && !empty($TFather)) {
3045 foreach ($TFather as &$fatherData) {
3046 $pFather = new Product($this->db);
3047 $pFather->id = $fatherData['id'];
3048 $qtyCoef = $fatherData['qty'];
3049
3050 if ($fatherData['incdec']) {
3051 $pFather->load_stats_propale($socid);
3052
3053 $this->stats_propale['customers'] += $pFather->stats_propale['customers'];
3054 $this->stats_propale['nb'] += $pFather->stats_propale['nb'];
3055 $this->stats_propale['rows'] += $pFather->stats_propale['rows'];
3056 $this->stats_propale['qty'] += $pFather->stats_propale['qty'] * $qtyCoef;
3057 }
3058 }
3059 }
3060 }
3061
3062 $parameters = array('socid' => $socid);
3063 $reshook = $hookmanager->executeHooks('loadStatsCustomerProposal', $parameters, $this, $action);
3064 if ($reshook > 0) {
3065 $this->stats_propale = $hookmanager->resArray['stats_propale'];
3066 }
3067
3068 return 1;
3069 } else {
3070 $this->error = $this->db->error();
3071 return -1;
3072 }
3073 }
3074
3075
3076 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3083 public function load_stats_proposal_supplier($socid = 0)
3084 {
3085 // phpcs:enable
3086 global $conf, $user, $hookmanager, $action;
3087
3088 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
3089 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3090 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as pd";
3091 $sql .= ", ".$this->db->prefix()."supplier_proposal as p";
3092 $sql .= ", ".$this->db->prefix()."societe as s";
3093 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3094 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3095 }
3096 $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
3097 $sql .= " AND p.fk_soc = s.rowid";
3098 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
3099 $sql .= " AND pd.fk_product = ".((int) $this->id);
3100 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3101 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3102 }
3103 //$sql.= " AND pr.fk_statut != 0";
3104 if ($socid > 0) {
3105 $sql .= " AND p.fk_soc = ".((int) $socid);
3106 }
3107
3108 $result = $this->db->query($sql);
3109 if ($result) {
3110 $obj = $this->db->fetch_object($result);
3111 $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
3112 $this->stats_proposal_supplier['nb'] = $obj->nb;
3113 $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
3114 $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
3115
3116 $parameters = array('socid' => $socid);
3117 $reshook = $hookmanager->executeHooks('loadStatsSupplierProposal', $parameters, $this, $action);
3118 if ($reshook > 0) {
3119 $this->stats_proposal_supplier = $hookmanager->resArray['stats_proposal_supplier'];
3120 }
3121
3122 return 1;
3123 } else {
3124 $this->error = $this->db->error();
3125 return -1;
3126 }
3127 }
3128
3129
3130 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3139 public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
3140 {
3141 // phpcs:enable
3142 global $conf, $user, $hookmanager, $action;
3143
3144 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3145 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3146 $sql .= " FROM ".$this->db->prefix()."commandedet as cd";
3147 $sql .= ", ".$this->db->prefix()."commande as c";
3148 $sql .= ", ".$this->db->prefix()."societe as s";
3149 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3150 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3151 }
3152 $sql .= " WHERE c.rowid = cd.fk_commande";
3153 $sql .= " AND c.fk_soc = s.rowid";
3154 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'commande').")";
3155 $sql .= " AND cd.fk_product = ".((int) $this->id);
3156 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3157 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3158 }
3159 if ($socid > 0) {
3160 $sql .= " AND c.fk_soc = ".((int) $socid);
3161 }
3162 if ($filtrestatut != '') {
3163 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3164 }
3165
3166 $result = $this->db->query($sql);
3167 if ($result) {
3168 $obj = $this->db->fetch_object($result);
3169 $this->stats_commande['customers'] = $obj->nb_customers;
3170 $this->stats_commande['nb'] = $obj->nb;
3171 $this->stats_commande['rows'] = $obj->nb_rows;
3172 $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
3173
3174 // if it's a virtual product, maybe it is in order by extension
3175 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3176 $TFather = $this->getFather();
3177 if (is_array($TFather) && !empty($TFather)) {
3178 foreach ($TFather as &$fatherData) {
3179 $pFather = new Product($this->db);
3180 $pFather->id = $fatherData['id'];
3181 $qtyCoef = $fatherData['qty'];
3182
3183 if ($fatherData['incdec']) {
3184 $pFather->load_stats_commande($socid, $filtrestatut);
3185
3186 $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
3187 $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
3188 $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
3189 $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
3190 }
3191 }
3192 }
3193 }
3194
3195 // If stock decrease is on invoice validation, the theorical stock continue to
3196 // count the orders lines in theoretical stock when some are already removed by invoice validation.
3197 if ($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
3198 if (getDolGlobalString('DECREASE_ONLY_UNINVOICEDPRODUCTS')) {
3199 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation but only if order not yet invoice.
3200 $adeduire = 0;
3201 $sql = "SELECT SUM(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3202 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3203 $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'))";
3204 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_source = c.rowid";
3205 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND c.facture = 0 AND fd.fk_product = ".((int) $this->id);
3206
3207 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3208 $resql = $this->db->query($sql);
3209 if ($resql) {
3210 if ($this->db->num_rows($resql) > 0) {
3211 $obj = $this->db->fetch_object($resql);
3212 $adeduire += $obj->count;
3213 }
3214 }
3215
3216 $this->stats_commande['qty'] -= $adeduire;
3217 } else {
3218 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is off, we make a compensation with lines of invoices linked to the order
3219 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3220
3221 // For every order having invoice already validated we need to decrease stock cause it's in physical stock
3222 $adeduire = 0;
3223 $sql = "SELECT sum(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet as fd ";
3224 $sql .= " JOIN ".MAIN_DB_PREFIX."facture as f ON fd.fk_facture = f.rowid";
3225 $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'))";
3226 $sql .= " JOIN ".MAIN_DB_PREFIX."commande as c ON el.fk_source = c.rowid";
3227 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product = ".((int) $this->id);
3228
3229 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3230 $resql = $this->db->query($sql);
3231 if ($resql) {
3232 if ($this->db->num_rows($resql) > 0) {
3233 $obj = $this->db->fetch_object($resql);
3234 $adeduire += $obj->count;
3235 }
3236 } else {
3237 $this->error = $this->db->error();
3238 return -1;
3239 }
3240
3241 $this->stats_commande['qty'] -= $adeduire;
3242 }
3243 }
3244
3245 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3246 $reshook = $hookmanager->executeHooks('loadStatsCustomerOrder', $parameters, $this, $action);
3247 if ($reshook > 0) {
3248 $this->stats_commande = $hookmanager->resArray['stats_commande'];
3249 }
3250 return 1;
3251 } else {
3252 $this->error = $this->db->error();
3253 return -1;
3254 }
3255 }
3256
3257 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3267 public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3268 {
3269 // phpcs:enable
3270 global $conf, $user, $hookmanager, $action;
3271
3272 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
3273 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3274 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as cd";
3275 $sql .= ", ".$this->db->prefix()."commande_fournisseur as c";
3276 $sql .= ", ".$this->db->prefix()."societe as s";
3277 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3278 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3279 }
3280 $sql .= " WHERE c.rowid = cd.fk_commande";
3281 $sql .= " AND c.fk_soc = s.rowid";
3282 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3283 $sql .= " AND cd.fk_product = ".((int) $this->id);
3284 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3285 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3286 }
3287 if ($socid > 0) {
3288 $sql .= " AND c.fk_soc = ".((int) $socid);
3289 }
3290 if ($filtrestatut != '') {
3291 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
3292 }
3293 if (!empty($dateofvirtualstock)) {
3294 $sql .= " AND c.date_livraison <= '".$this->db->idate($dateofvirtualstock)."'";
3295 }
3296
3297 $result = $this->db->query($sql);
3298 if ($result) {
3299 $obj = $this->db->fetch_object($result);
3300 $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
3301 $this->stats_commande_fournisseur['nb'] = $obj->nb;
3302 $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
3303 $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3304
3305 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3306 $reshook = $hookmanager->executeHooks('loadStatsSupplierOrder', $parameters, $this, $action);
3307 if ($reshook > 0) {
3308 $this->stats_commande_fournisseur = $hookmanager->resArray['stats_commande_fournisseur'];
3309 }
3310
3311 return 1;
3312 } else {
3313 $this->error = $this->db->error().' sql='.$sql;
3314 return -1;
3315 }
3316 }
3317
3318 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3328 public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $filterShipmentStatus = '')
3329 {
3330 // phpcs:enable
3331 global $conf, $user, $hookmanager, $action;
3332
3333 $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
3334 $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
3335 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
3336 $sql .= ", ".$this->db->prefix()."commandedet as cd";
3337 $sql .= ", ".$this->db->prefix()."commande as c";
3338 $sql .= ", ".$this->db->prefix()."expedition as e";
3339 $sql .= ", ".$this->db->prefix()."societe as s";
3340 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3341 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3342 }
3343 $sql .= " WHERE e.rowid = ed.fk_expedition";
3344 $sql .= " AND c.rowid = cd.fk_commande";
3345 $sql .= " AND e.fk_soc = s.rowid";
3346 $sql .= " AND e.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'expedition').")";
3347 $sql .= " AND ed.fk_origin_line = cd.rowid";
3348 $sql .= " AND cd.fk_product = ".((int) $this->id);
3349 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3350 $sql .= " AND e.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3351 }
3352 if ($socid > 0) {
3353 $sql .= " AND e.fk_soc = ".((int) $socid);
3354 }
3355 if ($filtrestatut != '') {
3356 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3357 }
3358 if (!empty($filterShipmentStatus)) {
3359 $sql .= " AND e.fk_statut IN (".$this->db->sanitize($filterShipmentStatus).")";
3360 }
3361
3362 $result = $this->db->query($sql);
3363 if ($result) {
3364 $obj = $this->db->fetch_object($result);
3365 $this->stats_expedition['customers'] = $obj->nb_customers;
3366 $this->stats_expedition['nb'] = $obj->nb;
3367 $this->stats_expedition['rows'] = $obj->nb_rows;
3368 $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
3369
3370 // if it's a virtual product, maybe it is in sending by extension
3371 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3372 $TFather = $this->getFather();
3373 if (is_array($TFather) && !empty($TFather)) {
3374 foreach ($TFather as &$fatherData) {
3375 $pFather = new Product($this->db);
3376 $pFather->id = $fatherData['id'];
3377 $qtyCoef = $fatherData['qty'];
3378
3379 if ($fatherData['incdec']) {
3380 $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
3381
3382 $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
3383 $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
3384 $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
3385 $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
3386 }
3387 }
3388 }
3389 }
3390
3391 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock, 'filterShipmentStatus' => $filterShipmentStatus);
3392 $reshook = $hookmanager->executeHooks('loadStatsSending', $parameters, $this, $action);
3393 if ($reshook > 0) {
3394 $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
3395 }
3396
3397 return 1;
3398 } else {
3399 $this->error = $this->db->error();
3400 return -1;
3401 }
3402 }
3403
3404 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3414 public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3415 {
3416 // phpcs:enable
3417 global $conf, $user, $hookmanager, $action;
3418
3419 $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_suppliers, COUNT(DISTINCT cf.rowid) as nb,";
3420 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3421 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur_dispatch as fd";
3422 $sql .= ", ".$this->db->prefix()."commande_fournisseur as cf";
3423 $sql .= ", ".$this->db->prefix()."societe as s";
3424 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3425 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3426 }
3427 $sql .= " WHERE cf.rowid = fd.fk_commande";
3428 $sql .= " AND cf.fk_soc = s.rowid";
3429 $sql .= " AND cf.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3430 $sql .= " AND fd.fk_product = ".((int) $this->id);
3431 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3432 $sql .= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3433 }
3434 if ($socid > 0) {
3435 $sql .= " AND cf.fk_soc = ".((int) $socid);
3436 }
3437 if ($filtrestatut != '') {
3438 $sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3439 }
3440 if (!empty($dateofvirtualstock)) {
3441 $sql .= " AND fd.datec <= '".$this->db->idate($dateofvirtualstock)."'";
3442 }
3443
3444 $result = $this->db->query($sql);
3445 if ($result) {
3446 $obj = $this->db->fetch_object($result);
3447 $this->stats_reception['suppliers'] = $obj->nb_suppliers;
3448 $this->stats_reception['nb'] = $obj->nb;
3449 $this->stats_reception['rows'] = $obj->nb_rows;
3450 $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
3451
3452 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3453 $reshook = $hookmanager->executeHooks('loadStatsReception', $parameters, $this, $action);
3454 if ($reshook > 0) {
3455 $this->stats_reception = $hookmanager->resArray['stats_reception'];
3456 }
3457
3458 return 1;
3459 } else {
3460 $this->error = $this->db->error();
3461 return -1;
3462 }
3463 }
3464
3465 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3475 public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3476 {
3477 // phpcs:enable
3478 global $conf, $user, $hookmanager, $action;
3479
3480 $serviceStockIsEnabled = isModEnabled("service") && getDolGlobalString('STOCK_SUPPORTS_SERVICES');
3481
3482 $sql = "SELECT COUNT(DISTINCT m.fk_soc) as nb_customers, COUNT(DISTINCT m.rowid) as nb,";
3483 $sql .= " COUNT(mp.rowid) as nb_rows, SUM(mp.qty) as qty, role";
3484 $sql .= " FROM ".$this->db->prefix()."mrp_production as mp";
3485 $sql .= ", ".$this->db->prefix()."mrp_mo as m";
3486 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON s.rowid = m.fk_soc";
3487 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3488 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3489 }
3490 $sql .= " WHERE m.rowid = mp.fk_mo";
3491 $sql .= " AND m.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'mrp').")";
3492 $sql .= " AND mp.fk_product = ".((int) $this->id);
3493 $sql .= " AND (mp.disable_stock_change IN (0) OR mp.disable_stock_change IS NULL)";
3494 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3495 $sql .= " AND m.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3496 }
3497 if ($socid > 0) {
3498 $sql .= " AND m.fk_soc = ".((int) $socid);
3499 }
3500 if ($filtrestatut != '') {
3501 $sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
3502 }
3503 if (!empty($dateofvirtualstock)) {
3504 $sql .= " AND m.date_valid <= '".$this->db->idate($dateofvirtualstock)."'"; // better date to code ? end of production ?
3505 }
3506 if (!$serviceStockIsEnabled) {
3507 $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))";
3508 }
3509 $sql .= " GROUP BY role";
3510
3511 $this->stats_mrptoconsume['customers'] = 0;
3512 $this->stats_mrptoconsume['nb'] = 0;
3513 $this->stats_mrptoconsume['rows'] = 0;
3514 $this->stats_mrptoconsume['qty'] = 0;
3515 $this->stats_mrptoproduce['customers'] = 0;
3516 $this->stats_mrptoproduce['nb'] = 0;
3517 $this->stats_mrptoproduce['rows'] = 0;
3518 $this->stats_mrptoproduce['qty'] = 0;
3519
3520 $result = $this->db->query($sql);
3521 if ($result) {
3522 while ($obj = $this->db->fetch_object($result)) {
3523 if ($obj->role == 'toconsume') {
3524 $this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3525 $this->stats_mrptoconsume['nb'] += $obj->nb;
3526 $this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3527 $this->stats_mrptoconsume['qty'] += ($obj->qty ? $obj->qty : 0);
3528 }
3529 if ($obj->role == 'consumed') {
3530 //$this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3531 //$this->stats_mrptoconsume['nb'] += $obj->nb;
3532 //$this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3533 $this->stats_mrptoconsume['qty'] -= ($obj->qty ? $obj->qty : 0);
3534 }
3535 if ($obj->role == 'toproduce') {
3536 $this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3537 $this->stats_mrptoproduce['nb'] += $obj->nb;
3538 $this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3539 $this->stats_mrptoproduce['qty'] += ($obj->qty ? $obj->qty : 0);
3540 }
3541 if ($obj->role == 'produced') {
3542 //$this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3543 //$this->stats_mrptoproduce['nb'] += $obj->nb;
3544 //$this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3545 $this->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3546 }
3547 }
3548
3549 // Clean data
3550 if ($this->stats_mrptoconsume['qty'] < 0) {
3551 $this->stats_mrptoconsume['qty'] = 0;
3552 }
3553 if ($this->stats_mrptoproduce['qty'] < 0) {
3554 $this->stats_mrptoproduce['qty'] = 0;
3555 }
3556
3557 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3558 $reshook = $hookmanager->executeHooks('loadStatsInProduction', $parameters, $this, $action);
3559 if ($reshook > 0) {
3560 $this->stats_mrptoproduce = $hookmanager->resArray['stats_mrptoproduce'];
3561 }
3562
3563 return 1;
3564 } else {
3565 $this->error = $this->db->error();
3566 return -1;
3567 }
3568 }
3569
3570 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3577 public function load_stats_contrat($socid = 0)
3578 {
3579 // phpcs:enable
3580 global $conf, $user, $hookmanager, $action;
3581
3582 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3583 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3584 $sql .= " FROM ".$this->db->prefix()."contratdet as cd";
3585 $sql .= ", ".$this->db->prefix()."contrat as c";
3586 $sql .= ", ".$this->db->prefix()."societe as s";
3587 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3588 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3589 }
3590 $sql .= " WHERE c.rowid = cd.fk_contrat";
3591 $sql .= " AND c.fk_soc = s.rowid";
3592 $sql .= " AND c.entity IN (".getEntity('contract').")";
3593 $sql .= " AND cd.fk_product = ".((int) $this->id);
3594 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3595 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3596 }
3597 //$sql.= " AND c.statut != 0";
3598 if ($socid > 0) {
3599 $sql .= " AND c.fk_soc = ".((int) $socid);
3600 }
3601
3602 $result = $this->db->query($sql);
3603 if ($result) {
3604 $obj = $this->db->fetch_object($result);
3605 $this->stats_contrat['customers'] = $obj->nb_customers;
3606 $this->stats_contrat['nb'] = $obj->nb;
3607 $this->stats_contrat['rows'] = $obj->nb_rows;
3608 $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
3609
3610 // if it's a virtual product, maybe it is in contract by extension
3611 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3612 $TFather = $this->getFather();
3613 if (is_array($TFather) && !empty($TFather)) {
3614 foreach ($TFather as &$fatherData) {
3615 $pFather = new Product($this->db);
3616 $pFather->id = $fatherData['id'];
3617 $qtyCoef = $fatherData['qty'];
3618
3619 if ($fatherData['incdec']) {
3620 $pFather->load_stats_contrat($socid);
3621
3622 $this->stats_contrat['customers'] += $pFather->stats_contrat['customers'];
3623 $this->stats_contrat['nb'] += $pFather->stats_contrat['nb'];
3624 $this->stats_contrat['rows'] += $pFather->stats_contrat['rows'];
3625 $this->stats_contrat['qty'] += $pFather->stats_contrat['qty'] * $qtyCoef;
3626 }
3627 }
3628 }
3629 }
3630
3631 $parameters = array('socid' => $socid);
3632 $reshook = $hookmanager->executeHooks('loadStatsContract', $parameters, $this, $action);
3633 if ($reshook > 0) {
3634 $this->stats_contrat = $hookmanager->resArray['stats_contrat'];
3635 }
3636
3637 return 1;
3638 } else {
3639 $this->error = $this->db->error().' sql='.$sql;
3640 return -1;
3641 }
3642 }
3643
3644 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3651 public function load_stats_facture($socid = 0)
3652 {
3653 // phpcs:enable
3654 global $conf, $user, $hookmanager, $action;
3655
3656 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3657 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(".$this->db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
3658 $sql .= " FROM ".$this->db->prefix()."facturedet as fd";
3659 $sql .= ", ".$this->db->prefix()."facture as f";
3660 $sql .= ", ".$this->db->prefix()."societe as s";
3661 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3662 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3663 }
3664 $sql .= " WHERE f.rowid = fd.fk_facture";
3665 $sql .= " AND f.fk_soc = s.rowid";
3666 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3667 $sql .= " AND fd.fk_product = ".((int) $this->id);
3668 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3669 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3670 }
3671 //$sql.= " AND f.fk_statut != 0";
3672 if ($socid > 0) {
3673 $sql .= " AND f.fk_soc = ".((int) $socid);
3674 }
3675
3676 $result = $this->db->query($sql);
3677 if ($result) {
3678 $obj = $this->db->fetch_object($result);
3679 $this->stats_facture['customers'] = $obj->nb_customers;
3680 $this->stats_facture['nb'] = $obj->nb;
3681 $this->stats_facture['rows'] = $obj->nb_rows;
3682 $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
3683
3684 // if it's a virtual product, maybe it is in invoice by extension
3685 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3686 $TFather = $this->getFather();
3687 if (is_array($TFather) && !empty($TFather)) {
3688 foreach ($TFather as &$fatherData) {
3689 $pFather = new Product($this->db);
3690 $pFather->id = $fatherData['id'];
3691 $qtyCoef = $fatherData['qty'];
3692
3693 if ($fatherData['incdec']) {
3694 $pFather->load_stats_facture($socid);
3695
3696 $this->stats_facture['customers'] += $pFather->stats_facture['customers'];
3697 $this->stats_facture['nb'] += $pFather->stats_facture['nb'];
3698 $this->stats_facture['rows'] += $pFather->stats_facture['rows'];
3699 $this->stats_facture['qty'] += $pFather->stats_facture['qty'] * $qtyCoef;
3700 }
3701 }
3702 }
3703 }
3704
3705 $parameters = array('socid' => $socid);
3706 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoice', $parameters, $this, $action);
3707 if ($reshook > 0) {
3708 $this->stats_facture = $hookmanager->resArray['stats_facture'];
3709 }
3710
3711 return 1;
3712 } else {
3713 $this->error = $this->db->error();
3714 return -1;
3715 }
3716 }
3717
3718
3719 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3726 public function load_stats_facturerec($socid = 0)
3727 {
3728 // phpcs:enable
3729 global $conf, $user, $hookmanager, $action;
3730
3731 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3732 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3733 $sql .= " FROM ".MAIN_DB_PREFIX."facturedet_rec as fd";
3734 $sql .= ", ".MAIN_DB_PREFIX."facture_rec as f";
3735 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
3736 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3737 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3738 }
3739 $sql .= " WHERE f.rowid = fd.fk_facture";
3740 $sql .= " AND f.fk_soc = s.rowid";
3741 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3742 $sql .= " AND fd.fk_product = ".((int) $this->id);
3743 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3744 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3745 }
3746 //$sql.= " AND f.fk_statut != 0";
3747 if ($socid > 0) {
3748 $sql .= " AND f.fk_soc = ".((int) $socid);
3749 }
3750
3751 $result = $this->db->query($sql);
3752 if ($result) {
3753 $obj = $this->db->fetch_object($result);
3754 $this->stats_facturerec['customers'] = $obj->nb_customers;
3755 $this->stats_facturerec['nb'] = $obj->nb;
3756 $this->stats_facturerec['rows'] = $obj->nb_rows;
3757 $this->stats_facturerec['qty'] = $obj->qty ? $obj->qty : 0;
3758
3759 // if it's a virtual product, maybe it is in invoice by extension
3760 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3761 $TFather = $this->getFather();
3762 if (is_array($TFather) && !empty($TFather)) {
3763 foreach ($TFather as &$fatherData) {
3764 $pFather = new Product($this->db);
3765 $pFather->id = $fatherData['id'];
3766 $qtyCoef = $fatherData['qty'];
3767
3768 if ($fatherData['incdec']) {
3769 $pFather->load_stats_facture($socid);
3770
3771 $this->stats_facturerec['customers'] += $pFather->stats_facturerec['customers'];
3772 $this->stats_facturerec['nb'] += $pFather->stats_facturerec['nb'];
3773 $this->stats_facturerec['rows'] += $pFather->stats_facturerec['rows'];
3774 $this->stats_facturerec['qty'] += $pFather->stats_facturerec['qty'] * $qtyCoef;
3775 }
3776 }
3777 }
3778 }
3779
3780 $parameters = array('socid' => $socid);
3781 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoiceRec', $parameters, $this, $action);
3782 if ($reshook > 0) {
3783 $this->stats_facturerec = $hookmanager->resArray['stats_facturerec'];
3784 }
3785
3786 return 1;
3787 } else {
3788 $this->error = $this->db->error();
3789 return -1;
3790 }
3791 }
3792
3793 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3800 public function load_stats_facture_fournisseur($socid = 0)
3801 {
3802 // phpcs:enable
3803 global $conf, $user, $hookmanager, $action;
3804
3805 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
3806 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3807 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as fd";
3808 $sql .= ", ".$this->db->prefix()."facture_fourn as f";
3809 $sql .= ", ".$this->db->prefix()."societe as s";
3810 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3811 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3812 }
3813 $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
3814 $sql .= " AND f.fk_soc = s.rowid";
3815 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
3816 $sql .= " AND fd.fk_product = ".((int) $this->id);
3817 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3818 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3819 }
3820 //$sql.= " AND f.fk_statut != 0";
3821 if ($socid > 0) {
3822 $sql .= " AND f.fk_soc = ".((int) $socid);
3823 }
3824
3825 $result = $this->db->query($sql);
3826 if ($result) {
3827 $obj = $this->db->fetch_object($result);
3828 $this->stats_facture_fournisseur['suppliers'] = $obj->nb_suppliers;
3829 $this->stats_facture_fournisseur['nb'] = $obj->nb;
3830 $this->stats_facture_fournisseur['rows'] = $obj->nb_rows;
3831 $this->stats_facture_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3832
3833 $parameters = array('socid' => $socid);
3834 $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoice', $parameters, $this, $action);
3835 if ($reshook > 0) {
3836 $this->stats_facture_fournisseur = $hookmanager->resArray['stats_facture_fournisseur'];
3837 }
3838
3839 return 1;
3840 } else {
3841 $this->error = $this->db->error();
3842 return -1;
3843 }
3844 }
3845
3846 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3855 private function _get_stats($sql, $mode, $year = 0)
3856 {
3857 // phpcs:enable
3858 $tab = array();
3859
3860 $resql = $this->db->query($sql);
3861 if ($resql) {
3862 $num = $this->db->num_rows($resql);
3863 $i = 0;
3864 while ($i < $num) {
3865 $arr = $this->db->fetch_array($resql);
3866 $keyfortab = (string) $arr[1];
3867 if ($year == -1) {
3868 $keyfortab = substr($keyfortab, -2);
3869 }
3870
3871 if ($mode == 'byunit') {
3872 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[0]; // 1st field
3873 } elseif ($mode == 'bynumber') {
3874 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3875 } elseif ($mode == 'byamount') {
3876 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3877 } else {
3878 // Bad value for $mode
3879 return -1;
3880 }
3881 $i++;
3882 }
3883 } else {
3884 $this->error = $this->db->error().' sql='.$sql;
3885 return -1;
3886 }
3887
3888 if (empty($year)) {
3889 $year = strftime('%Y', time());
3890 $month = strftime('%m', time());
3891 } elseif ($year == -1) {
3892 $year = '';
3893 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3894 } else {
3895 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3896 }
3897
3898 $result = array();
3899
3900 for ($j = 0; $j < 12; $j++) {
3901 // $ids is 'D', 'N', 'O', 'S', ... (First letter of month in user language)
3902 $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, 1970), "%b"), 1, 'right', 'UTF-8', 1));
3903
3904 //print $idx.'-'.$year.'-'.$month.'<br>';
3905 $result[$j] = array($idx, isset($tab[$year.$month]) ? $tab[$year.$month] : 0);
3906 // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
3907
3908 $month = "0".($month - 1);
3909 if (dol_strlen($month) == 3) {
3910 $month = substr($month, 1);
3911 }
3912 if ($month == 0) {
3913 $month = 12;
3914 $year = $year - 1;
3915 }
3916 }
3917
3918 return array_reverse($result);
3919 }
3920
3921
3922 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3933 public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3934 {
3935 // phpcs:enable
3936 global $conf;
3937 global $user;
3938
3939 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
3940 if ($mode == 'bynumber') {
3941 $sql .= ", count(DISTINCT f.rowid)";
3942 }
3943 $sql .= ", sum(d.total_ht) as total_ht";
3944 $sql .= " FROM ".$this->db->prefix()."facturedet as d, ".$this->db->prefix()."facture as f, ".$this->db->prefix()."societe as s";
3945 if ($filteronproducttype >= 0) {
3946 $sql .= ", ".$this->db->prefix()."product as p";
3947 }
3948 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3949 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3950 }
3951 $sql .= " WHERE f.rowid = d.fk_facture";
3952 if ($this->id > 0) {
3953 $sql .= " AND d.fk_product = ".((int) $this->id);
3954 } else {
3955 $sql .= " AND d.fk_product > 0";
3956 }
3957 if ($filteronproducttype >= 0) {
3958 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
3959 }
3960 $sql .= " AND f.fk_soc = s.rowid";
3961 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3962 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3963 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3964 }
3965 if ($socid > 0) {
3966 $sql .= " AND f.fk_soc = $socid";
3967 }
3968 $sql .= $morefilter;
3969 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
3970 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
3971
3972 return $this->_get_stats($sql, $mode, $year);
3973 }
3974
3975
3976 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3987 public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3988 {
3989 // phpcs:enable
3990 global $conf;
3991 global $user;
3992
3993 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
3994 if ($mode == 'bynumber') {
3995 $sql .= ", count(DISTINCT f.rowid)";
3996 }
3997 $sql .= ", sum(d.total_ht) as total_ht";
3998 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as d, ".$this->db->prefix()."facture_fourn as f, ".$this->db->prefix()."societe as s";
3999 if ($filteronproducttype >= 0) {
4000 $sql .= ", ".$this->db->prefix()."product as p";
4001 }
4002 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4003 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4004 }
4005 $sql .= " WHERE f.rowid = d.fk_facture_fourn";
4006 if ($this->id > 0) {
4007 $sql .= " AND d.fk_product = ".((int) $this->id);
4008 } else {
4009 $sql .= " AND d.fk_product > 0";
4010 }
4011 if ($filteronproducttype >= 0) {
4012 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4013 }
4014 $sql .= " AND f.fk_soc = s.rowid";
4015 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4016 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4017 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4018 }
4019 if ($socid > 0) {
4020 $sql .= " AND f.fk_soc = $socid";
4021 }
4022 $sql .= $morefilter;
4023 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4024 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4025
4026 return $this->_get_stats($sql, $mode, $year);
4027 }
4028
4029 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4040 public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4041 {
4042 // phpcs:enable
4043 global $conf, $user;
4044
4045 $sql = "SELECT sum(d.qty) as qty, date_format(p.datep, '%Y%m')";
4046 if ($mode == 'bynumber') {
4047 $sql .= ", count(DISTINCT p.rowid)";
4048 }
4049 $sql .= ", sum(d.total_ht) as total_ht";
4050 $sql .= " FROM ".$this->db->prefix()."propaldet as d, ".$this->db->prefix()."propal as p, ".$this->db->prefix()."societe as s";
4051 if ($filteronproducttype >= 0) {
4052 $sql .= ", ".$this->db->prefix()."product as prod";
4053 }
4054 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4055 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4056 }
4057 $sql .= " WHERE p.rowid = d.fk_propal";
4058 if ($this->id > 0) {
4059 $sql .= " AND d.fk_product = ".((int) $this->id);
4060 } else {
4061 $sql .= " AND d.fk_product > 0";
4062 }
4063 if ($filteronproducttype >= 0) {
4064 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4065 }
4066 $sql .= " AND p.fk_soc = s.rowid";
4067 $sql .= " AND p.entity IN (".getEntity('propal').")";
4068 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4069 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4070 }
4071 if ($socid > 0) {
4072 $sql .= " AND p.fk_soc = ".((int) $socid);
4073 }
4074 $sql .= $morefilter;
4075 $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
4076 $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
4077
4078 return $this->_get_stats($sql, $mode, $year);
4079 }
4080
4081 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4092 public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4093 {
4094 // phpcs:enable
4095 global $conf;
4096 global $user;
4097
4098 $sql = "SELECT sum(d.qty) as qty, date_format(p.date_valid, '%Y%m')";
4099 if ($mode == 'bynumber') {
4100 $sql .= ", count(DISTINCT p.rowid)";
4101 }
4102 $sql .= ", sum(d.total_ht) as total_ht";
4103 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as d, ".$this->db->prefix()."supplier_proposal as p, ".$this->db->prefix()."societe as s";
4104 if ($filteronproducttype >= 0) {
4105 $sql .= ", ".$this->db->prefix()."product as prod";
4106 }
4107 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4108 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4109 }
4110 $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
4111 if ($this->id > 0) {
4112 $sql .= " AND d.fk_product = ".((int) $this->id);
4113 } else {
4114 $sql .= " AND d.fk_product > 0";
4115 }
4116 if ($filteronproducttype >= 0) {
4117 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4118 }
4119 $sql .= " AND p.fk_soc = s.rowid";
4120 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
4121 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4122 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4123 }
4124 if ($socid > 0) {
4125 $sql .= " AND p.fk_soc = ".((int) $socid);
4126 }
4127 $sql .= $morefilter;
4128 $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
4129 $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
4130
4131 return $this->_get_stats($sql, $mode, $year);
4132 }
4133
4134 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4145 public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4146 {
4147 // phpcs:enable
4148 global $conf, $user;
4149
4150 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4151 if ($mode == 'bynumber') {
4152 $sql .= ", count(DISTINCT c.rowid)";
4153 }
4154 $sql .= ", sum(d.total_ht) as total_ht";
4155 $sql .= " FROM ".$this->db->prefix()."commandedet as d, ".$this->db->prefix()."commande as c, ".$this->db->prefix()."societe as s";
4156 if ($filteronproducttype >= 0) {
4157 $sql .= ", ".$this->db->prefix()."product as p";
4158 }
4159 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4160 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4161 }
4162 $sql .= " WHERE c.rowid = d.fk_commande";
4163 if ($this->id > 0) {
4164 $sql .= " AND d.fk_product = ".((int) $this->id);
4165 } else {
4166 $sql .= " AND d.fk_product > 0";
4167 }
4168 if ($filteronproducttype >= 0) {
4169 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4170 }
4171 $sql .= " AND c.fk_soc = s.rowid";
4172 $sql .= " AND c.entity IN (".getEntity('commande').")";
4173 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4174 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4175 }
4176 if ($socid > 0) {
4177 $sql .= " AND c.fk_soc = ".((int) $socid);
4178 }
4179 $sql .= $morefilter;
4180 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4181 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4182
4183 return $this->_get_stats($sql, $mode, $year);
4184 }
4185
4186 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4197 public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4198 {
4199 // phpcs:enable
4200 global $conf, $user;
4201
4202 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4203 if ($mode == 'bynumber') {
4204 $sql .= ", count(DISTINCT c.rowid)";
4205 }
4206 $sql .= ", sum(d.total_ht) as total_ht";
4207 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as d, ".$this->db->prefix()."commande_fournisseur as c, ".$this->db->prefix()."societe as s";
4208 if ($filteronproducttype >= 0) {
4209 $sql .= ", ".$this->db->prefix()."product as p";
4210 }
4211 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4212 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4213 }
4214 $sql .= " WHERE c.rowid = d.fk_commande";
4215 if ($this->id > 0) {
4216 $sql .= " AND d.fk_product = ".((int) $this->id);
4217 } else {
4218 $sql .= " AND d.fk_product > 0";
4219 }
4220 if ($filteronproducttype >= 0) {
4221 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4222 }
4223 $sql .= " AND c.fk_soc = s.rowid";
4224 $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
4225 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4226 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4227 }
4228 if ($socid > 0) {
4229 $sql .= " AND c.fk_soc = ".((int) $socid);
4230 }
4231 $sql .= $morefilter;
4232 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4233 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4234
4235 return $this->_get_stats($sql, $mode, $year);
4236 }
4237
4238 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4249 public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4250 {
4251 // phpcs:enable
4252 global $conf, $user;
4253
4254 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_contrat, '%Y%m')";
4255 if ($mode == 'bynumber') {
4256 $sql .= ", count(DISTINCT c.rowid)";
4257 }
4258 $sql .= ", sum(d.total_ht) as total_ht";
4259 $sql .= " FROM ".$this->db->prefix()."contratdet as d, ".$this->db->prefix()."contrat as c, ".$this->db->prefix()."societe as s";
4260 if ($filteronproducttype >= 0) {
4261 $sql .= ", ".$this->db->prefix()."product as p";
4262 }
4263 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4264 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4265 }
4266
4267 $sql .= " WHERE c.entity IN (".getEntity('contract').")";
4268 $sql .= " AND c.rowid = d.fk_contrat";
4269
4270 if ($this->id > 0) {
4271 $sql .= " AND d.fk_product = ".((int) $this->id);
4272 } else {
4273 $sql .= " AND d.fk_product > 0";
4274 }
4275 if ($filteronproducttype >= 0) {
4276 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4277 }
4278 $sql .= " AND c.fk_soc = s.rowid";
4279
4280 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4281 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4282 }
4283 if ($socid > 0) {
4284 $sql .= " AND c.fk_soc = ".((int) $socid);
4285 }
4286 $sql .= $morefilter;
4287 $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
4288 $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
4289
4290 return $this->_get_stats($sql, $mode, $year);
4291 }
4292
4293 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4304 public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4305 {
4306 // phpcs:enable
4307 global $conf, $user;
4308
4309 $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
4310 if ($mode == 'bynumber') {
4311 $sql .= ", count(DISTINCT d.rowid)";
4312 }
4313 $sql .= " FROM ".$this->db->prefix()."mrp_mo as d LEFT JOIN ".$this->db->prefix()."societe as s ON d.fk_soc = s.rowid";
4314 if ($filteronproducttype >= 0) {
4315 $sql .= ", ".$this->db->prefix()."product as p";
4316 }
4317 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4318 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4319 }
4320
4321 $sql .= " WHERE d.entity IN (".getEntity('mo').")";
4322 $sql .= " AND d.status > 0";
4323
4324 if ($this->id > 0) {
4325 $sql .= " AND d.fk_product = ".((int) $this->id);
4326 } else {
4327 $sql .= " AND d.fk_product > 0";
4328 }
4329 if ($filteronproducttype >= 0) {
4330 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4331 }
4332
4333 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4334 $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4335 }
4336 if ($socid > 0) {
4337 $sql .= " AND d.fk_soc = ".((int) $socid);
4338 }
4339 $sql .= $morefilter;
4340 $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
4341 $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
4342
4343 return $this->_get_stats($sql, $mode, $year);
4344 }
4345
4346 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4357 public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4358 {
4359 global $user;
4360
4361 // phpcs:enable
4362 // Clean parameters
4363 if (!is_numeric($id_pere)) {
4364 $id_pere = 0;
4365 }
4366 if (!is_numeric($id_fils)) {
4367 $id_fils = 0;
4368 }
4369 if (!is_numeric($incdec)) {
4370 $incdec = 0;
4371 }
4372
4373 $result = $this->del_sousproduit($id_pere, $id_fils);
4374 if ($result < 0) {
4375 return $result;
4376 }
4377
4378 // Check not already father of id_pere (to avoid father -> child -> father links)
4379 $sql = "SELECT fk_product_pere from ".$this->db->prefix()."product_association";
4380 $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere);
4381 if (!$this->db->query($sql)) {
4382 dol_print_error($this->db);
4383 return -1;
4384 } else {
4385 //Selection of the highest row
4386 $sql = "SELECT MAX(rang) as max_rank FROM ".$this->db->prefix()."product_association";
4387 $sql .= " WHERE fk_product_pere = ".((int) $id_pere);
4388 $resql = $this->db->query($sql);
4389 if ($resql) {
4390 $obj = $this->db->fetch_object($resql);
4391 $rank = $obj->max_rank + 1;
4392 //Addition of a product with the highest rank +1
4393 $sql = "INSERT INTO ".$this->db->prefix()."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)";
4394 $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".price2num($qty, 'MS').", ".price2num($incdec, 'MS').", ".((int) $rank).")";
4395 if (! $this->db->query($sql)) {
4396 dol_print_error($this->db);
4397 return -1;
4398 } else {
4399 if (!$notrigger) {
4400 // Call trigger
4401 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_ADD', $user);
4402 if ($result < 0) {
4403 $this->error = $this->db->lasterror();
4404 dol_syslog(get_class($this).'::addSubproduct error='.$this->error, LOG_ERR);
4405 return -1;
4406 }
4407 }
4408 // End call triggers
4409
4410 return 1;
4411 }
4412 } else {
4413 dol_print_error($this->db);
4414 return -1;
4415 }
4416 }
4417 }
4418
4419 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4430 public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4431 {
4432 global $user;
4433
4434 // phpcs:enable
4435 // Clean parameters
4436 if (!is_numeric($id_pere)) {
4437 $id_pere = 0;
4438 }
4439 if (!is_numeric($id_fils)) {
4440 $id_fils = 0;
4441 }
4442 if (!is_numeric($incdec)) {
4443 $incdec = 1;
4444 }
4445 if (!is_numeric($qty)) {
4446 $qty = 1;
4447 }
4448
4449 $sql = 'UPDATE '.$this->db->prefix().'product_association SET ';
4450 $sql .= 'qty = '.price2num($qty, 'MS');
4451 $sql .= ',incdec = '.price2num($incdec, 'MS');
4452 $sql .= ' WHERE fk_product_pere = '.((int) $id_pere).' AND fk_product_fils = '.((int) $id_fils);
4453
4454 if (!$this->db->query($sql)) {
4455 dol_print_error($this->db);
4456 return -1;
4457 } else {
4458 if (!$notrigger) {
4459 // Call trigger
4460 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_UPDATE', $user);
4461 if ($result < 0) {
4462 $this->error = $this->db->lasterror();
4463 dol_syslog(get_class($this).'::updateSubproduct error='.$this->error, LOG_ERR);
4464 return -1;
4465 }
4466 // End call triggers
4467 }
4468
4469 return 1;
4470 }
4471 }
4472
4473 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4482 public function del_sousproduit($fk_parent, $fk_child, $notrigger = 0)
4483 {
4484 global $user;
4485
4486 // phpcs:enable
4487 if (!is_numeric($fk_parent)) {
4488 $fk_parent = 0;
4489 }
4490 if (!is_numeric($fk_child)) {
4491 $fk_child = 0;
4492 }
4493
4494 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
4495 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4496 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4497
4498 dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
4499 if (!$this->db->query($sql)) {
4500 dol_print_error($this->db);
4501 return -1;
4502 }
4503
4504 // Updated ranks so that none are missing
4505 $sqlrank = "SELECT rowid, rang FROM ".$this->db->prefix()."product_association";
4506 $sqlrank.= " WHERE fk_product_pere = ".((int) $fk_parent);
4507 $sqlrank.= " ORDER BY rang";
4508 $resqlrank = $this->db->query($sqlrank);
4509 if ($resqlrank) {
4510 $cpt = 0;
4511 while ($objrank = $this->db->fetch_object($resqlrank)) {
4512 $cpt++;
4513 $sql = "UPDATE ".$this->db->prefix()."product_association";
4514 $sql.= " SET rang = ".((int) $cpt);
4515 $sql.= " WHERE rowid = ".((int) $objrank->rowid);
4516 if (! $this->db->query($sql)) {
4517 dol_print_error($this->db);
4518 return -1;
4519 }
4520 }
4521 }
4522
4523 if (!$notrigger) {
4524 // Call trigger
4525 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_DELETE', $user);
4526 if ($result < 0) {
4527 $this->error = $this->db->lasterror();
4528 dol_syslog(get_class($this).'::delSubproduct error='.$this->error, LOG_ERR);
4529 return -1;
4530 }
4531 // End call triggers
4532 }
4533
4534 return 1;
4535 }
4536
4537 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4545 public function is_sousproduit($fk_parent, $fk_child)
4546 {
4547 // phpcs:enable
4548 $sql = "SELECT fk_product_pere, qty, incdec";
4549 $sql .= " FROM ".$this->db->prefix()."product_association";
4550 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4551 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4552
4553 $result = $this->db->query($sql);
4554 if ($result) {
4555 $num = $this->db->num_rows($result);
4556
4557 if ($num > 0) {
4558 $obj = $this->db->fetch_object($result);
4559
4560 $this->is_sousproduit_qty = $obj->qty;
4561 $this->is_sousproduit_incdec = $obj->incdec;
4562
4563 return true;
4564 } else {
4565 return false;
4566 }
4567 } else {
4568 dol_print_error($this->db);
4569 return -1;
4570 }
4571 }
4572
4573
4574 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4585 public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
4586 {
4587 // phpcs:enable
4588 global $conf;
4589
4590 $now = dol_now();
4591
4592 dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
4593
4594 // Clean parameters
4595 $quantity = price2num($quantity, 'MS');
4596
4597 if ($ref_fourn) {
4598 $sql = "SELECT rowid, fk_product";
4599 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4600 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4601 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4602 $sql .= " AND fk_product <> ".((int) $this->id);
4603 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4604
4605 $resql = $this->db->query($sql);
4606 if ($resql) {
4607 $obj = $this->db->fetch_object($resql);
4608 if ($obj) {
4609 // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
4610 $this->product_id_already_linked = $obj->fk_product;
4611 return -3;
4612 }
4613 $this->db->free($resql);
4614 }
4615 }
4616
4617 $sql = "SELECT rowid";
4618 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4619 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4620 if ($ref_fourn) {
4621 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4622 } else {
4623 $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
4624 }
4625 $sql .= " AND quantity = ".((float) $quantity);
4626 $sql .= " AND fk_product = ".((int) $this->id);
4627 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4628
4629 $resql = $this->db->query($sql);
4630 if ($resql) {
4631 $obj = $this->db->fetch_object($resql);
4632
4633 // The reference supplier does not exist, we create it for this product.
4634 if (empty($obj)) {
4635 $sql = "INSERT INTO ".$this->db->prefix()."product_fournisseur_price(";
4636 $sql .= "datec";
4637 $sql .= ", entity";
4638 $sql .= ", fk_product";
4639 $sql .= ", fk_soc";
4640 $sql .= ", ref_fourn";
4641 $sql .= ", quantity";
4642 $sql .= ", fk_user";
4643 $sql .= ", tva_tx";
4644 $sql .= ") VALUES (";
4645 $sql .= "'".$this->db->idate($now)."'";
4646 $sql .= ", ".$conf->entity;
4647 $sql .= ", ".$this->id;
4648 $sql .= ", ".$id_fourn;
4649 $sql .= ", '".$this->db->escape($ref_fourn)."'";
4650 $sql .= ", ".$quantity;
4651 $sql .= ", ".$user->id;
4652 $sql .= ", 0";
4653 $sql .= ")";
4654
4655 if ($this->db->query($sql)) {
4656 $this->product_fourn_price_id = $this->db->last_insert_id($this->db->prefix()."product_fournisseur_price");
4657 return 1;
4658 } else {
4659 $this->error = $this->db->lasterror();
4660 return -1;
4661 }
4662 } else {
4663 // If the supplier price already exists for this product and quantity
4664 $this->product_fourn_price_id = $obj->rowid;
4665 return 0;
4666 }
4667 } else {
4668 $this->error = $this->db->lasterror();
4669 return -2;
4670 }
4671 }
4672
4673
4674 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4680 public function list_suppliers()
4681 {
4682 // phpcs:enable
4683 global $conf;
4684
4685 $list = array();
4686
4687 $sql = "SELECT DISTINCT p.fk_soc";
4688 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as p";
4689 $sql .= " WHERE p.fk_product = ".((int) $this->id);
4690 $sql .= " AND p.entity = ".((int) $conf->entity);
4691
4692 $result = $this->db->query($sql);
4693 if ($result) {
4694 $num = $this->db->num_rows($result);
4695 $i = 0;
4696 while ($i < $num) {
4697 $obj = $this->db->fetch_object($result);
4698 $list[$i] = $obj->fk_soc;
4699 $i++;
4700 }
4701 }
4702
4703 return $list;
4704 }
4705
4706 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4714 public function clone_price($fromId, $toId)
4715 {
4716 global $conf, $user;
4717
4718 $now = dol_now();
4719
4720 $this->db->begin();
4721
4722 // prices
4723 $sql = "INSERT INTO ".$this->db->prefix()."product_price (";
4724 $sql .= " entity";
4725 $sql .= ", fk_product";
4726 $sql .= ", date_price";
4727 $sql .= ", price_level";
4728 $sql .= ", price";
4729 $sql .= ", price_ttc";
4730 $sql .= ", price_min";
4731 $sql .= ", price_min_ttc";
4732 $sql .= ", price_base_type";
4733 $sql .= ", default_vat_code";
4734 $sql .= ", tva_tx";
4735 $sql .= ", recuperableonly";
4736 $sql .= ", localtax1_tx";
4737 $sql .= ", localtax1_type";
4738 $sql .= ", localtax2_tx";
4739 $sql .= ", localtax2_type";
4740 $sql .= ", fk_user_author";
4741 $sql .= ", tosell";
4742 $sql .= ", price_by_qty";
4743 $sql .= ", fk_price_expression";
4744 $sql .= ", fk_multicurrency";
4745 $sql .= ", multicurrency_code";
4746 $sql .= ", multicurrency_tx";
4747 $sql .= ", multicurrency_price";
4748 $sql .= ", multicurrency_price_ttc";
4749 $sql .= ")";
4750 $sql .= " SELECT";
4751 $sql .= " entity";
4752 $sql .= ", ".$toId;
4753 $sql .= ", '".$this->db->idate($now)."'";
4754 $sql .= ", price_level";
4755 $sql .= ", price";
4756 $sql .= ", price_ttc";
4757 $sql .= ", price_min";
4758 $sql .= ", price_min_ttc";
4759 $sql .= ", price_base_type";
4760 $sql .= ", default_vat_code";
4761 $sql .= ", tva_tx";
4762 $sql .= ", recuperableonly";
4763 $sql .= ", localtax1_tx";
4764 $sql .= ", localtax1_type";
4765 $sql .= ", localtax2_tx";
4766 $sql .= ", localtax2_type";
4767 $sql .= ", ".$user->id;
4768 $sql .= ", tosell";
4769 $sql .= ", price_by_qty";
4770 $sql .= ", fk_price_expression";
4771 $sql .= ", fk_multicurrency";
4772 $sql .= ", multicurrency_code";
4773 $sql .= ", multicurrency_tx";
4774 $sql .= ", multicurrency_price";
4775 $sql .= ", multicurrency_price_ttc";
4776 $sql .= " FROM ".$this->db->prefix()."product_price ps";
4777 $sql .= " WHERE fk_product = ".((int) $fromId);
4778 $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)";
4779 $sql .= " ORDER BY date_price DESC";
4780
4781 dol_syslog(__METHOD__, LOG_DEBUG);
4782 $resql = $this->db->query($sql);
4783 if (!$resql) {
4784 $this->db->rollback();
4785 return -1;
4786 }
4787
4788 $this->db->commit();
4789 return 1;
4790 }
4791
4792 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4800 public function clone_associations($fromId, $toId)
4801 {
4802 // phpcs:enable
4803 $this->db->begin();
4804
4805 $sql = 'INSERT INTO '.$this->db->prefix().'product_association (fk_product_pere, fk_product_fils, qty, incdec)';
4806 $sql .= " SELECT ".$toId.", fk_product_fils, qty, incdec FROM ".$this->db->prefix()."product_association";
4807 $sql .= " WHERE fk_product_pere = ".((int) $fromId);
4808
4809 dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
4810 if (!$this->db->query($sql)) {
4811 $this->db->rollback();
4812 return -1;
4813 }
4814
4815 $this->db->commit();
4816 return 1;
4817 }
4818
4819 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4827 public function clone_fournisseurs($fromId, $toId)
4828 {
4829 // phpcs:enable
4830 $this->db->begin();
4831
4832 $now = dol_now();
4833
4834 // les fournisseurs
4835 /*$sql = "INSERT ".$this->db->prefix()."product_fournisseur ("
4836 . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
4837 . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
4838 . " FROM ".$this->db->prefix()."product_fournisseur"
4839 . " WHERE fk_product = ".((int) $fromId);
4840
4841 if ( ! $this->db->query($sql ) )
4842 {
4843 $this->db->rollback();
4844 return -1;
4845 }*/
4846
4847 // les prix de fournisseurs.
4848 $sql = "INSERT ".$this->db->prefix()."product_fournisseur_price (";
4849 $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user, tva_tx)";
4850 $sql .= " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, price, quantity, fk_user, tva_tx";
4851 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4852 $sql .= " WHERE fk_product = ".((int) $fromId);
4853
4854 dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
4855 $resql = $this->db->query($sql);
4856 if (!$resql) {
4857 $this->db->rollback();
4858 return -1;
4859 } else {
4860 $this->db->commit();
4861 return 1;
4862 }
4863 }
4864
4865 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4878 public function fetch_prod_arbo($prod, $compl_path = '', $multiply = 1, $level = 1, $id_parent = 0, $ignore_stock_load = 0)
4879 {
4880 // phpcs:enable
4881 global $conf, $langs;
4882
4883 $tmpproduct = null;
4884 //var_dump($prod);
4885 foreach ($prod as $id_product => $desc_pere) { // $id_product is 0 (first call starting with root top) or an id of a sub_product
4886 if (is_array($desc_pere)) { // If desc_pere is an array, this means it's a child
4887 $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
4888 $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
4889 $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
4890 $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
4891 $incdec = (!empty($desc_pere[4]) ? $desc_pere[4] : 0);
4892
4893 if ($multiply < 1) {
4894 $multiply = 1;
4895 }
4896
4897 //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
4898 if (is_null($tmpproduct)) {
4899 $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
4900 }
4901 $tmpproduct->fetch($id); // Load product to get ->ref
4902
4903 if (empty($ignore_stock_load) && ($tmpproduct->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
4904 $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
4905 }
4906
4907 $this->res[] = array(
4908 'id'=>$id, // Id product
4909 'id_parent'=>$id_parent,
4910 'ref'=>$tmpproduct->ref, // Ref product
4911 'nb'=>$nb, // Nb of units that compose parent product
4912 'nb_total'=>$nb * $multiply, // Nb of units for all nb of product
4913 'stock'=>$tmpproduct->stock_reel, // Stock
4914 'stock_alert'=>$tmpproduct->seuil_stock_alerte, // Stock alert
4915 'label'=>$label,
4916 'fullpath'=>$compl_path.$label, // Label
4917 'type'=>$type, // Nb of units that compose parent product
4918 'desiredstock'=>$tmpproduct->desiredstock,
4919 'level'=>$level,
4920 'incdec'=>$incdec,
4921 'entity'=>$tmpproduct->entity
4922 );
4923
4924 // Recursive call if there is childs to child
4925 if (isset($desc_pere['childs']) && is_array($desc_pere['childs'])) {
4926 //print 'YYY We go down for '.$desc_pere[3]." -> \n";
4927 $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1] * $multiply, $level + 1, $id, $ignore_stock_load);
4928 }
4929 }
4930 }
4931 }
4932
4933 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4942 public function get_arbo_each_prod($multiply = 1, $ignore_stock_load = 0)
4943 {
4944 // phpcs:enable
4945 $this->res = array();
4946 if (isset($this->sousprods) && is_array($this->sousprods)) {
4947 foreach ($this->sousprods as $prod_name => $desc_product) {
4948 if (is_array($desc_product)) {
4949 $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id, $ignore_stock_load); // This set $this->res
4950 }
4951 }
4952 }
4953 //var_dump($res);
4954 return $this->res;
4955 }
4956
4964 public function hasFatherOrChild($mode = 0)
4965 {
4966 $nb = 0;
4967
4968 $sql = "SELECT COUNT(pa.rowid) as nb";
4969 $sql .= " FROM ".$this->db->prefix()."product_association as pa";
4970 if ($mode == 0) {
4971 $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id)." OR pa.fk_product_pere = ".((int) $this->id);
4972 } elseif ($mode == -1) {
4973 $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)
4974 } elseif ($mode == 1) {
4975 $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)
4976 }
4977
4978 $resql = $this->db->query($sql);
4979 if ($resql) {
4980 $obj = $this->db->fetch_object($resql);
4981 if ($obj) {
4982 $nb = $obj->nb;
4983 }
4984 } else {
4985 return -1;
4986 }
4987
4988 return $nb;
4989 }
4990
4996 public function hasVariants()
4997 {
4998 $nb = 0;
4999 $sql = "SELECT count(rowid) as nb FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_parent = ".((int) $this->id);
5000 $sql .= " AND entity IN (".getEntity('product').")";
5001
5002 $resql = $this->db->query($sql);
5003 if ($resql) {
5004 $obj = $this->db->fetch_object($resql);
5005 if ($obj) {
5006 $nb = $obj->nb;
5007 }
5008 }
5009
5010 return $nb;
5011 }
5012
5013
5019 public function isVariant()
5020 {
5021 global $conf;
5022 if (isModEnabled('variants')) {
5023 $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_child = ".((int) $this->id)." AND entity IN (".getEntity('product').")";
5024
5025 $query = $this->db->query($sql);
5026
5027 if ($query) {
5028 if (!$this->db->num_rows($query)) {
5029 return false;
5030 }
5031 return true;
5032 } else {
5033 dol_print_error($this->db);
5034 return -1;
5035 }
5036 } else {
5037 return false;
5038 }
5039 }
5040
5047 public function getFather()
5048 {
5049 $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";
5050 $sql .= ", p.tosell as status, p.tobuy as status_buy";
5051 $sql .= " FROM ".$this->db->prefix()."product_association as pa,";
5052 $sql .= " ".$this->db->prefix()."product as p";
5053 $sql .= " WHERE p.rowid = pa.fk_product_pere";
5054 $sql .= " AND pa.fk_product_fils = ".((int) $this->id);
5055
5056 $res = $this->db->query($sql);
5057 if ($res) {
5058 $prods = array();
5059 while ($record = $this->db->fetch_array($res)) {
5060 // $record['id'] = $record['rowid'] = id of father
5061 $prods[$record['id']]['id'] = $record['rowid'];
5062 $prods[$record['id']]['ref'] = $record['ref'];
5063 $prods[$record['id']]['label'] = $record['label'];
5064 $prods[$record['id']]['qty'] = $record['qty'];
5065 $prods[$record['id']]['incdec'] = $record['incdec'];
5066 $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
5067 $prods[$record['id']]['entity'] = $record['entity'];
5068 $prods[$record['id']]['status'] = $record['status'];
5069 $prods[$record['id']]['status_buy'] = $record['status_buy'];
5070 }
5071 return $prods;
5072 } else {
5073 dol_print_error($this->db);
5074 return -1;
5075 }
5076 }
5077
5078
5088 public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array())
5089 {
5090 if (empty($id)) {
5091 return array();
5092 }
5093
5094 $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
5095 $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
5096 $sql .= " pa.rowid as fk_association, pa.rang";
5097 $sql .= " FROM ".$this->db->prefix()."product as p,";
5098 $sql .= " ".$this->db->prefix()."product_association as pa";
5099 $sql .= " WHERE p.rowid = pa.fk_product_fils";
5100 $sql .= " AND pa.fk_product_pere = ".((int) $id);
5101 $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
5102 $sql.= " ORDER BY pa.rang";
5103
5104 dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG);
5105
5106 // Protection against infinite loop
5107 if ($level > 30) {
5108 return array();
5109 }
5110
5111 $res = $this->db->query($sql);
5112 if ($res) {
5113 $prods = array();
5114 if ($this->db->num_rows($res) > 0) {
5115 $parents[] = $id;
5116 }
5117
5118 while ($rec = $this->db->fetch_array($res)) {
5119 if (in_array($rec['id'], $parents)) {
5120 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);
5121 continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5122 }
5123
5124 $prods[$rec['rowid']] = array(
5125 0=>$rec['rowid'],
5126 1=>$rec['qty'],
5127 2=>$rec['fk_product_type'],
5128 3=>$this->db->escape($rec['label']),
5129 4=>$rec['incdec'],
5130 5=>$rec['ref'],
5131 6=>$rec['fk_association'],
5132 7=>$rec['rang']
5133 );
5134 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5135 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5136 if (empty($firstlevelonly)) {
5137 $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents);
5138 foreach ($listofchilds as $keyChild => $valueChild) {
5139 $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5140 }
5141 }
5142 }
5143
5144 return $prods;
5145 } else {
5146 dol_print_error($this->db);
5147 return -1;
5148 }
5149 }
5150
5151 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5158 public function get_sousproduits_arbo()
5159 {
5160 // phpcs:enable
5161 $parent = array();
5162
5163 foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5164 $parent[$this->label][$keyChild] = $valueChild;
5165 }
5166 foreach ($parent as $key => $value) { // key=label, value is array of childs
5167 $this->sousprods[$key] = $value;
5168 }
5169 }
5170
5177 public function getTooltipContentArray($params)
5178 {
5179 global $conf, $langs;
5180
5181 $langs->loadLangs(array('products', 'other'));
5182
5183 $datas = array();
5184 $nofetch = !empty($params['nofetch']);
5185
5186 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5187 return ['optimize' => $langs->trans("ShowProduct")];
5188 }
5189
5190 if (!empty($this->entity)) {
5191 $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, 1);
5192 if ($this->nbphoto > 0) {
5193 $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5194 }
5195 }
5196
5197 if ($this->type == Product::TYPE_PRODUCT) {
5198 $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5199 } elseif ($this->type == Product::TYPE_SERVICE) {
5200 $datas['picto']= img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5201 }
5202 if (isset($this->status) && isset($this->status_buy)) {
5203 $datas['status']= ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5204 }
5205
5206 if (!empty($this->ref)) {
5207 $datas['ref']= '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5208 }
5209 if (!empty($this->label)) {
5210 $datas['label']= '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label;
5211 }
5212 if (!empty($this->description)) {
5213 $datas['description']= '<br><b>'.$langs->trans('ProductDescription').':</b> '.dolGetFirstLineofText($this->description, 5);
5214 }
5215 if ($this->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
5216 if (isModEnabled('productbatch')) {
5217 $langs->load("productbatch");
5218 $datas['batchstatus']= "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5219 }
5220 }
5221 if (isModEnabled('barcode')) {
5222 $datas['barcode']= '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5223 }
5224
5225 if ($this->type == Product::TYPE_PRODUCT) {
5226 if ($this->weight) {
5227 $datas['weight']= "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5228 }
5229 $labelsize = "";
5230 if ($this->length) {
5231 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5232 }
5233 if ($this->width) {
5234 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5235 }
5236 if ($this->height) {
5237 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5238 }
5239 if ($labelsize) {
5240 $datas['size']= "<br>".$labelsize;
5241 }
5242
5243 $labelsurfacevolume = "";
5244 if ($this->surface) {
5245 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5246 }
5247 if ($this->volume) {
5248 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5249 }
5250 if ($labelsurfacevolume) {
5251 $datas['surface']= "<br>" . $labelsurfacevolume;
5252 }
5253 }
5254 if (!empty($this->pmp) && $this->pmp) {
5255 $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5256 }
5257
5258 if (isModEnabled('accounting')) {
5259 if ($this->status && isset($this->accountancy_code_sell)) {
5260 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5261 $selllabel = '<br>';
5262 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5263 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5264 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5265 $datas['accountancysell'] = $selllabel;
5266 }
5267 if ($this->status_buy && isset($this->accountancy_code_buy)) {
5268 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5269 $buylabel = '';
5270 if (empty($this->status)) {
5271 $buylabel .= '<br>';
5272 }
5273 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5274 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5275 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5276 $datas['accountancybuy'] = $buylabel;
5277 }
5278 }
5279 // show categories for this record only in ajax to not overload lists
5280 if (isModEnabled('categorie') && !$nofetch) {
5281 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5282 $form = new Form($this->db);
5283 $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5284 }
5285
5286 return $datas;
5287 }
5288
5302 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5303 {
5304 global $conf, $langs, $hookmanager;
5305 include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5306
5307 $result = '';
5308
5309 $newref = $this->ref;
5310 if ($maxlength) {
5311 $newref = dol_trunc($newref, $maxlength, 'middle');
5312 }
5313 $params = [
5314 'id' => $this->id,
5315 'objecttype' => (isset($this->type) ? ($this->type == 1 ? 'service' : 'product') : $this->element),
5316 'option' => $option,
5317 'nofetch' => 1,
5318 ];
5319 $classfortooltip = 'classfortooltip';
5320 $dataparams = '';
5321 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5322 $classfortooltip = 'classforajaxtooltip';
5323 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5324 $label = '';
5325 } else {
5326 $label = implode($this->getTooltipContentArray($params));
5327 }
5328
5329 $linkclose = '';
5330 if (empty($notooltip)) {
5331 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5332 $label = $langs->trans("ShowProduct");
5333 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5334 }
5335 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5336 $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5337 } else {
5338 $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5339 }
5340
5341 if ($option == 'supplier' || $option == 'category') {
5342 $url = DOL_URL_ROOT.'/product/fournisseurs.php?id='.$this->id;
5343 } elseif ($option == 'stock') {
5344 $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5345 } elseif ($option == 'composition') {
5346 $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5347 } else {
5348 $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5349 }
5350
5351 if ($option !== 'nolink') {
5352 // Add param to save lastsearch_values or not
5353 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
5354 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
5355 $add_save_lastsearch_values = 1;
5356 }
5357 if ($add_save_lastsearch_values) {
5358 $url .= '&save_lastsearch_values=1';
5359 }
5360 }
5361
5362 $linkstart = '<a href="'.$url.'"';
5363 $linkstart .= $linkclose.'>';
5364 $linkend = '</a>';
5365
5366 $result .= $linkstart;
5367 if ($withpicto) {
5368 if ($this->type == Product::TYPE_PRODUCT) {
5369 $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5370 }
5371 if ($this->type == Product::TYPE_SERVICE) {
5372 $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5373 }
5374 }
5375 $result .= '<span class="aaa">'.dol_escape_htmltag($newref).'</span>';
5376 $result .= $linkend;
5377 if ($withpicto != 2) {
5378 $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
5379 }
5380
5381 global $action;
5382 $hookmanager->initHooks(array('productdao'));
5383 $parameters = array('id'=>$this->id, 'getnomurl' => &$result, 'label' => &$label);
5384 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5385 if ($reshook > 0) {
5386 $result = $hookmanager->resPrint;
5387 } else {
5388 $result .= $hookmanager->resPrint;
5389 }
5390
5391 return $result;
5392 }
5393
5394
5405 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
5406 {
5407 global $conf, $user, $langs;
5408
5409 $langs->load("products");
5410 $outputlangs->load("products");
5411
5412 // Positionne le modele sur le nom du modele a utiliser
5413 if (!dol_strlen($modele)) {
5414 $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
5415 }
5416
5417 $modelpath = "core/modules/product/doc/";
5418
5419 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
5420 }
5421
5429 public function getLibStatut($mode = 0, $type = 0)
5430 {
5431 switch ($type) {
5432 case 0:
5433 return $this->LibStatut($this->status, $mode, $type);
5434 case 1:
5435 return $this->LibStatut($this->status_buy, $mode, $type);
5436 case 2:
5437 return $this->LibStatut($this->status_batch, $mode, $type);
5438 default:
5439 //Simulate previous behavior but should return an error string
5440 return $this->LibStatut($this->status_buy, $mode, $type);
5441 }
5442 }
5443
5444 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5453 public function LibStatut($status, $mode = 0, $type = 0)
5454 {
5455 // phpcs:enable
5456 global $conf, $langs;
5457
5458 $labelStatus = $labelStatusShort = '';
5459
5460 $langs->load('products');
5461 if (isModEnabled('productbatch')) {
5462 $langs->load("productbatch");
5463 }
5464
5465 if ($type == 2) {
5466 switch ($mode) {
5467 case 0:
5468 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
5469 return dolGetStatus($label);
5470 case 1:
5471 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
5472 return dolGetStatus($label);
5473 case 2:
5474 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
5475 case 3:
5476 return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
5477 case 4:
5478 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
5479 case 5:
5480 return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
5481 default:
5482 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
5483 }
5484 }
5485
5486 $statuttrans = empty($status) ? 'status5' : 'status4';
5487
5488 if ($status == 0) {
5489 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5490 if ($type == 0) {
5491 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
5492 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
5493 } elseif ($type == 1) {
5494 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
5495 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
5496 } elseif ($type == 2) {
5497 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
5498 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
5499 }
5500 } elseif ($status == 1) {
5501 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5502 if ($type == 0) {
5503 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
5504 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
5505 } elseif ($type == 1) {
5506 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
5507 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
5508 } elseif ($type == 2) {
5509 $labelStatus = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial'));
5510 $labelStatusShort = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort'));
5511 }
5512 } elseif ($type == 2 && $status == 2) {
5513 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
5514 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
5515 }
5516
5517 if ($mode > 6) {
5518 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
5519 } else {
5520 return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
5521 }
5522 }
5523
5524
5530 public function getLibFinished()
5531 {
5532 global $langs;
5533 $langs->load('products');
5534
5535 if (isset($this->finished) && $this->finished >= 0) {
5536 $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
5537 $resql = $this->db->query($sql);
5538 if ($resql && $this->db->num_rows($resql) > 0) {
5539 $res = $this->db->fetch_array($resql);
5540 $label = $langs->trans($res['label']);
5541 $this->db->free($resql);
5542 return $label;
5543 } else {
5544 $this->error = $this->db->error().' sql='.$sql;
5545 dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
5546 return -1;
5547 }
5548 }
5549
5550 return '';
5551 }
5552
5553
5554 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5571 public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
5572 {
5573 // phpcs:enable
5574 if ($id_entrepot) {
5575 $this->db->begin();
5576
5577 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5578
5579 if ($nbpiece < 0) {
5580 if (!$movement) {
5581 $movement = 1;
5582 }
5583 $nbpiece = abs($nbpiece);
5584 }
5585
5586 $op[0] = "+".trim($nbpiece);
5587 $op[1] = "-".trim($nbpiece);
5588
5589 $movementstock = new MouvementStock($this->db);
5590 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->origin_id
5591 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
5592
5593 if ($result >= 0) {
5594 if ($extrafields) {
5595 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5596 $movementstock->array_options = $array_options;
5597 $movementstock->insertExtraFields();
5598 }
5599 $this->db->commit();
5600 return 1;
5601 } else {
5602 $this->error = $movementstock->error;
5603 $this->errors = $movementstock->errors;
5604
5605 $this->db->rollback();
5606 return -1;
5607 }
5608 }
5609
5610 return -1;
5611 }
5612
5613 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5634 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)
5635 {
5636 // phpcs:enable
5637 if ($id_entrepot) {
5638 $this->db->begin();
5639
5640 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5641
5642 if ($nbpiece < 0) {
5643 if (!$movement) {
5644 $movement = 1;
5645 }
5646 $nbpiece = abs($nbpiece);
5647 }
5648
5649 $op[0] = "+".trim($nbpiece);
5650 $op[1] = "-".trim($nbpiece);
5651
5652 $movementstock = new MouvementStock($this->db);
5653 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->fk_origin
5654 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct, 0, $force_update_batch);
5655
5656 if ($result >= 0) {
5657 if ($extrafields) {
5658 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5659 $movementstock->array_options = $array_options;
5660 $movementstock->insertExtraFields();
5661 }
5662 $this->db->commit();
5663 return 1;
5664 } else {
5665 $this->error = $movementstock->error;
5666 $this->errors = $movementstock->errors;
5667
5668 $this->db->rollback();
5669 return -1;
5670 }
5671 }
5672 return -1;
5673 }
5674
5675 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5688 public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
5689 {
5690 // phpcs:enable
5691 global $conf;
5692
5693 $this->stock_reel = 0;
5694 $this->stock_warehouse = array();
5695 $this->stock_theorique = 0;
5696
5697 // Set filter on warehouse status
5698 $warehouseStatus = array();
5699 if (preg_match('/warehouseclosed/', $option)) {
5701 }
5702 if (preg_match('/warehouseopen/', $option)) {
5704 }
5705 if (preg_match('/warehouseinternal/', $option)) {
5706 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
5708 } else {
5710 }
5711 }
5712
5713 $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
5714 $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
5715 $sql .= ", ".$this->db->prefix()."entrepot as w";
5716 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
5717 $sql .= " AND w.rowid = ps.fk_entrepot";
5718 $sql .= " AND ps.fk_product = ".((int) $this->id);
5719 if (count($warehouseStatus)) {
5720 $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
5721 }
5722
5723 $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;
5724
5725 dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
5726 $result = $this->db->query($sql);
5727 if ($result) {
5728 $num = $this->db->num_rows($result);
5729 $i = 0;
5730 if ($num > 0) {
5731 while ($i < $num) {
5732 $row = $this->db->fetch_object($result);
5733 $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
5734 $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
5735 $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
5736 if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
5737 $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
5738 }
5739 $this->stock_reel += $row->reel;
5740 $i++;
5741 }
5742 }
5743 $this->db->free($result);
5744
5745 if (!preg_match('/novirtual/', $option)) {
5746 $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
5747 }
5748
5749 return 1;
5750 } else {
5751 $this->error = $this->db->lasterror();
5752 return -1;
5753 }
5754 }
5755
5756
5757 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5767 public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
5768 {
5769 // phpcs:enable
5770 global $conf, $hookmanager, $action;
5771
5772 $stock_commande_client = 0;
5773 $stock_commande_fournisseur = 0;
5774 $stock_sending_client = 0;
5775 $stock_reception_fournisseur = 0;
5776 $stock_inproduction = 0;
5777
5778 //dol_syslog("load_virtual_stock");
5779
5780 if (isModEnabled('commande')) {
5781 $result = $this->load_stats_commande(0, '1,2', 1);
5782 if ($result < 0) {
5783 dol_print_error($this->db, $this->error);
5784 }
5785 $stock_commande_client = $this->stats_commande['qty'];
5786 }
5787 if (isModEnabled("expedition")) {
5788 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
5789 $filterShipmentStatus = '';
5790 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
5791 $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
5792 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
5793 $filterShipmentStatus = Expedition::STATUS_CLOSED;
5794 }
5795 $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
5796 if ($result < 0) {
5797 dol_print_error($this->db, $this->error);
5798 }
5799 $stock_sending_client = $this->stats_expedition['qty'];
5800 }
5801 if (isModEnabled("supplier_order")) {
5802 $filterStatus = !getDolGlobalString('SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK') ? '3,4' : $conf->global->SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK;
5803 if (isset($includedraftpoforvirtual)) {
5804 $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
5805 }
5806 $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
5807 if ($result < 0) {
5808 dol_print_error($this->db, $this->error);
5809 }
5810 $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
5811 }
5812 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && empty($conf->reception->enabled)) {
5813 // Case module reception is not used
5814 $filterStatus = '4';
5815 if (isset($includedraftpoforvirtual)) {
5816 $filterStatus = '0,'.$filterStatus;
5817 }
5818 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
5819 if ($result < 0) {
5820 dol_print_error($this->db, $this->error);
5821 }
5822 $stock_reception_fournisseur = $this->stats_reception['qty'];
5823 }
5824 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && isModEnabled("reception")) {
5825 // Case module reception is used
5826 $filterStatus = '4';
5827 if (isset($includedraftpoforvirtual)) {
5828 $filterStatus = '0,'.$filterStatus;
5829 }
5830 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock); // Use same tables than when module reception is not used.
5831 if ($result < 0) {
5832 dol_print_error($this->db, $this->error);
5833 }
5834 $stock_reception_fournisseur = $this->stats_reception['qty'];
5835 }
5836 if (isModEnabled('mrp')) {
5837 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
5838 if ($result < 0) {
5839 dol_print_error($this->db, $this->error);
5840 }
5841 $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
5842 }
5843
5844 $this->stock_theorique = $this->stock_reel + $stock_inproduction;
5845
5846 // $weBillOrderOrShipmentReception is set to 'order' or 'shipmentreception'. it will be used to know how to make virtual stock
5847 // calculation when we have a stock increase or decrease on billing. Do we have to count orders to bill or shipment/reception to bill ?
5848 $weBillOrderOrShipmentReception = getDolGlobalString('STOCK_DO_WE_BILL_ORDER_OR_SHIPMENTECEPTION_FOR_VIRTUALSTOCK', 'order');
5849
5850 // Stock decrease mode
5851 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
5852 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
5853 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER')) {
5854 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
5855 $tmpnewprod = dol_clone($this, 1);
5856 $result = $tmpnewprod->load_stats_commande(0, '0', 1); // Get qty in draft orders
5857 $this->stock_theorique += $tmpnewprod->stats_commande['qty'];
5858 }
5859 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'order') {
5860 $this->stock_theorique -= $stock_commande_client;
5861 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
5862 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
5863 }
5864
5865 // Stock Increase mode
5866 if (getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION') || getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
5867 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5868 } 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
5869 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5870 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) { // Warning: stock change "on approval", not on validation !
5871 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
5872 $tmpnewprod = dol_clone($this, 1);
5873 $result = $tmpnewprod->load_stats_commande_fournisseur(0, '0', 1); // Get qty in draft orders
5874 $this->stock_theorique += $this->stats_commande_fournisseur['qty'];
5875 }
5876 $this->stock_theorique -= $stock_reception_fournisseur;
5877 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'order') {
5878 $this->stock_theorique += $stock_commande_fournisseur;
5879 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
5880 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5881 }
5882
5883 $parameters = array('id'=>$this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
5884 // Note that $action and $object may have been modified by some hooks
5885 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
5886 if ($reshook > 0) {
5887 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
5888 } elseif ($reshook == 0 && isset($hookmanager->resArray['stock_stats_hook'])) {
5889 $this->stock_theorique += $hookmanager->resArray['stock_stats_hook'];
5890 }
5891
5892 return 1;
5893 }
5894
5895
5903 public function loadBatchInfo($batch)
5904 {
5905 $result = array();
5906
5907 $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";
5908 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
5909 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
5910 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
5911 $resql = $this->db->query($sql);
5912 if ($resql) {
5913 $num = $this->db->num_rows($resql);
5914 $i = 0;
5915 while ($i < $num) {
5916 $obj = $this->db->fetch_object($resql);
5917 $result[] = array('batch'=>$batch, 'eatby'=>$this->db->jdate($obj->eatby), 'sellby'=>$this->db->jdate($obj->sellby), 'qty'=>$obj->qty);
5918 $i++;
5919 }
5920 return $result;
5921 } else {
5922 dol_print_error($this->db);
5923 $this->db->rollback();
5924 return array();
5925 }
5926 }
5927
5928 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5936 public function add_photo($sdir, $file)
5937 {
5938 // phpcs:enable
5939 global $conf;
5940
5941 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5942
5943 $result = 0;
5944
5945 $dir = $sdir;
5946 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5947 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
5948 } else {
5949 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
5950 }
5951
5952 dol_mkdir($dir);
5953
5954 $dir_osencoded = $dir;
5955
5956 if (is_dir($dir_osencoded)) {
5957 $originImage = $dir.'/'.$file['name'];
5958
5959 // Cree fichier en taille origine
5960 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
5961
5962 if (file_exists(dol_osencode($originImage))) {
5963 // Create thumbs
5964 $this->addThumbs($originImage);
5965 }
5966 }
5967
5968 if (is_numeric($result) && $result > 0) {
5969 return 1;
5970 } else {
5971 return -1;
5972 }
5973 }
5974
5975 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5982 public function is_photo_available($sdir)
5983 {
5984 // phpcs:enable
5985 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5986 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5987
5988 global $conf;
5989
5990 $dir = $sdir;
5991 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5992 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
5993 } else {
5994 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
5995 }
5996
5997 $nbphoto = 0;
5998
5999 $dir_osencoded = dol_osencode($dir);
6000 if (file_exists($dir_osencoded)) {
6001 $handle = opendir($dir_osencoded);
6002 if (is_resource($handle)) {
6003 while (($file = readdir($handle)) !== false) {
6004 if (!utf8_check($file)) {
6005 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
6006 }
6007 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6008 return true;
6009 }
6010 }
6011 }
6012 }
6013 return false;
6014 }
6015
6016 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6024 public function liste_photos($dir, $nbmax = 0)
6025 {
6026 // phpcs:enable
6027 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6028 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6029
6030 $nbphoto = 0;
6031 $tabobj = array();
6032
6033 $dir_osencoded = dol_osencode($dir);
6034 $handle = @opendir($dir_osencoded);
6035 if (is_resource($handle)) {
6036 while (($file = readdir($handle)) !== false) {
6037 if (!utf8_check($file)) {
6038 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // readdir returns ISO
6039 }
6040 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6041 $nbphoto++;
6042
6043 // We forge name of thumb.
6044 $photo = $file;
6045 $photo_vignette = '';
6046 $regs = array();
6047 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
6048 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
6049 }
6050
6051 $dirthumb = $dir.'thumbs/';
6052
6053 // Objet
6054 $obj = array();
6055 $obj['photo'] = $photo;
6056 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
6057 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
6058 } else {
6059 $obj['photo_vignette'] = "";
6060 }
6061
6062 $tabobj[$nbphoto - 1] = $obj;
6063
6064 // Do we have to continue with next photo ?
6065 if ($nbmax && $nbphoto >= $nbmax) {
6066 break;
6067 }
6068 }
6069 }
6070
6071 closedir($handle);
6072 }
6073
6074 return $tabobj;
6075 }
6076
6077 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6084 public function delete_photo($file)
6085 {
6086 // phpcs:enable
6087 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6088 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6089
6090 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
6091 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
6092 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
6093
6094 // On efface l'image d'origine
6095 dol_delete_file($file, 0, 0, 0, $this); // For triggers
6096
6097 // Si elle existe, on efface la vignette
6098 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
6099 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
6100 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6101 dol_delete_file($dirthumb.$photo_vignette);
6102 }
6103
6104 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
6105 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6106 dol_delete_file($dirthumb.$photo_vignette);
6107 }
6108 }
6109 }
6110
6111 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6118 public function get_image_size($file)
6119 {
6120 // phpcs:enable
6121 $file_osencoded = dol_osencode($file);
6122 $infoImg = getimagesize($file_osencoded); // Get information on image
6123 $this->imgWidth = $infoImg[0]; // Largeur de l'image
6124 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
6125 }
6126
6127 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6133 public function load_state_board()
6134 {
6135 // phpcs:enable
6136 global $hookmanager;
6137
6138 $this->nb = array();
6139
6140 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6141 $sql .= " FROM ".$this->db->prefix()."product as p";
6142 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6143 // Add where from hooks
6144 if (is_object($hookmanager)) {
6145 $parameters = array();
6146 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6147 $sql .= $hookmanager->resPrint;
6148 }
6149 $sql .= ' GROUP BY fk_product_type';
6150
6151 $resql = $this->db->query($sql);
6152 if ($resql) {
6153 while ($obj = $this->db->fetch_object($resql)) {
6154 if ($obj->fk_product_type == 1) {
6155 $this->nb["services"] = $obj->nb;
6156 } else {
6157 $this->nb["products"] = $obj->nb;
6158 }
6159 }
6160 $this->db->free($resql);
6161 return 1;
6162 } else {
6163 dol_print_error($this->db);
6164 $this->error = $this->db->error();
6165 return -1;
6166 }
6167 }
6168
6174 public function isProduct()
6175 {
6176 return ($this->type == Product::TYPE_PRODUCT ? true : false);
6177 }
6178
6184 public function isService()
6185 {
6186 return ($this->type == Product::TYPE_SERVICE ? true : false);
6187 }
6188
6194 public function isStockManaged()
6195 {
6196 return ($this->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'));
6197 }
6198
6204 public function isMandatoryPeriod()
6205 {
6206 return ($this->mandatory_period == 1 ? true : false);
6207 }
6208
6214 public function hasbatch()
6215 {
6216 return ($this->status_batch > 0 ? true : false);
6217 }
6218
6219
6220 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6229 public function get_barcode($object, $type = '')
6230 {
6231 // phpcs:enable
6232 global $conf;
6233
6234 $result = '';
6235 if (getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
6236 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6237 foreach ($dirsociete as $dirroot) {
6238 $res = dol_include_once($dirroot . getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM').'.php');
6239 if ($res) {
6240 break;
6241 }
6242 }
6243 $var = $conf->global->BARCODE_PRODUCT_ADDON_NUM;
6244 $mod = new $var();
6245
6246 $result = $mod->getNextValue($object, $type);
6247
6248 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6249 }
6250 return $result;
6251 }
6252
6260 public function initAsSpecimen()
6261 {
6262 $now = dol_now();
6263
6264 // Initialize parameters
6265 $this->specimen = 1;
6266 $this->id = 0;
6267 $this->ref = 'PRODUCT_SPEC';
6268 $this->label = 'PRODUCT SPECIMEN';
6269 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
6270 $this->specimen = 1;
6271 $this->country_id = 1;
6272 $this->status = 1;
6273 $this->status_buy = 1;
6274 $this->tobatch = 0;
6275 $this->note_private = 'This is a comment (private)';
6276 $this->note_public = 'This is a comment (public)';
6277 $this->date_creation = $now;
6278 $this->date_modification = $now;
6279
6280 $this->weight = 4;
6281 $this->weight_units = 3;
6282
6283 $this->length = 5;
6284 $this->length_units = 1;
6285 $this->width = 6;
6286 $this->width_units = 0;
6287 $this->height = null;
6288 $this->height_units = null;
6289
6290 $this->surface = 30;
6291 $this->surface_units = 0;
6292 $this->volume = 300;
6293 $this->volume_units = 0;
6294
6295 $this->barcode = -1; // Create barcode automatically
6296 }
6297
6304 public function getLabelOfUnit($type = 'long')
6305 {
6306 global $langs;
6307
6308 if (!$this->fk_unit) {
6309 return '';
6310 }
6311
6312 $langs->load('products');
6313
6314 $label_type = 'label';
6315 if ($type == 'short') {
6316 $label_type = 'short_label';
6317 }
6318
6319 $sql = "SELECT ".$label_type.", code from ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
6320
6321 $resql = $this->db->query($sql);
6322 if ($resql && $this->db->num_rows($resql) > 0) {
6323 $res = $this->db->fetch_array($resql);
6324 $label = ($label_type == 'short_label' ? $res[$label_type] : 'unit'.$res['code']);
6325 $this->db->free($resql);
6326 return $label;
6327 } else {
6328 $this->error = $this->db->error();
6329 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
6330 return -1;
6331 }
6332 }
6333
6334 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6340 public function min_recommended_price()
6341 {
6342 // phpcs:enable
6343 global $conf;
6344
6345 $maxpricesupplier = 0;
6346
6347 if (getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE')) {
6348 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6349 $product_fourn = new ProductFournisseur($this->db);
6350 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
6351
6352 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
6353 foreach ($product_fourn_list as $productfourn) {
6354 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
6355 $maxpricesupplier = $productfourn->fourn_unitprice;
6356 }
6357 }
6358
6359 $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
6360 }
6361 }
6362
6363 return $maxpricesupplier;
6364 }
6365
6366
6377 public function setCategories($categories)
6378 {
6379 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
6380 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
6381 }
6382
6391 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
6392 {
6393 $tables = array(
6394 'product_customer_price',
6395 'product_customer_price_log'
6396 );
6397
6398 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
6399 }
6400
6412 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
6413 {
6414 global $conf;
6415
6416 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
6417 $query = $this->db->query($sql);
6418
6419 $rules = array();
6420
6421 while ($result = $this->db->fetch_object($query)) {
6422 $rules[$result->level] = $result;
6423 }
6424
6425 //Because prices can be based on other level's prices, we temporarily store them
6426 $prices = array(
6427 1 => $baseprice
6428 );
6429
6430 $nbofproducts = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
6431 for ($i = 1; $i <= $nbofproducts; $i++) {
6432 $price = $baseprice;
6433 $price_min = $baseprice;
6434
6435 //We have to make sure it does exist and it is > 0
6436 //First price level only allows changing min_price
6437 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
6438 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
6439 }
6440
6441 $prices[$i] = $price;
6442
6443 //We have to make sure it does exist and it is > 0
6444 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
6445 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
6446 }
6447
6448 //Little check to make sure the price is modified before triggering generation
6449 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
6450 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
6451
6452 if ($check_amount && $check_type) {
6453 continue;
6454 }
6455
6456 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
6457 return -1;
6458 }
6459 }
6460
6461 return 1;
6462 }
6463
6469 public function getRights()
6470 {
6471 global $user;
6472
6473 if ($this->isProduct()) {
6474 return $user->rights->produit;
6475 } else {
6476 return $user->rights->service;
6477 }
6478 }
6479
6486 public function info($id)
6487 {
6488 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
6489 $sql .= " p.fk_user_author, p.fk_user_modif";
6490 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
6491 $sql .= " WHERE p.rowid = ".((int) $id);
6492
6493 $result = $this->db->query($sql);
6494 if ($result) {
6495 if ($this->db->num_rows($result)) {
6496 $obj = $this->db->fetch_object($result);
6497
6498 $this->id = $obj->rowid;
6499 $this->ref = $obj->ref;
6500
6501 $this->user_creation_id = $obj->fk_user_author;
6502 $this->user_modification_id = $obj->fk_user_modif;
6503
6504 $this->date_creation = $this->db->jdate($obj->date_creation);
6505 $this->date_modification = $this->db->jdate($obj->date_modification);
6506 }
6507
6508 $this->db->free($result);
6509 } else {
6510 dol_print_error($this->db);
6511 }
6512 }
6513
6514
6519 public function getProductDurationHours()
6520 {
6521 global $langs;
6522
6523 if (empty($this->duration_value)) {
6524 $this->errors[]='ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
6525 return -1;
6526 }
6527
6528 if ($this->duration_unit == 'i') {
6529 $prodDurationHours = 1. / 60;
6530 }
6531 if ($this->duration_unit == 'h') {
6532 $prodDurationHours = 1.;
6533 }
6534 if ($this->duration_unit == 'd') {
6535 $prodDurationHours = 24.;
6536 }
6537 if ($this->duration_unit == 'w') {
6538 $prodDurationHours = 24. * 7;
6539 }
6540 if ($this->duration_unit == 'm') {
6541 $prodDurationHours = 24. * 30;
6542 }
6543 if ($this->duration_unit == 'y') {
6544 $prodDurationHours = 24. * 365;
6545 }
6546 $prodDurationHours *= $this->duration_value;
6547
6548 return $prodDurationHours;
6549 }
6550
6551
6559 public function getKanbanView($option = '', $arraydata = null)
6560 {
6561 global $langs,$conf;
6562
6563 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6564
6565 $return = '<div class="box-flex-item box-flex-grow-zero">';
6566 $return .= '<div class="info-box info-box-sm">';
6567 $return .= '<div class="info-box-img">';
6568 $label = '';
6569 if ($this->is_photo_available($conf->product->multidir_output[$this->entity])) {
6570 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
6571 $return .= $label;
6572 } else {
6573 if ($this->type == Product::TYPE_PRODUCT) {
6574 $label .= img_picto('', 'product');
6575 } elseif ($this->type == Product::TYPE_SERVICE) {
6576 $label .= img_picto('', 'service');
6577 }
6578 $return .= $label;
6579 }
6580 $return .= '</div>';
6581 $return .= '<div class="info-box-content">';
6582 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
6583 if ($selected >= 0) {
6584 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
6585 }
6586 if (property_exists($this, 'label')) {
6587 $return .= '<br><span class="info-box-label opacitymedium">'.$this->label.'</span>';
6588 }
6589 if (property_exists($this, 'price') && property_exists($this, 'price_ttc')) {
6590 if ($this->price_base_type == 'TTC') {
6591 $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
6592 } else {
6593 if ($this->status) {
6594 $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
6595 }
6596 }
6597 }
6598 $br = 1;
6599 if (property_exists($this, 'stock_reel') && $this->isProduct()) {
6600 $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>';
6601 $br = 0;
6602 }
6603 if (method_exists($this, 'getLibStatut')) {
6604 if ($br) {
6605 $return .= '<br><div class="info-box-status inline-block valignmiddle">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6606 } else {
6607 $return .= '<div class="info-box-status inline-block valignmiddle marginleftonly paddingleft">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6608 }
6609 }
6610 $return .= '</div>';
6611 $return .= '</div>';
6612 $return .= '</div>';
6613 return $return;
6614 }
6615}
6616
6622{
6623 public $picto = 'service';
6624}
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