dolibarr 18.0.6
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-2018 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 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 3 of the License, or
22 * (at your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <https://www.gnu.org/licenses/>.
31 */
32
38require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
39require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
40require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
41require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
42
46class Product extends CommonObject
47{
51 public $element = 'product';
52
56 public $table_element = 'product';
57
61 public $fk_element = 'fk_product';
62
66 protected $childtables = array(
67 'supplier_proposaldet' => array('name' => 'SupplierProposal', 'parent' => 'supplier_proposal', 'parentkey' => 'fk_supplier_proposal'),
68 'propaldet' => array('name' => 'Proposal', 'parent' => 'propal', 'parentkey' => 'fk_propal'),
69 'commandedet' => array('name' => 'Order', 'parent' => 'commande', 'parentkey' => 'fk_commande'),
70 'facturedet' => array('name' => 'Invoice', 'parent' => 'facture', 'parentkey' => 'fk_facture'),
71 'contratdet' => array('name' => 'Contract', 'parent' => 'contrat', 'parentkey' => 'fk_contrat'),
72 'facture_fourn_det' => array('name' => 'SupplierInvoice', 'parent' => 'facture_fourn', 'parentkey' => 'fk_facture_fourn'),
73 'commande_fournisseurdet' => array('name' => 'SupplierOrder', 'parent' => 'commande_fournisseur', 'parentkey' => 'fk_commande'),
74 'mrp_production' => array('name' => 'Mo', 'parent' => 'mrp_mo', 'parentkey' => 'fk_mo' ),
75 'bom_bom' => array('name' => 'BOM'),
76 'bom_bomline' => array('name' => 'BOMLine', 'parent' => 'bom_bom', 'parentkey' => 'fk_bom'),
77 );
78
84 public $ismultientitymanaged = 1;
85
89 public $isextrafieldmanaged = 1;
90
94 public $picto = 'product';
95
99 protected $table_ref_field = 'ref';
100
101 public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.xpm|\.xbm'; // See also into images.lib.php
102
107 public $libelle;
108
114 public $label;
115
121 public $description;
122
128 public $other;
129
135 public $type = self::TYPE_PRODUCT;
136
142 public $price;
143
144 public $price_formated; // used by takepos/ajax/ajax.php
145
151 public $price_ttc;
152
153 public $price_ttc_formated; // used by takepos/ajax/ajax.php
154
160 public $price_min;
161
167 public $price_min_ttc;
168
173 public $price_base_type;
174
176 public $multiprices = array();
177 public $multiprices_ttc = array();
178 public $multiprices_base_type = array();
179 public $multiprices_min = array();
180 public $multiprices_min_ttc = array();
181 public $multiprices_tva_tx = array();
182 public $multiprices_recuperableonly = array();
183
186 public $prices_by_qty = array();
187 public $prices_by_qty_id = array();
188 public $prices_by_qty_list = array();
189
191 public $multilangs = array();
192
195
197 public $tva_tx;
198
202 public $tva_npr = 0;
207
210
213 public $localtax2_tx;
214 public $localtax1_type;
215 public $localtax2_type;
216
217 // Properties set by get_buyprice() for return
218
219 public $desc_supplier;
220 public $vatrate_supplier;
221 public $default_vat_code_supplier;
222 public $fourn_multicurrency_price;
223 public $fourn_multicurrency_unitprice;
224 public $fourn_multicurrency_tx;
225 public $fourn_multicurrency_id;
226 public $fourn_multicurrency_code;
227 public $packaging;
228
229
230 public $lifetime;
231
232 public $qc_frequency;
233
239 public $stock_reel = 0;
240
246 public $stock_theorique;
247
253 public $cost_price;
254
256 public $pmp;
257
263 public $seuil_stock_alerte = 0;
264
268 public $desiredstock = 0;
269
281 public $duration;
282
287
293 public $status = 0;
294
301 public $tosell;
302
308 public $status_buy = 0;
309
316 public $tobuy;
317
323 public $finished;
324
330 public $fk_default_bom;
331
337 public $status_batch = 0;
338
344 public $batch_mask = '';
345
351 public $customcode;
352
358 public $url;
359
361 public $weight;
362 public $weight_units; // scale -3, 0, 3, 6
363 public $length;
364 public $length_units; // scale -3, 0, 3, 6
365 public $width;
366 public $width_units; // scale -3, 0, 3, 6
367 public $height;
368 public $height_units; // scale -3, 0, 3, 6
369 public $surface;
370 public $surface_units; // scale -3, 0, 3, 6
371 public $volume;
372 public $volume_units; // scale -3, 0, 3, 6
373
374 public $net_measure;
375 public $net_measure_units; // scale -3, 0, 3, 6
376
377 public $accountancy_code_sell;
378 public $accountancy_code_sell_intra;
379 public $accountancy_code_sell_export;
380 public $accountancy_code_buy;
381 public $accountancy_code_buy_intra;
382 public $accountancy_code_buy_export;
383
389 public $barcode;
390
396 public $barcode_type;
397
403 public $barcode_type_code;
404
405 public $stats_propale = array();
406 public $stats_commande = array();
407 public $stats_contrat = array();
408 public $stats_facture = array();
409 public $stats_proposal_supplier = array();
410 public $stats_commande_fournisseur = array();
411 public $stats_expedition = array();
412 public $stats_reception = array();
413 public $stats_mo = array();
414 public $stats_bom = array();
415 public $stats_mrptoconsume = array();
416 public $stats_mrptoproduce = array();
417 public $stats_facturerec = array();
418 public $stats_facture_fournisseur = array();
419
421 public $imgWidth;
422 public $imgHeight;
423
427 public $date_creation;
428
432 public $date_modification;
433
436
439
440 public $nbphoto = 0;
441
443 public $stock_warehouse = array();
444
445 public $oldcopy;
446
450 public $fk_default_warehouse;
454 public $fk_price_expression;
455
456 /* To store supplier price found */
457 public $fourn_qty;
458 public $fourn_pu;
459 public $fourn_price_base_type;
460 public $fourn_socid;
461
467
471 public $ref_supplier;
472
478 public $fk_unit;
479
485 public $price_autogen = 0;
486
492 public $supplierprices;
493
499 public $sousprods;
500
504 public $res;
505
506
512 public $is_object_used;
513
514
518 public $mandatory_period;
519
548 public $fields = array(
549 'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'index'=>1, 'position'=>1, 'comment'=>'Id'),
550 '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'),
551 'entity' =>array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'default'=>1, 'notnull'=>1, 'index'=>1, 'position'=>5),
552 'label' =>array('type'=>'varchar(255)', 'label'=>'Label', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>2, 'position'=>15, 'csslist'=>'tdoverflowmax250'),
553 'barcode' =>array('type'=>'varchar(255)', 'label'=>'Barcode', 'enabled'=>'isModEnabled("barcode")', 'position'=>20, 'visible'=>-1, 'showoncombobox'=>3),
554 'fk_barcode_type' => array('type'=>'integer', 'label'=>'BarcodeType', 'enabled'=>'1', 'position'=>21, 'notnull'=>0, 'visible'=>-1,),
555 'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>61),
556 'note' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>62),
557 'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>500),
558 'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>501),
559 //'date_valid' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>502),
560 'fk_user_author'=>array('type'=>'integer', 'label'=>'UserAuthor', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>510, 'foreignkey'=>'llx_user.rowid'),
561 'fk_user_modif' =>array('type'=>'integer', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>511),
562 //'fk_user_valid' =>array('type'=>'integer', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>512),
563 'localtax1_tx' => array('type'=>'double(6,3)', 'label'=>'Localtax1tx', 'enabled'=>'1', 'position'=>150, 'notnull'=>0, 'visible'=>-1,),
564 'localtax1_type' => array('type'=>'varchar(10)', 'label'=>'Localtax1type', 'enabled'=>'1', 'position'=>155, 'notnull'=>1, 'visible'=>-1,),
565 'localtax2_tx' => array('type'=>'double(6,3)', 'label'=>'Localtax2tx', 'enabled'=>'1', 'position'=>160, 'notnull'=>0, 'visible'=>-1,),
566 'localtax2_type' => array('type'=>'varchar(10)', 'label'=>'Localtax2type', 'enabled'=>'1', 'position'=>165, 'notnull'=>1, 'visible'=>-1,),
567 'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'index'=>0, 'position'=>1000),
568 //'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')),
569 //'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')),
570 'mandatory_period' => array('type'=>'integer', 'label'=>'mandatoryperiod', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>0, 'index'=>1, 'position'=>1000),
571 );
572
576 const TYPE_PRODUCT = 0;
580 const TYPE_SERVICE = 1;
588 const TYPE_STOCKKIT = 3;
589
590
596 public function __construct($db)
597 {
598 $this->db = $db;
599 $this->canvas = '';
600 }
601
607 public function check()
608 {
609 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
610 $this->ref = trim($this->ref);
611 } else {
612 $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
613 }
614
615 $err = 0;
616 if (dol_strlen(trim($this->ref)) == 0) {
617 $err++;
618 }
619
620 if (dol_strlen(trim($this->label)) == 0) {
621 $err++;
622 }
623
624 if ($err > 0) {
625 return 0;
626 } else {
627 return 1;
628 }
629 }
630
638 public function create($user, $notrigger = 0)
639 {
640 global $conf, $langs;
641
642 $error = 0;
643
644 // Clean parameters
645 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
646 $this->ref = trim($this->ref);
647 } else {
648 $this->ref = dol_sanitizeFileName(dol_string_nospecial(trim($this->ref)));
649 }
650 $this->label = trim($this->label);
651 $this->price_ttc = price2num($this->price_ttc);
652 $this->price = price2num($this->price);
653 $this->price_min_ttc = price2num($this->price_min_ttc);
654 $this->price_min = price2num($this->price_min);
655 if (empty($this->tva_tx)) {
656 $this->tva_tx = 0;
657 }
658 if (empty($this->tva_npr)) {
659 $this->tva_npr = 0;
660 }
661 //Local taxes
662 if (empty($this->localtax1_tx)) {
663 $this->localtax1_tx = 0;
664 }
665 if (empty($this->localtax2_tx)) {
666 $this->localtax2_tx = 0;
667 }
668 if (empty($this->localtax1_type)) {
669 $this->localtax1_type = '0';
670 }
671 if (empty($this->localtax2_type)) {
672 $this->localtax2_type = '0';
673 }
674 if (empty($this->price)) {
675 $this->price = 0;
676 }
677 if (empty($this->price_min)) {
678 $this->price_min = 0;
679 }
680 // Price by quantity
681 if (empty($this->price_by_qty)) {
682 $this->price_by_qty = 0;
683 }
684
685 if (empty($this->status)) {
686 $this->status = 0;
687 }
688 if (empty($this->status_buy)) {
689 $this->status_buy = 0;
690 }
691
692 $price_ht = 0;
693 $price_ttc = 0;
694 $price_min_ht = 0;
695 $price_min_ttc = 0;
696
697 //
698 if ($this->price_base_type == 'TTC' && $this->price_ttc > 0) {
699 $price_ttc = price2num($this->price_ttc, 'MU');
700 $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)), 'MU');
701 }
702
703 //
704 if ($this->price_base_type != 'TTC' && $this->price > 0) {
705 $price_ht = price2num($this->price, 'MU');
706 $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)), 'MU');
707 }
708
709 //
710 if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC')) {
711 $price_min_ttc = price2num($this->price_min_ttc, 'MU');
712 $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)), 'MU');
713 }
714
715 //
716 if (($this->price_min > 0) && ($this->price_base_type != 'TTC')) {
717 $price_min_ht = price2num($this->price_min, 'MU');
718 $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)), 'MU');
719 }
720
721 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
722 $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
723 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
724 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
725 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
726 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
727
728 // Barcode value
729 $this->barcode = trim($this->barcode);
730 $this->mandatory_period = empty($this->mandatory_period) ? 0 : $this->mandatory_period;
731 // Check parameters
732 if (empty($this->label)) {
733 $this->error = 'ErrorMandatoryParametersNotProvided';
734 return -1;
735 }
736
737 if (empty($this->ref) || $this->ref == 'auto') {
738 // Load object modCodeProduct
739 $module = getDolGlobalString('PRODUCT_CODEPRODUCT_ADDON', 'mod_codeproduct_leopard');
740 if ($module != 'mod_codeproduct_leopard') { // Do not load module file for leopard
741 if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
742 $module = substr($module, 0, dol_strlen($module) - 4);
743 }
744 dol_include_once('/core/modules/product/'.$module.'.php');
745 $modCodeProduct = new $module;
746 if (!empty($modCodeProduct->code_auto)) {
747 $this->ref = $modCodeProduct->getNextValue($this, $this->type);
748 }
749 unset($modCodeProduct);
750 }
751
752 if (empty($this->ref)) {
753 $this->error = 'ProductModuleNotSetupForAutoRef';
754 return -2;
755 }
756 }
757
758 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);
759
760 $now = dol_now();
761
762 $this->db->begin();
763
764 // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
765 if ($this->barcode == -1) {
766 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
767 }
768
769 // Check more parameters
770 // If error, this->errors[] is filled
771 $result = $this->verify();
772
773 if ($result >= 0) {
774 $sql = "SELECT count(*) as nb";
775 $sql .= " FROM ".$this->db->prefix()."product";
776 $sql .= " WHERE entity IN (".getEntity('product').")";
777 $sql .= " AND ref = '".$this->db->escape($this->ref)."'";
778
779 $result = $this->db->query($sql);
780 if ($result) {
781 $obj = $this->db->fetch_object($result);
782 if ($obj->nb == 0) {
783 // Produit non deja existant
784 $sql = "INSERT INTO ".$this->db->prefix()."product (";
785 $sql .= "datec";
786 $sql .= ", entity";
787 $sql .= ", ref";
788 $sql .= ", ref_ext";
789 $sql .= ", price_min";
790 $sql .= ", price_min_ttc";
791 $sql .= ", label";
792 $sql .= ", fk_user_author";
793 $sql .= ", fk_product_type";
794 $sql .= ", price";
795 $sql .= ", price_ttc";
796 $sql .= ", price_base_type";
797 $sql .= ", tobuy";
798 $sql .= ", tosell";
799 if (empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
800 $sql .= ", accountancy_code_buy";
801 $sql .= ", accountancy_code_buy_intra";
802 $sql .= ", accountancy_code_buy_export";
803 $sql .= ", accountancy_code_sell";
804 $sql .= ", accountancy_code_sell_intra";
805 $sql .= ", accountancy_code_sell_export";
806 }
807 $sql .= ", canvas";
808 $sql .= ", finished";
809 $sql .= ", tobatch";
810 $sql .= ", batch_mask";
811 $sql .= ", fk_unit";
812 $sql .= ", mandatory_period";
813 $sql .= ") VALUES (";
814 $sql .= "'".$this->db->idate($now)."'";
815 $sql .= ", ".(!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
816 $sql .= ", '".$this->db->escape($this->ref)."'";
817 $sql .= ", ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
818 $sql .= ", ".price2num($price_min_ht);
819 $sql .= ", ".price2num($price_min_ttc);
820 $sql .= ", ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
821 $sql .= ", ".((int) $user->id);
822 $sql .= ", ".((int) $this->type);
823 $sql .= ", ".price2num($price_ht, 'MT');
824 $sql .= ", ".price2num($price_ttc, 'MT');
825 $sql .= ", '".$this->db->escape($this->price_base_type)."'";
826 $sql .= ", ".((int) $this->status);
827 $sql .= ", ".((int) $this->status_buy);
828 if (empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
829 $sql .= ", '".$this->db->escape($this->accountancy_code_buy)."'";
830 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_intra)."'";
831 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_export)."'";
832 $sql .= ", '".$this->db->escape($this->accountancy_code_sell)."'";
833 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
834 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
835 }
836 $sql .= ", '".$this->db->escape($this->canvas)."'";
837 $sql .= ", ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'NULL' : (int) $this->finished);
838 $sql .= ", ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : ((int) $this->status_batch));
839 $sql .= ", '".$this->db->escape($this->batch_mask)."'";
840 $sql .= ", ".($this->fk_unit > 0 ? ((int) $this->fk_unit) : 'NULL');
841 $sql .= ", '".$this->db->escape($this->mandatory_period)."'";
842 $sql .= ")";
843
844 dol_syslog(get_class($this)."::Create", LOG_DEBUG);
845 $result = $this->db->query($sql);
846 if ($result) {
847 $id = $this->db->last_insert_id($this->db->prefix()."product");
848
849 if ($id > 0) {
850 $this->id = $id;
851 $this->price = $price_ht;
852 $this->price_ttc = $price_ttc;
853 $this->price_min = $price_min_ht;
854 $this->price_min_ttc = $price_min_ttc;
855
856 $result = $this->_log_price($user);
857 if ($result > 0) {
858 if ($this->update($id, $user, true, 'add') <= 0) {
859 $error++;
860 }
861 } else {
862 $error++;
863 $this->error = $this->db->lasterror();
864 }
865
866 // update accountancy for this entity
867 if (!$error && !empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
868 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " .((int) $this->id) . " AND entity = " . ((int) $conf->entity));
869
870 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
871 $sql .= " fk_product";
872 $sql .= ", entity";
873 $sql .= ", accountancy_code_buy";
874 $sql .= ", accountancy_code_buy_intra";
875 $sql .= ", accountancy_code_buy_export";
876 $sql .= ", accountancy_code_sell";
877 $sql .= ", accountancy_code_sell_intra";
878 $sql .= ", accountancy_code_sell_export";
879 $sql .= ") VALUES (";
880 $sql .= $this->id;
881 $sql .= ", " . $conf->entity;
882 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
883 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
884 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
885 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
886 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
887 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
888 $sql .= ")";
889 $result = $this->db->query($sql);
890 if (!$result) {
891 $error++;
892 $this->error = 'ErrorFailedToInsertAccountancyForEntity';
893 }
894 }
895 } else {
896 $error++;
897 $this->error = 'ErrorFailedToGetInsertedId';
898 }
899 } else {
900 $error++;
901 $this->error = $this->db->lasterror();
902 }
903 } else {
904 // Product already exists with this ref
905 $langs->load("products");
906 $error++;
907 $this->error = "ErrorProductAlreadyExists";
908 dol_syslog(get_class($this)."::Create fails, ref ".$this->ref." already exists");
909 }
910 } else {
911 $error++;
912 $this->error = $this->db->lasterror();
913 }
914
915 if (!$error && !$notrigger) {
916 // Call trigger
917 $result = $this->call_trigger('PRODUCT_CREATE', $user);
918 if ($result < 0) {
919 $error++;
920 }
921 // End call triggers
922 }
923
924 if (!$error) {
925 $this->db->commit();
926 return $this->id;
927 } else {
928 $this->db->rollback();
929 return -$error;
930 }
931 } else {
932 $this->db->rollback();
933 dol_syslog(get_class($this)."::Create fails verify ".join(',', $this->errors), LOG_WARNING);
934 return -3;
935 }
936 }
937
938
945 public function verify()
946 {
947 global $langs;
948
949 $this->errors = array();
950
951 $result = 0;
952 $this->ref = trim($this->ref);
953
954 if (!$this->ref) {
955 $this->errors[] = 'ErrorBadRef';
956 $result = -2;
957 }
958
959 $arrayofnonnegativevalue = array('weight'=>'Weight', 'width'=>'Width', 'height'=>'Height', 'length'=>'Length', 'surface'=>'Surface', 'volume'=>'Volume');
960 foreach ($arrayofnonnegativevalue as $key => $value) {
961 if (property_exists($this, $key) && !empty($this->$key) && ($this->$key < 0)) {
962 $langs->loadLangs(array("main", "other"));
963 $this->error = $langs->trans("FieldCannotBeNegative", $langs->transnoentitiesnoconv($value));
964 $this->errors[] = $this->error;
965 $result = -4;
966 }
967 }
968
969 $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
970 if ($rescode) {
971 if ($rescode == -1) {
972 $this->errors[] = 'ErrorBadBarCodeSyntax';
973 } elseif ($rescode == -2) {
974 $this->errors[] = 'ErrorBarCodeRequired';
975 } elseif ($rescode == -3) {
976 // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
977 $this->errors[] = 'ErrorBarCodeAlreadyUsed';
978 }
979
980 $result = -3;
981 }
982
983 return $result;
984 }
985
986 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
997 public function check_barcode($valuetotest, $typefortest)
998 {
999 // phpcs:enable
1000 global $conf;
1001 if (isModEnabled('barcode') && !empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
1002 $module = strtolower($conf->global->BARCODE_PRODUCT_ADDON_NUM);
1003
1004 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
1005 foreach ($dirsociete as $dirroot) {
1006 $res = dol_include_once($dirroot.$module.'.php');
1007 if ($res) {
1008 break;
1009 }
1010 }
1011
1012 $mod = new $module();
1013
1014 dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
1015 $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
1016 return $result;
1017 } else {
1018 return 0;
1019 }
1020 }
1021
1033 public function update($id, $user, $notrigger = false, $action = 'update', $updatetype = false)
1034 {
1035 global $langs, $conf, $hookmanager;
1036
1037 $error = 0;
1038
1039 // Check parameters
1040 if (!$this->label) {
1041 $this->label = 'MISSING LABEL';
1042 }
1043
1044 // Clean parameters
1045 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
1046 $this->ref = trim($this->ref);
1047 } else {
1048 $this->ref = dol_string_nospecial(trim($this->ref));
1049 }
1050 $this->label = trim($this->label);
1051 $this->description = trim($this->description);
1052 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : null);
1053 $this->note_public = (isset($this->note_public) ? trim($this->note_public) : null);
1054 $this->net_measure = price2num($this->net_measure);
1055 $this->net_measure_units = trim($this->net_measure_units);
1056 $this->weight = price2num($this->weight);
1057 $this->weight_units = trim($this->weight_units);
1058 $this->length = price2num($this->length);
1059 $this->length_units = trim($this->length_units);
1060 $this->width = price2num($this->width);
1061 $this->width_units = trim($this->width_units);
1062 $this->height = price2num($this->height);
1063 $this->height_units = trim($this->height_units);
1064 $this->surface = price2num($this->surface);
1065 $this->surface_units = trim($this->surface_units);
1066 $this->volume = price2num($this->volume);
1067 $this->volume_units = trim($this->volume_units);
1068
1069 // set unit not defined
1070 if (is_numeric($this->length_units)) {
1071 $this->width_units = $this->length_units; // Not used yet
1072 }
1073 if (is_numeric($this->length_units)) {
1074 $this->height_units = $this->length_units; // Not used yet
1075 }
1076
1077 // Automated compute surface and volume if not filled
1078 if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
1079 $this->surface = $this->length * $this->width;
1080 $this->surface_units = measuring_units_squared($this->length_units);
1081 }
1082 if (empty($this->volume) && !empty($this->surface) && !empty($this->height) && $this->length_units == $this->height_units) {
1083 $this->volume = $this->surface * $this->height;
1084 $this->volume_units = measuring_units_cubed($this->height_units);
1085 }
1086
1087 if (empty($this->tva_tx)) {
1088 $this->tva_tx = 0;
1089 }
1090 if (empty($this->tva_npr)) {
1091 $this->tva_npr = 0;
1092 }
1093 if (empty($this->localtax1_tx)) {
1094 $this->localtax1_tx = 0;
1095 }
1096 if (empty($this->localtax2_tx)) {
1097 $this->localtax2_tx = 0;
1098 }
1099 if (empty($this->localtax1_type)) {
1100 $this->localtax1_type = '0';
1101 }
1102 if (empty($this->localtax2_type)) {
1103 $this->localtax2_type = '0';
1104 }
1105 if (empty($this->status)) {
1106 $this->status = 0;
1107 }
1108 if (empty($this->status_buy)) {
1109 $this->status_buy = 0;
1110 }
1111
1112 if (empty($this->country_id)) {
1113 $this->country_id = 0;
1114 }
1115
1116 if (empty($this->state_id)) {
1117 $this->state_id = 0;
1118 }
1119
1120 // Barcode value
1121 $this->barcode = trim($this->barcode);
1122
1123 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1124 $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
1125 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1126 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1127 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1128 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1129
1130
1131 $this->db->begin();
1132
1133 $result = 0;
1134 // Check name is required and codes are ok or unique. If error, this->errors[] is filled
1135 if ($action != 'add') {
1136 $result = $this->verify(); // We don't check when update called during a create because verify was already done
1137 } else {
1138 // we can continue
1139 $result = 0;
1140 }
1141
1142 if ($result >= 0) {
1143 // $this->oldcopy should have been set by the caller of update (here properties were already modified)
1144 if (empty($this->oldcopy)) {
1145 $this->oldcopy = dol_clone($this);
1146 }
1147
1148 // Test if batch management is activated on existing product
1149 // If yes, we create missing entries into product_batch
1150 if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
1151 //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
1152 $valueforundefinedlot = '000000';
1153 if (!empty($conf->global->STOCK_DEFAULT_BATCH)) {
1154 $valueforundefinedlot = $conf->global->STOCK_DEFAULT_BATCH;
1155 }
1156
1157 dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
1158
1159 $this->load_stock();
1160 foreach ($this->stock_warehouse as $idW => $ObjW) { // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
1161 $qty_batch = 0;
1162 foreach ($ObjW->detail_batch as $detail) { // Each lines of detail in product_batch of the current $ObjW = product_stock
1163 if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
1164 // We discard this line, we will create it later
1165 $sqlclean = "DELETE FROM ".$this->db->prefix()."product_batch WHERE batch in('Undefined', '".$this->db->escape($valueforundefinedlot)."') AND fk_product_stock = ".((int) $ObjW->id);
1166 $result = $this->db->query($sqlclean);
1167 if (!$result) {
1168 dol_print_error($this->db);
1169 exit;
1170 }
1171 continue;
1172 }
1173
1174 $qty_batch += $detail->qty;
1175 }
1176 // Quantities in batch details are not same as stock quantity,
1177 // so we add a default batch record to complete and get same qty in parent and child table
1178 if ($ObjW->real <> $qty_batch) {
1179 $ObjBatch = new Productbatch($this->db);
1180 $ObjBatch->batch = $valueforundefinedlot;
1181 $ObjBatch->qty = ($ObjW->real - $qty_batch);
1182 $ObjBatch->fk_product_stock = $ObjW->id;
1183
1184 if ($ObjBatch->create($user, 1) < 0) {
1185 $error++;
1186 $this->errors = $ObjBatch->errors;
1187 }
1188 }
1189 }
1190 }
1191
1192 // For automatic creation
1193 if ($this->barcode == -1) {
1194 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1195 }
1196
1197 $sql = "UPDATE ".$this->db->prefix()."product";
1198 $sql .= " SET label = '".$this->db->escape($this->label)."'";
1199
1200 if ($updatetype && ($this->isProduct() || $this->isService())) {
1201 $sql .= ", fk_product_type = ".((int) $this->type);
1202 }
1203
1204 $sql .= ", ref = '".$this->db->escape($this->ref)."'";
1205 $sql .= ", ref_ext = ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1206 $sql .= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
1207 $sql .= ", tva_tx = ".((float) $this->tva_tx);
1208 $sql .= ", recuperableonly = ".((int) $this->tva_npr);
1209 $sql .= ", localtax1_tx = ".((float) $this->localtax1_tx);
1210 $sql .= ", localtax2_tx = ".((float) $this->localtax2_tx);
1211 $sql .= ", localtax1_type = ".($this->localtax1_type != '' ? "'".$this->db->escape($this->localtax1_type)."'" : "'0'");
1212 $sql .= ", localtax2_type = ".($this->localtax2_type != '' ? "'".$this->db->escape($this->localtax2_type)."'" : "'0'");
1213
1214 $sql .= ", barcode = ".(empty($this->barcode) ? "null" : "'".$this->db->escape($this->barcode)."'");
1215 $sql .= ", fk_barcode_type = ".(empty($this->barcode_type) ? "null" : $this->db->escape($this->barcode_type));
1216
1217 $sql .= ", tosell = ".(int) $this->status;
1218 $sql .= ", tobuy = ".(int) $this->status_buy;
1219 $sql .= ", tobatch = ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
1220 $sql .= ", batch_mask = '".$this->db->escape($this->batch_mask)."'";
1221
1222 $sql .= ", finished = ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? "null" : (int) $this->finished);
1223 $sql .= ", fk_default_bom = ".((!isset($this->fk_default_bom) || $this->fk_default_bom < 0 || $this->fk_default_bom == '') ? "null" : (int) $this->fk_default_bom);
1224 $sql .= ", net_measure = ".($this->net_measure != '' ? "'".$this->db->escape($this->net_measure)."'" : 'null');
1225 $sql .= ", net_measure_units = ".($this->net_measure_units != '' ? "'".$this->db->escape($this->net_measure_units)."'" : 'null');
1226 $sql .= ", weight = ".($this->weight != '' ? "'".$this->db->escape($this->weight)."'" : 'null');
1227 $sql .= ", weight_units = ".($this->weight_units != '' ? "'".$this->db->escape($this->weight_units)."'" : 'null');
1228 $sql .= ", length = ".($this->length != '' ? "'".$this->db->escape($this->length)."'" : 'null');
1229 $sql .= ", length_units = ".($this->length_units != '' ? "'".$this->db->escape($this->length_units)."'" : 'null');
1230 $sql .= ", width= ".($this->width != '' ? "'".$this->db->escape($this->width)."'" : 'null');
1231 $sql .= ", width_units = ".($this->width_units != '' ? "'".$this->db->escape($this->width_units)."'" : 'null');
1232 $sql .= ", height = ".($this->height != '' ? "'".$this->db->escape($this->height)."'" : 'null');
1233 $sql .= ", height_units = ".($this->height_units != '' ? "'".$this->db->escape($this->height_units)."'" : 'null');
1234 $sql .= ", surface = ".($this->surface != '' ? "'".$this->db->escape($this->surface)."'" : 'null');
1235 $sql .= ", surface_units = ".($this->surface_units != '' ? "'".$this->db->escape($this->surface_units)."'" : 'null');
1236 $sql .= ", volume = ".($this->volume != '' ? "'".$this->db->escape($this->volume)."'" : 'null');
1237 $sql .= ", volume_units = ".($this->volume_units != '' ? "'".$this->db->escape($this->volume_units)."'" : 'null');
1238 $sql .= ", fk_default_warehouse = ".($this->fk_default_warehouse > 0 ? ((int) $this->fk_default_warehouse) : 'null');
1239 $sql .= ", fk_default_workstation = ".($this->fk_default_workstation > 0 ? ((int) $this->fk_default_workstation) : 'null');
1240 $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (float) $this->seuil_stock_alerte : 'null');
1241 $sql .= ", description = '".$this->db->escape($this->description)."'";
1242 $sql .= ", url = ".($this->url ? "'".$this->db->escape($this->url)."'" : 'null');
1243 $sql .= ", customcode = '".$this->db->escape($this->customcode)."'";
1244 $sql .= ", fk_country = ".($this->country_id > 0 ? (int) $this->country_id : 'null');
1245 $sql .= ", fk_state = ".($this->state_id > 0 ? (int) $this->state_id : 'null');
1246 $sql .= ", lifetime = ".($this->lifetime > 0 ? (int) $this->lifetime : 'null');
1247 $sql .= ", qc_frequency = ".($this->qc_frequency > 0 ? (int) $this->qc_frequency : 'null');
1248 $sql .= ", note = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : 'null');
1249 $sql .= ", note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : 'null');
1250 $sql .= ", duration = '".$this->db->escape($this->duration_value.$this->duration_unit)."'";
1251 if (empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
1252 $sql .= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy) . "'";
1253 $sql .= ", accountancy_code_buy_intra = '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1254 $sql .= ", accountancy_code_buy_export = '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1255 $sql .= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell) . "'";
1256 $sql .= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1257 $sql .= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1258 }
1259 $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (float) $this->desiredstock : "null");
1260 $sql .= ", cost_price = ".($this->cost_price != '' ? $this->db->escape($this->cost_price) : 'null');
1261 $sql .= ", fk_unit= ".(!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
1262 $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1);
1263 $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
1264 $sql .= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL');
1265 $sql .= ", mandatory_period = ".($this->mandatory_period );
1266 // stock field is not here because it is a denormalized value from product_stock.
1267 $sql .= " WHERE rowid = ".((int) $id);
1268
1269 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1270
1271 $resql = $this->db->query($sql);
1272 if ($resql) {
1273 $this->id = $id;
1274
1275 // Multilangs
1276 if (getDolGlobalInt('MAIN_MULTILANGS')) {
1277 if ($this->setMultiLangs($user) < 0) {
1278 return -2;
1279 }
1280 }
1281
1282 $action = 'update';
1283
1284 // update accountancy for this entity
1285 if (!$error && !empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
1286 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " . ((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1287
1288 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1289 $sql .= " fk_product";
1290 $sql .= ", entity";
1291 $sql .= ", accountancy_code_buy";
1292 $sql .= ", accountancy_code_buy_intra";
1293 $sql .= ", accountancy_code_buy_export";
1294 $sql .= ", accountancy_code_sell";
1295 $sql .= ", accountancy_code_sell_intra";
1296 $sql .= ", accountancy_code_sell_export";
1297 $sql .= ") VALUES (";
1298 $sql .= $this->id;
1299 $sql .= ", " . $conf->entity;
1300 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1301 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1302 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1303 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1304 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1305 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1306 $sql .= ")";
1307 $result = $this->db->query($sql);
1308 if (!$result) {
1309 $error++;
1310 $this->error = 'ErrorFailedToUpdateAccountancyForEntity';
1311 }
1312 }
1313
1314 // Actions on extra fields
1315 if (!$error) {
1316 $result = $this->insertExtraFields();
1317 if ($result < 0) {
1318 $error++;
1319 }
1320 }
1321
1322 if (!$error && !$notrigger) {
1323 // Call trigger
1324 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1325 if ($result < 0) {
1326 $error++;
1327 }
1328 // End call triggers
1329 }
1330
1331 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
1332 // We remove directory
1333 if ($conf->product->dir_output) {
1334 $olddir = $conf->product->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
1335 $newdir = $conf->product->dir_output."/".dol_sanitizeFileName($this->ref);
1336 if (file_exists($olddir)) {
1337 //include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1338 //$res = dol_move($olddir, $newdir);
1339 // do not use dol_move with directory
1340 $res = @rename($olddir, $newdir);
1341 if (!$res) {
1342 $langs->load("errors");
1343 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
1344 $error++;
1345 }
1346 }
1347 }
1348 }
1349
1350 if (!$error) {
1351 if (isModEnabled('variants')) {
1352 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1353
1354 $comb = new ProductCombination($this->db);
1355
1356 foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1357 $currcomb->updateProperties($this, $user);
1358 }
1359 }
1360
1361 $this->db->commit();
1362 return 1;
1363 } else {
1364 $this->db->rollback();
1365 return -$error;
1366 }
1367 } else {
1368 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1369 $langs->load("errors");
1370 if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1371 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1372 } else {
1373 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1374 }
1375 $this->errors[] = $this->error;
1376 $this->db->rollback();
1377 return -1;
1378 } else {
1379 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1380 $this->errors[] = $this->error;
1381 $this->db->rollback();
1382 return -2;
1383 }
1384 }
1385 } else {
1386 $this->db->rollback();
1387 dol_syslog(get_class($this)."::Update fails verify ".join(',', $this->errors), LOG_WARNING);
1388 return -3;
1389 }
1390 }
1391
1399 public function delete(User $user, $notrigger = 0)
1400 {
1401 global $conf, $langs;
1402 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1403
1404 $error = 0;
1405
1406 // Check parameters
1407 if (empty($this->id)) {
1408 $this->error = "Object must be fetched before calling delete";
1409 return -1;
1410 }
1411 if (($this->type == Product::TYPE_PRODUCT && empty($user->rights->produit->supprimer)) || ($this->type == Product::TYPE_SERVICE && empty($user->rights->service->supprimer))) {
1412 $this->error = "ErrorForbidden";
1413 return 0;
1414 }
1415
1416 $objectisused = $this->isObjectUsed($this->id);
1417 if (empty($objectisused)) {
1418 $this->db->begin();
1419
1420 if (!$error && empty($notrigger)) {
1421 // Call trigger
1422 $result = $this->call_trigger('PRODUCT_DELETE', $user);
1423 if ($result < 0) {
1424 $error++;
1425 }
1426 // End call triggers
1427 }
1428
1429 // Delete from product_batch on product delete
1430 if (!$error) {
1431 $sql = "DELETE FROM ".$this->db->prefix().'product_batch';
1432 $sql .= " WHERE fk_product_stock IN (";
1433 $sql .= "SELECT rowid FROM ".$this->db->prefix().'product_stock';
1434 $sql .= " WHERE fk_product = ".((int) $this->id).")";
1435
1436 $result = $this->db->query($sql);
1437 if (!$result) {
1438 $error++;
1439 $this->errors[] = $this->db->lasterror();
1440 }
1441 }
1442
1443 // Delete all child tables
1444 if (!$error) {
1445 $elements = array('product_fournisseur_price', 'product_price', 'product_lang', 'categorie_product', 'product_stock', 'product_customer_price', 'product_lot'); // product_batch is done before
1446 foreach ($elements as $table) {
1447 if (!$error) {
1448 $sql = "DELETE FROM ".$this->db->prefix().$table;
1449 $sql .= " WHERE fk_product = ".(int) $this->id;
1450
1451 $result = $this->db->query($sql);
1452 if (!$result) {
1453 $error++;
1454 $this->errors[] = $this->db->lasterror();
1455 }
1456 }
1457 }
1458 }
1459
1460 if (!$error) {
1461 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1462 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1463
1464 //If it is a parent product, then we remove the association with child products
1465 $prodcomb = new ProductCombination($this->db);
1466
1467 if ($prodcomb->deleteByFkProductParent($user, $this->id) < 0) {
1468 $error++;
1469 $this->errors[] = 'Error deleting combinations';
1470 }
1471
1472 //We also check if it is a child product
1473 if (!$error && ($prodcomb->fetchByFkProductChild($this->id) > 0) && ($prodcomb->delete($user) < 0)) {
1474 $error++;
1475 $this->errors[] = 'Error deleting child combination';
1476 }
1477 }
1478
1479 // Delete from product_association
1480 if (!$error) {
1481 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
1482 $sql .= " WHERE fk_product_pere = ".(int) $this->id." OR fk_product_fils = ".(int) $this->id;
1483
1484 $result = $this->db->query($sql);
1485 if (!$result) {
1486 $error++;
1487 $this->errors[] = $this->db->lasterror();
1488 }
1489 }
1490
1491 // Remove extrafields
1492 if (!$error) {
1493 $result = $this->deleteExtraFields();
1494 if ($result < 0) {
1495 $error++;
1496 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1497 }
1498 }
1499
1500 // Delete product
1501 if (!$error) {
1502 $sqlz = "DELETE FROM ".$this->db->prefix()."product";
1503 $sqlz .= " WHERE rowid = ".(int) $this->id;
1504
1505 $resultz = $this->db->query($sqlz);
1506 if (!$resultz) {
1507 $error++;
1508 $this->errors[] = $this->db->lasterror();
1509 }
1510 }
1511
1512 // Delete record into ECM index and physically
1513 if (!$error) {
1514 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1515 if (!$res) {
1516 $error++;
1517 }
1518 }
1519
1520 if (!$error) {
1521 // We remove directory
1522 $ref = dol_sanitizeFileName($this->ref);
1523 if ($conf->product->dir_output) {
1524 $dir = $conf->product->dir_output."/".$ref;
1525 if (file_exists($dir)) {
1527 if (!$res) {
1528 $this->errors[] = 'ErrorFailToDeleteDir';
1529 $error++;
1530 }
1531 }
1532 }
1533 }
1534
1535 if (!$error) {
1536 $this->db->commit();
1537 return 1;
1538 } else {
1539 foreach ($this->errors as $errmsg) {
1540 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1541 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1542 }
1543 $this->db->rollback();
1544 return -$error;
1545 }
1546 } else {
1547 $this->error = "ErrorRecordIsUsedCantDelete";
1548 return 0;
1549 }
1550 }
1551
1558 public function setMultiLangs($user)
1559 {
1560 global $conf, $langs;
1561
1562 $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1563 $current_lang = $langs->getDefaultLang();
1564
1565 foreach ($langs_available as $key => $value) {
1566 if ($key == $current_lang) {
1567 $sql = "SELECT rowid";
1568 $sql .= " FROM ".$this->db->prefix()."product_lang";
1569 $sql .= " WHERE fk_product = ".((int) $this->id);
1570 $sql .= " AND lang = '".$this->db->escape($key)."'";
1571
1572 $result = $this->db->query($sql);
1573
1574 if ($this->db->num_rows($result)) { // if there is already a description line for this language
1575 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1576 $sql2 .= " SET ";
1577 $sql2 .= " label='".$this->db->escape($this->label)."',";
1578 $sql2 .= " description='".$this->db->escape($this->description)."'";
1579 if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1580 $sql2 .= ", note='".$this->db->escape($this->other)."'";
1581 }
1582 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1583 } else {
1584 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1585 if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1586 $sql2 .= ", note";
1587 }
1588 $sql2 .= ")";
1589 $sql2 .= " VALUES(".$this->id.",'".$this->db->escape($key)."','".$this->db->escape($this->label)."',";
1590 $sql2 .= " '".$this->db->escape($this->description)."'";
1591 if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1592 $sql2 .= ", '".$this->db->escape($this->other)."'";
1593 }
1594 $sql2 .= ")";
1595 }
1596 dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
1597 if (!$this->db->query($sql2)) {
1598 $this->error = $this->db->lasterror();
1599 return -1;
1600 }
1601 } elseif (isset($this->multilangs[$key])) {
1602 if (empty($this->multilangs["$key"]["label"])) {
1603 $this->errors[] = $key . ' : ' . $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Label"));
1604 return -1;
1605 }
1606
1607 $sql = "SELECT rowid";
1608 $sql .= " FROM ".$this->db->prefix()."product_lang";
1609 $sql .= " WHERE fk_product = ".((int) $this->id);
1610 $sql .= " AND lang = '".$this->db->escape($key)."'";
1611
1612 $result = $this->db->query($sql);
1613
1614 if ($this->db->num_rows($result)) { // if there is already a description line for this language
1615 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1616 $sql2 .= " SET ";
1617 $sql2 .= " label = '".$this->db->escape($this->multilangs["$key"]["label"])."',";
1618 $sql2 .= " description = '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1619 if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1620 $sql2 .= ", note = '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1621 }
1622 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1623 } else {
1624 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1625 if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1626 $sql2 .= ", note";
1627 }
1628 $sql2 .= ")";
1629 $sql2 .= " VALUES(".$this->id.",'".$this->db->escape($key)."','".$this->db->escape($this->multilangs["$key"]["label"])."',";
1630 $sql2 .= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1631 if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1632 $sql2 .= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1633 }
1634 $sql2 .= ")";
1635 }
1636
1637 // We do not save if main fields are empty
1638 if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
1639 if (!$this->db->query($sql2)) {
1640 $this->error = $this->db->lasterror();
1641 return -1;
1642 }
1643 }
1644 } else {
1645 // language is not current language and we didn't provide a multilang description for this language
1646 }
1647 }
1648
1649 // Call trigger
1650 $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
1651 if ($result < 0) {
1652 $this->error = $this->db->lasterror();
1653 return -1;
1654 }
1655 // End call triggers
1656
1657 return 1;
1658 }
1659
1668 public function delMultiLangs($langtodelete, $user)
1669 {
1670 $sql = "DELETE FROM ".$this->db->prefix()."product_lang";
1671 $sql .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
1672
1673 dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
1674 $result = $this->db->query($sql);
1675 if ($result) {
1676 // Call trigger
1677 $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
1678 if ($result < 0) {
1679 $this->error = $this->db->lasterror();
1680 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1681 return -1;
1682 }
1683 // End call triggers
1684 return 1;
1685 } else {
1686 $this->error = $this->db->lasterror();
1687 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1688 return -1;
1689 }
1690 }
1691
1700 public function setAccountancyCode($type, $value)
1701 {
1702 global $user, $langs, $conf;
1703
1704 $error = 0;
1705
1706 $this->db->begin();
1707
1708 if ($type == 'buy') {
1709 $field = 'accountancy_code_buy';
1710 } elseif ($type == 'buy_intra') {
1711 $field = 'accountancy_code_buy_intra';
1712 } elseif ($type == 'buy_export') {
1713 $field = 'accountancy_code_buy_export';
1714 } elseif ($type == 'sell') {
1715 $field = 'accountancy_code_sell';
1716 } elseif ($type == 'sell_intra') {
1717 $field = 'accountancy_code_sell_intra';
1718 } elseif ($type == 'sell_export') {
1719 $field = 'accountancy_code_sell_export';
1720 } else {
1721 return -1;
1722 }
1723
1724 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ";
1725 $sql .= "$field = '".$this->db->escape($value)."'";
1726 $sql .= " WHERE rowid = ".((int) $this->id);
1727
1728 dol_syslog(__METHOD__, LOG_DEBUG);
1729 $resql = $this->db->query($sql);
1730
1731 if ($resql) {
1732 // Call trigger
1733 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1734 if ($result < 0) {
1735 $error++;
1736 }
1737 // End call triggers
1738
1739 if ($error) {
1740 $this->db->rollback();
1741 return -1;
1742 }
1743
1744 $this->$field = $value;
1745
1746 $this->db->commit();
1747 return 1;
1748 } else {
1749 $this->error = $this->db->lasterror();
1750 $this->db->rollback();
1751 return -1;
1752 }
1753 }
1754
1760 public function getMultiLangs()
1761 {
1762 global $langs;
1763
1764 $current_lang = $langs->getDefaultLang();
1765
1766 $sql = "SELECT lang, label, description, note as other";
1767 $sql .= " FROM ".$this->db->prefix()."product_lang";
1768 $sql .= " WHERE fk_product = ".((int) $this->id);
1769
1770 $result = $this->db->query($sql);
1771 if ($result) {
1772 while ($obj = $this->db->fetch_object($result)) {
1773 //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
1774 if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
1775 $this->label = $obj->label;
1776 $this->description = $obj->description;
1777 $this->other = $obj->other;
1778 }
1779 $this->multilangs["$obj->lang"]["label"] = $obj->label;
1780 $this->multilangs["$obj->lang"]["description"] = $obj->description;
1781 $this->multilangs["$obj->lang"]["other"] = $obj->other;
1782 }
1783 return 1;
1784 } else {
1785 $this->error = "Error: ".$this->db->lasterror()." - ".$sql;
1786 return -1;
1787 }
1788 }
1789
1796 private function getArrayForPriceCompare($level = 0)
1797 {
1798
1799 $testExit = array('multiprices','multiprices_ttc','multiprices_base_type','multiprices_min','multiprices_min_ttc','multiprices_tva_tx','multiprices_recuperableonly');
1800
1801 foreach ($testExit as $field) {
1802 if (!isset($this->$field)) {
1803 return array();
1804 }
1805 $tmparray = $this->$field;
1806 if (!isset($tmparray[$level])) {
1807 return array();
1808 }
1809 }
1810
1811 $lastPrice = array(
1812 'level' => $level ? $level : 1,
1813 'multiprices' => doubleval($this->multiprices[$level]),
1814 'multiprices_ttc' => doubleval($this->multiprices_ttc[$level]),
1815 'multiprices_base_type' => $this->multiprices_base_type[$level],
1816 'multiprices_min' => doubleval($this->multiprices_min[$level]),
1817 'multiprices_min_ttc' => doubleval($this->multiprices_min_ttc[$level]),
1818 'multiprices_tva_tx' => doubleval($this->multiprices_tva_tx[$level]),
1819 'multiprices_recuperableonly' => doubleval($this->multiprices_recuperableonly[$level]),
1820 );
1821
1822 return $lastPrice;
1823 }
1824
1825
1826 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1834 private function _log_price($user, $level = 0)
1835 {
1836 // phpcs:enable
1837 global $conf;
1838
1839 $now = dol_now();
1840
1841 // Clean parameters
1842 if (empty($this->price_by_qty)) {
1843 $this->price_by_qty = 0;
1844 }
1845
1846 // Add new price
1847 $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,";
1848 $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
1849 $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).",";
1850 $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');
1851 $sql .= ")";
1852
1853 dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
1854 $resql = $this->db->query($sql);
1855 if (!$resql) {
1856 $this->error = $this->db->lasterror();
1857 dol_print_error($this->db);
1858 return -1;
1859 } else {
1860 return 1;
1861 }
1862 }
1863
1864
1865 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1873 public function log_price_delete($user, $rowid)
1874 {
1875 // phpcs:enable
1876 $sql = "DELETE FROM ".$this->db->prefix()."product_price_by_qty";
1877 $sql .= " WHERE fk_product_price = ".((int) $rowid);
1878 $resql = $this->db->query($sql);
1879
1880 $sql = "DELETE FROM ".$this->db->prefix()."product_price";
1881 $sql .= " WHERE rowid=".((int) $rowid);
1882 $resql = $this->db->query($sql);
1883 if ($resql) {
1884 return 1;
1885 } else {
1886 $this->error = $this->db->lasterror();
1887 return -1;
1888 }
1889 }
1890
1891
1901 public function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp = 0)
1902 {
1903 global $conf, $db, $hookmanager, $action;
1904
1905 // Call hook if any
1906 if (is_object($hookmanager)) {
1907 $parameters = array('thirdparty_seller'=>$thirdparty_seller, 'thirdparty_buyer' => $thirdparty_buyer, 'pqp' => $pqp);
1908 // Note that $action and $object may have been modified by some hooks
1909 $reshook = $hookmanager->executeHooks('getSellPrice', $parameters, $this, $action);
1910 if ($reshook > 0) {
1911 return $hookmanager->resArray;
1912 }
1913 }
1914
1915 // Update if prices fields are defined
1916 $tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id);
1917 $tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id);
1918 if (empty($tva_tx)) {
1919 $tva_npr = 0;
1920 }
1921
1922 $pu_ht = $this->price;
1923 $pu_ttc = $this->price_ttc;
1924 $price_min = $this->price_min;
1925 $price_base_type = $this->price_base_type;
1926
1927 // If price per segment
1928 if (!empty($conf->global->PRODUIT_MULTIPRICES) && !empty($thirdparty_buyer->price_level)) {
1929 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
1930 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
1931 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
1932 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
1933 if (!empty($conf->global->PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL)) { // using this option is a bug. kept for backward compatibility
1934 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
1935 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
1936 }
1937 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
1938 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
1939 }
1940 if (empty($tva_tx)) {
1941 $tva_npr = 0;
1942 }
1943 }
1944 } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
1945 // If price per customer
1946 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
1947
1948 $prodcustprice = new Productcustomerprice($this->db);
1949
1950 $filter = array('t.fk_product' => $this->id, 't.fk_soc' => $thirdparty_buyer->id);
1951
1952 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1953 if ($result) {
1954 if (count($prodcustprice->lines) > 0) {
1955 $pu_ht = price($prodcustprice->lines[0]->price);
1956 $price_min = price($prodcustprice->lines[0]->price_min);
1957 $pu_ttc = price($prodcustprice->lines[0]->price_ttc);
1958 $price_base_type = $prodcustprice->lines[0]->price_base_type;
1959 $tva_tx = $prodcustprice->lines[0]->tva_tx;
1960 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
1961 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
1962 }
1963 $tva_npr = $prodcustprice->lines[0]->recuperableonly;
1964 if (empty($tva_tx)) {
1965 $tva_npr = 0;
1966 }
1967 }
1968 }
1969 } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY)) {
1970 // If price per quantity
1971 if ($this->prices_by_qty[0]) {
1972 // yes, this product has some prices per quantity
1973 // Search price into product_price_by_qty from $this->id
1974 foreach ($this->prices_by_qty_list[0] as $priceforthequantityarray) {
1975 if ($priceforthequantityarray['rowid'] != $pqp) {
1976 continue;
1977 }
1978 // We found the price
1979 if ($priceforthequantityarray['price_base_type'] == 'HT') {
1980 $pu_ht = $priceforthequantityarray['unitprice'];
1981 } else {
1982 $pu_ttc = $priceforthequantityarray['unitprice'];
1983 }
1984 break;
1985 }
1986 }
1987 } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) {
1988 // If price per quantity and customer
1989 if ($this->prices_by_qty[$thirdparty_buyer->price_level]) {
1990 // yes, this product has some prices per quantity
1991 // Search price into product_price_by_qty from $this->id
1992 foreach ($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray) {
1993 if ($priceforthequantityarray['rowid'] != $pqp) {
1994 continue;
1995 }
1996 // We found the price
1997 if ($priceforthequantityarray['price_base_type'] == 'HT') {
1998 $pu_ht = $priceforthequantityarray['unitprice'];
1999 } else {
2000 $pu_ttc = $priceforthequantityarray['unitprice'];
2001 }
2002 break;
2003 }
2004 }
2005 }
2006
2007 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);
2008 }
2009
2010 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2024 public function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
2025 {
2026 // phpcs:enable
2027 global $conf;
2028 $result = 0;
2029
2030 // 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)
2031 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2032 $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,";
2033 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2034 $sql .= " pfp.packaging";
2035 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2036 $sql .= " WHERE pfp.rowid = ".((int) $prodfournprice);
2037 if ($qty > 0) {
2038 $sql .= " AND pfp.quantity <= ".((float) $qty);
2039 }
2040 $sql .= " ORDER BY pfp.quantity DESC";
2041
2042 dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
2043 $resql = $this->db->query($sql);
2044 if ($resql) {
2045 $obj = $this->db->fetch_object($resql);
2046 if ($obj && $obj->quantity > 0) { // If we found a supplier prices from the id of supplier price
2047 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2048 $prod_supplier = new ProductFournisseur($this->db);
2049 $prod_supplier->product_fourn_price_id = $obj->rowid;
2050 $prod_supplier->id = $obj->fk_product;
2051 $prod_supplier->fourn_qty = $obj->quantity;
2052 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2053 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2054
2055 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2056 $priceparser = new PriceParser($this->db);
2057 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2058 if ($price_result >= 0) {
2059 $obj->price = $price_result;
2060 }
2061 }
2062 $this->product_fourn_price_id = $obj->rowid;
2063 $this->buyprice = $obj->price; // deprecated
2064 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
2065 $this->fourn_price_base_type = 'HT'; // Price base type
2066 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2067 $this->ref_fourn = $obj->ref_supplier; // deprecated
2068 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2069 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2070 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2071 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2072 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2073 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2074 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2075 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2076 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2077 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2078 if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) {
2079 $this->packaging = $obj->packaging;
2080 }
2081 $result = $obj->fk_product;
2082 return $result;
2083 } else { // If not found
2084 // 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.
2085 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2086 $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,";
2087 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2088 $sql .= " pfp.packaging";
2089 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2090 $sql .= " WHERE 1 = 1";
2091 if ($product_id > 0) {
2092 $sql .= " AND pfp.fk_product = ".((int) $product_id);
2093 }
2094 if ($fourn_ref != 'none') {
2095 $sql .= " AND pfp.ref_fourn = '".$this->db->escape($fourn_ref)."'";
2096 }
2097 if ($fk_soc > 0) {
2098 $sql .= " AND pfp.fk_soc = ".((int) $fk_soc);
2099 }
2100 if ($qty > 0) {
2101 $sql .= " AND pfp.quantity <= ".((float) $qty);
2102 }
2103 $sql .= " ORDER BY pfp.quantity DESC";
2104 $sql .= " LIMIT 1";
2105
2106 dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
2107 $resql = $this->db->query($sql);
2108 if ($resql) {
2109 $obj = $this->db->fetch_object($resql);
2110 if ($obj && $obj->quantity > 0) { // If found
2111 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2112 $prod_supplier = new ProductFournisseur($this->db);
2113 $prod_supplier->product_fourn_price_id = $obj->rowid;
2114 $prod_supplier->id = $obj->fk_product;
2115 $prod_supplier->fourn_qty = $obj->quantity;
2116 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2117 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2118
2119 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2120 $priceparser = new PriceParser($this->db);
2121 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2122 if ($result >= 0) {
2123 $obj->price = $price_result;
2124 }
2125 }
2126 $this->product_fourn_price_id = $obj->rowid;
2127 $this->buyprice = $obj->price; // deprecated
2128 $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
2129 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
2130 $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
2131 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2132 $this->ref_fourn = $obj->ref_supplier; // deprecated
2133 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2134 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2135 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2136 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2137 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2138 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2139 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2140 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2141 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2142 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2143 if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) {
2144 $this->packaging = $obj->packaging;
2145 }
2146 $result = $obj->fk_product;
2147 return $result;
2148 } else {
2149 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é.
2150 }
2151 } else {
2152 $this->error = $this->db->lasterror();
2153 return -3;
2154 }
2155 }
2156 } else {
2157 $this->error = $this->db->lasterror();
2158 return -2;
2159 }
2160 }
2161
2162
2179 public function updatePrice($newprice, $newpricebase, $user, $newvat = '', $newminprice = 0, $level = 0, $newnpr = 0, $newpbq = 0, $ignore_autogen = 0, $localtaxes_array = array(), $newdefaultvatcode = '')
2180 {
2181 global $conf, $langs;
2182
2183 $lastPriceData = $this->getArrayForPriceCompare($level); // temporary store current price before update
2184
2185 $id = $this->id;
2186
2187 dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
2188
2189 // Clean parameters
2190 if (empty($this->tva_tx)) {
2191 $this->tva_tx = 0;
2192 }
2193 if (empty($newnpr)) {
2194 $newnpr = 0;
2195 }
2196 if (empty($newminprice)) {
2197 $newminprice = 0;
2198 }
2199 if (empty($newminprice)) {
2200 $newminprice = 0;
2201 }
2202
2203 // Check parameters
2204 if ($newvat == '') {
2205 $newvat = $this->tva_tx;
2206 }
2207
2208 // If multiprices are enabled, then we check if the current product is subject to price autogeneration
2209 // Price will be modified ONLY when the first one is the one that is being modified
2210 if ((!empty($conf->global->PRODUIT_MULTIPRICES) || !empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
2211 return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
2212 }
2213
2214 if (!empty($newminprice) && ($newminprice > $newprice)) {
2215 $this->error = 'ErrorPriceCantBeLowerThanMinPrice';
2216 return -1;
2217 }
2218
2219 if ($newprice !== '' || $newprice === 0) {
2220 if ($newpricebase == 'TTC') {
2221 $price_ttc = price2num($newprice, 'MU');
2222 $price = price2num($newprice) / (1 + ($newvat / 100));
2223 $price = price2num($price, 'MU');
2224
2225 if ($newminprice != '' || $newminprice == 0) {
2226 $price_min_ttc = price2num($newminprice, 'MU');
2227 $price_min = price2num($newminprice) / (1 + ($newvat / 100));
2228 $price_min = price2num($price_min, 'MU');
2229 } else {
2230 $price_min = 0;
2231 $price_min_ttc = 0;
2232 }
2233 } else {
2234 $price = price2num($newprice, 'MU');
2235 $price_ttc = ($newnpr != 1) ? (float) price2num($newprice) * (1 + ($newvat / 100)) : $price;
2236 $price_ttc = price2num($price_ttc, 'MU');
2237
2238 if ($newminprice !== '' || $newminprice === 0) {
2239 $price_min = price2num($newminprice, 'MU');
2240 $price_min_ttc = price2num($newminprice) * (1 + ($newvat / 100));
2241 $price_min_ttc = price2num($price_min_ttc, 'MU');
2242 //print 'X'.$newminprice.'-'.$price_min;
2243 } else {
2244 $price_min = 0;
2245 $price_min_ttc = 0;
2246 }
2247 }
2248 //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
2249
2250 if (count($localtaxes_array) > 0) {
2251 $localtaxtype1 = $localtaxes_array['0'];
2252 $localtax1 = $localtaxes_array['1'];
2253 $localtaxtype2 = $localtaxes_array['2'];
2254 $localtax2 = $localtaxes_array['3'];
2255 } else {
2256 // if array empty, we try to use the vat code
2257 if (!empty($newdefaultvatcode)) {
2258 global $mysoc;
2259 // Get record from code
2260 $sql = "SELECT t.rowid, t.code, t.recuperableonly, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
2261 $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
2262 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$this->db->escape($mysoc->country_code)."'";
2263 $sql .= " AND t.taux = ".((float) $newdefaultvatcode)." AND t.active = 1";
2264 $sql .= " AND t.code = '".$this->db->escape($newdefaultvatcode)."'";
2265 $resql = $this->db->query($sql);
2266 if ($resql) {
2267 $obj = $this->db->fetch_object($resql);
2268 if ($obj) {
2269 $npr = $obj->recuperableonly;
2270 $localtax1 = $obj->localtax1;
2271 $localtax2 = $obj->localtax2;
2272 $localtaxtype1 = $obj->localtax1_type;
2273 $localtaxtype2 = $obj->localtax2_type;
2274 }
2275 }
2276 } else {
2277 // old method. deprecated because we can't retrieve type
2278 $localtaxtype1 = '0';
2279 $localtax1 = get_localtax($newvat, 1);
2280 $localtaxtype2 = '0';
2281 $localtax2 = get_localtax($newvat, 2);
2282 }
2283 }
2284 if (empty($localtax1)) {
2285 $localtax1 = 0; // If = '' then = 0
2286 }
2287 if (empty($localtax2)) {
2288 $localtax2 = 0; // If = '' then = 0
2289 }
2290
2291 $this->db->begin();
2292
2293 // Ne pas mettre de quote sur les numeriques decimaux.
2294 // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
2295 $sql = "UPDATE ".$this->db->prefix()."product SET";
2296 $sql .= " price_base_type='".$this->db->escape($newpricebase)."',";
2297 $sql .= " price=".$price.",";
2298 $sql .= " price_ttc=".$price_ttc.",";
2299 $sql .= " price_min=".$price_min.",";
2300 $sql .= " price_min_ttc=".$price_min_ttc.",";
2301 $sql .= " localtax1_tx=".($localtax1 >= 0 ? $localtax1 : 'NULL').",";
2302 $sql .= " localtax2_tx=".($localtax2 >= 0 ? $localtax2 : 'NULL').",";
2303 $sql .= " localtax1_type=".($localtaxtype1 != '' ? "'".$this->db->escape($localtaxtype1)."'" : "'0'").",";
2304 $sql .= " localtax2_type=".($localtaxtype2 != '' ? "'".$this->db->escape($localtaxtype2)."'" : "'0'").",";
2305 $sql .= " default_vat_code=".($newdefaultvatcode ? "'".$this->db->escape($newdefaultvatcode)."'" : "null").",";
2306 $sql .= " tva_tx='".price2num($newvat)."',";
2307 $sql .= " recuperableonly='".$this->db->escape($newnpr)."'";
2308 $sql .= " WHERE rowid = ".((int) $id);
2309
2310 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2311 $resql = $this->db->query($sql);
2312 if ($resql) {
2313 $this->multiprices[$level] = $price;
2314 $this->multiprices_ttc[$level] = $price_ttc;
2315 $this->multiprices_min[$level] = $price_min;
2316 $this->multiprices_min_ttc[$level] = $price_min_ttc;
2317 $this->multiprices_base_type[$level] = $newpricebase;
2318 $this->multiprices_default_vat_code[$level] = $newdefaultvatcode;
2319 $this->multiprices_tva_tx[$level] = $newvat;
2320 $this->multiprices_recuperableonly[$level] = $newnpr;
2321
2322 $this->price = $price;
2323 $this->price_ttc = $price_ttc;
2324 $this->price_min = $price_min;
2325 $this->price_min_ttc = $price_min_ttc;
2326 $this->price_base_type = $newpricebase;
2327 $this->default_vat_code = $newdefaultvatcode;
2328 $this->tva_tx = $newvat;
2329 $this->tva_npr = $newnpr;
2330 //Local taxes
2331 $this->localtax1_tx = $localtax1;
2332 $this->localtax2_tx = $localtax2;
2333 $this->localtax1_type = $localtaxtype1;
2334 $this->localtax2_type = $localtaxtype2;
2335
2336 // Price by quantity
2337 $this->price_by_qty = $newpbq;
2338
2339 // check if price have really change before log
2340 $newPriceData = $this->getArrayForPriceCompare($level);
2341 if (!empty(array_diff_assoc($newPriceData, $lastPriceData)) || empty($conf->global->PRODUIT_MULTIPRICES)) {
2342 $this->_log_price($user, $level); // Save price for level into table product_price
2343 }
2344
2345 $this->level = $level; // Store level of price edited for trigger
2346
2347 // Call trigger
2348 $result = $this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
2349 if ($result < 0) {
2350 $this->db->rollback();
2351 return -1;
2352 }
2353 // End call triggers
2354
2355 $this->db->commit();
2356 } else {
2357 $this->db->rollback();
2358 $this->error = $this->db->lasterror();
2359 return -1;
2360 }
2361 }
2362
2363 return 1;
2364 }
2365
2373 public function setPriceExpression($expression_id)
2374 {
2375 global $user;
2376
2377 $this->fk_price_expression = $expression_id;
2378
2379 return $this->update($this->id, $user);
2380 }
2381
2394 public function fetch($id = '', $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0, $ignore_price_load = 0, $ignore_lang_load = 0)
2395 {
2396 include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2397
2398 global $langs, $conf;
2399
2400 dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
2401
2402 // Check parameters
2403 if (!$id && !$ref && !$ref_ext && !$barcode) {
2404 $this->error = 'ErrorWrongParameters';
2405 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2406 return -1;
2407 }
2408
2409 $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,";
2410 $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,";
2411 $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,";
2412 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
2413 $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,";
2414 if (empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
2415 $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,";
2416 } else {
2417 $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,";
2418 }
2419
2420 //For MultiCompany
2421 //PMP per entity & Stocks Sharings stock_reel includes only stocks shared with this entity
2422 $separatedEntityPMP = false; // Set to true to get the AWP from table llx_product_perentity instead of field 'pmp' into llx_product.
2423 $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.
2424 $visibleWarehousesEntities = $conf->entity;
2425 if (!empty($conf->global->MULTICOMPANY_PRODUCT_SHARING_ENABLED)) {
2426 if (!empty($conf->global->MULTICOMPANY_PMP_PER_ENTITY_ENABLED)) {
2427 $checkPMPPerEntity = $this->db->query("SELECT pmp FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = ".((int) $id)." AND entity = ".(int) $conf->entity);
2428 if ($this->db->num_rows($checkPMPPerEntity)>0) {
2429 $separatedEntityPMP = true;
2430 }
2431 }
2432 global $mc;
2433 $separatedStock = true;
2434 if (isset($mc->sharings['stock']) && !empty($mc->sharings['stock'])) {
2435 $visibleWarehousesEntities .= "," . implode(",", $mc->sharings['stock']);
2436 }
2437 }
2438 if ($separatedEntityPMP) {
2439 $sql .= " ppe.pmp,";
2440 } else {
2441 $sql .= " p.pmp,";
2442 }
2443 $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.batch_mask, p.fk_unit,";
2444 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf,";
2445 if ($separatedStock) {
2446 $sql .= " SUM(sp.reel) as stock";
2447 } else {
2448 $sql .= " p.stock";
2449 }
2450 $sql .= " FROM ".$this->db->prefix()."product as p";
2451 if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED) || $separatedEntityPMP) {
2452 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
2453 }
2454 if ($separatedStock) {
2455 $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)."))";
2456 }
2457
2458 if ($id) {
2459 $sql .= " WHERE p.rowid = ".((int) $id);
2460 } else {
2461 $sql .= " WHERE p.entity IN (".getEntity($this->element).")";
2462 if ($ref) {
2463 $sql .= " AND p.ref = '".$this->db->escape($ref)."'";
2464 } elseif ($ref_ext) {
2465 $sql .= " AND p.ref_ext = '".$this->db->escape($ref_ext)."'";
2466 } elseif ($barcode) {
2467 $sql .= " AND p.barcode = '".$this->db->escape($barcode)."'";
2468 }
2469 }
2470 if ($separatedStock) {
2471 $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,";
2472 $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,";
2473 $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,";
2474 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
2475 $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,";
2476 if (empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
2477 $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,";
2478 } else {
2479 $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,";
2480 }
2481 if ($separatedEntityPMP) {
2482 $sql .= " ppe.pmp,";
2483 } else {
2484 $sql .= " p.pmp,";
2485 }
2486 $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.batch_mask, p.fk_unit,";
2487 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf";
2488 if (!$separatedStock) {
2489 $sql .= ", p.stock";
2490 }
2491 }
2492
2493 $resql = $this->db->query($sql);
2494 if ($resql) {
2495 unset($this->oldcopy);
2496
2497 if ($this->db->num_rows($resql) > 0) {
2498 $obj = $this->db->fetch_object($resql);
2499
2500 $this->id = $obj->rowid;
2501 $this->ref = $obj->ref;
2502 $this->ref_ext = $obj->ref_ext;
2503 $this->label = $obj->label;
2504 $this->description = $obj->description;
2505 $this->url = $obj->url;
2506 $this->note_public = $obj->note_public;
2507 $this->note_private = $obj->note_private;
2508 $this->note = $obj->note_private; // deprecated
2509
2510 $this->type = $obj->fk_product_type;
2511 $this->status = $obj->tosell;
2512 $this->status_buy = $obj->tobuy;
2513 $this->status_batch = $obj->tobatch;
2514 $this->batch_mask = $obj->batch_mask;
2515
2516 $this->customcode = $obj->customcode;
2517 $this->country_id = $obj->fk_country;
2518 $this->country_code = getCountry($this->country_id, 2, $this->db);
2519 $this->state_id = $obj->fk_state;
2520 $this->lifetime = $obj->lifetime;
2521 $this->qc_frequency = $obj->qc_frequency;
2522 $this->price = $obj->price;
2523 $this->price_ttc = $obj->price_ttc;
2524 $this->price_min = $obj->price_min;
2525 $this->price_min_ttc = $obj->price_min_ttc;
2526 $this->price_base_type = $obj->price_base_type;
2527 $this->cost_price = $obj->cost_price;
2528 $this->default_vat_code = $obj->default_vat_code;
2529 $this->tva_tx = $obj->tva_tx;
2531 $this->tva_npr = $obj->tva_npr;
2532 $this->recuperableonly = $obj->tva_npr; // For backward compatibility
2534 $this->localtax1_tx = $obj->localtax1_tx;
2535 $this->localtax2_tx = $obj->localtax2_tx;
2536 $this->localtax1_type = $obj->localtax1_type;
2537 $this->localtax2_type = $obj->localtax2_type;
2538
2539 $this->finished = $obj->finished;
2540 $this->fk_default_bom = $obj->fk_default_bom;
2541
2542 $this->duration = $obj->duration;
2543 $this->duration_value = $obj->duration ? (int) (substr($obj->duration, 0, dol_strlen($obj->duration) - 1)) : 0;
2544 $this->duration_unit = $obj->duration ? substr($obj->duration, -1) : null;
2545 $this->canvas = $obj->canvas;
2546 $this->net_measure = $obj->net_measure;
2547 $this->net_measure_units = $obj->net_measure_units;
2548 $this->weight = $obj->weight;
2549 $this->weight_units = $obj->weight_units;
2550 $this->length = $obj->length;
2551 $this->length_units = $obj->length_units;
2552 $this->width = $obj->width;
2553 $this->width_units = $obj->width_units;
2554 $this->height = $obj->height;
2555 $this->height_units = $obj->height_units;
2556
2557 $this->surface = $obj->surface;
2558 $this->surface_units = $obj->surface_units;
2559 $this->volume = $obj->volume;
2560 $this->volume_units = $obj->volume_units;
2561 $this->barcode = $obj->barcode;
2562 $this->barcode_type = $obj->fk_barcode_type;
2563
2564 $this->accountancy_code_buy = $obj->accountancy_code_buy;
2565 $this->accountancy_code_buy_intra = $obj->accountancy_code_buy_intra;
2566 $this->accountancy_code_buy_export = $obj->accountancy_code_buy_export;
2567 $this->accountancy_code_sell = $obj->accountancy_code_sell;
2568 $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
2569 $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
2570
2571 $this->fk_default_warehouse = $obj->fk_default_warehouse;
2572 $this->fk_default_workstation = $obj->fk_default_workstation;
2573 $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
2574 $this->desiredstock = $obj->desiredstock;
2575 $this->stock_reel = $obj->stock;
2576 $this->pmp = $obj->pmp;
2577
2578 $this->date_creation = $obj->datec;
2579 $this->date_modification = $obj->tms;
2580 $this->import_key = $obj->import_key;
2581 $this->entity = $obj->entity;
2582
2583 $this->ref_ext = $obj->ref_ext;
2584 $this->fk_price_expression = $obj->fk_price_expression;
2585 $this->fk_unit = $obj->fk_unit;
2586 $this->price_autogen = $obj->price_autogen;
2587 $this->model_pdf = $obj->model_pdf;
2588
2589 $this->mandatory_period = $obj->mandatory_period;
2590
2591 $this->db->free($resql);
2592
2593 // fetch optionals attributes and labels
2594 $this->fetch_optionals();
2595
2596 // Multilangs
2597 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($ignore_lang_load)) {
2598 $this->getMultiLangs();
2599 }
2600
2601 // Load multiprices array
2602 if (!empty($conf->global->PRODUIT_MULTIPRICES) && empty($ignore_price_load)) { // prices per segment
2603 for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
2604 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2605 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2606 $sql .= " FROM ".$this->db->prefix()."product_price";
2607 $sql .= " WHERE entity IN (".getEntity('productprice').")";
2608 $sql .= " AND price_level=".((int) $i);
2609 $sql .= " AND fk_product = ".((int) $this->id);
2610 $sql .= " ORDER BY date_price DESC, rowid DESC"; // Get the most recent line
2611 $sql .= " LIMIT 1"; // Only the first one
2612 $resql = $this->db->query($sql);
2613 if ($resql) {
2614 $result = $this->db->fetch_array($resql);
2615
2616 $this->multiprices[$i] = $result ? $result["price"] : null;
2617 $this->multiprices_ttc[$i] = $result ? $result["price_ttc"] : null;
2618 $this->multiprices_min[$i] = $result ? $result["price_min"] : null;
2619 $this->multiprices_min_ttc[$i] = $result ? $result["price_min_ttc"] : null;
2620 $this->multiprices_base_type[$i] = $result ? $result["price_base_type"] : null;
2621 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2622 $this->multiprices_tva_tx[$i] = $result ? $result["tva_tx"].($result ? ' ('.$result['default_vat_code'].')' : '') : null;
2623 $this->multiprices_recuperableonly[$i] = $result ? $result["recuperableonly"] : null;
2624
2625 // Price by quantity
2626 /*
2627 $this->prices_by_qty[$i]=$result["price_by_qty"];
2628 $this->prices_by_qty_id[$i]=$result["rowid"];
2629 // Récuperation de la liste des prix selon qty si flag positionné
2630 if ($this->prices_by_qty[$i] == 1)
2631 {
2632 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2633 $sql.= " FROM ".$this->db->prefix()."product_price_by_qty";
2634 $sql.= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2635 $sql.= " ORDER BY quantity ASC";
2636 $resultat=array();
2637 $resql = $this->db->query($sql);
2638 if ($resql)
2639 {
2640 $ii=0;
2641 while ($result= $this->db->fetch_array($resql)) {
2642 $resultat[$ii]=array();
2643 $resultat[$ii]["rowid"]=$result["rowid"];
2644 $resultat[$ii]["price"]= $result["price"];
2645 $resultat[$ii]["unitprice"]= $result["unitprice"];
2646 $resultat[$ii]["quantity"]= $result["quantity"];
2647 $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2648 $resultat[$ii]["remise"]= $result["remise"]; // deprecated
2649 $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2650 $ii++;
2651 }
2652 $this->prices_by_qty_list[$i]=$resultat;
2653 }
2654 else
2655 {
2656 dol_print_error($this->db);
2657 return -1;
2658 }
2659 }*/
2660 } else {
2661 $this->error = $this->db->lasterror;
2662 return -1;
2663 }
2664 }
2665 } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES) && empty($ignore_price_load)) { // prices per customers
2666 // Nothing loaded by default. List may be very long.
2667 } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY) && empty($ignore_price_load)) { // prices per quantity
2668 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2669 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
2670 $sql .= " FROM ".$this->db->prefix()."product_price";
2671 $sql .= " WHERE fk_product = ".((int) $this->id);
2672 $sql .= " ORDER BY date_price DESC, rowid DESC";
2673 $sql .= " LIMIT 1";
2674 $resql = $this->db->query($sql);
2675 if ($resql) {
2676 $result = $this->db->fetch_array($resql);
2677
2678 // Price by quantity
2679 $this->prices_by_qty[0] = $result["price_by_qty"];
2680 $this->prices_by_qty_id[0] = $result["rowid"];
2681 // Récuperation de la liste des prix selon qty si flag positionné
2682 if ($this->prices_by_qty[0] == 1) {
2683 $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
2684 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2685 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[0]);
2686 $sql .= " ORDER BY quantity ASC";
2687 $resultat = array();
2688 $resql = $this->db->query($sql);
2689 if ($resql) {
2690 $ii = 0;
2691 while ($result = $this->db->fetch_array($resql)) {
2692 $resultat[$ii] = array();
2693 $resultat[$ii]["rowid"] = $result["rowid"];
2694 $resultat[$ii]["price"] = $result["price"];
2695 $resultat[$ii]["unitprice"] = $result["unitprice"];
2696 $resultat[$ii]["quantity"] = $result["quantity"];
2697 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2698 //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
2699 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2700 $ii++;
2701 }
2702 $this->prices_by_qty_list[0] = $resultat;
2703 } else {
2704 $this->error = $this->db->lasterror;
2705 return -1;
2706 }
2707 }
2708 } else {
2709 $this->error = $this->db->lasterror;
2710 return -1;
2711 }
2712 } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES) && empty($ignore_price_load)) { // prices per customer and quantity
2713 for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
2714 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2715 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2716 $sql .= " FROM ".$this->db->prefix()."product_price";
2717 $sql .= " WHERE entity IN (".getEntity('productprice').")";
2718 $sql .= " AND price_level=".((int) $i);
2719 $sql .= " AND fk_product = ".((int) $this->id);
2720 $sql .= " ORDER BY date_price DESC, rowid DESC";
2721 $sql .= " LIMIT 1";
2722 $resql = $this->db->query($sql);
2723 if ($resql) {
2724 $result = $this->db->fetch_array($resql);
2725
2726 $this->multiprices[$i] = (!empty($result["price"]) ? $result["price"] : 0);
2727 $this->multiprices_ttc[$i] = (!empty($result["price_ttc"]) ? $result["price_ttc"] : 0);
2728 $this->multiprices_min[$i] = (!empty($result["price_min"]) ? $result["price_min"] : 0);
2729 $this->multiprices_min_ttc[$i] = (!empty($result["price_min_ttc"]) ? $result["price_min_ttc"] : 0);
2730 $this->multiprices_base_type[$i] = (!empty($result["price_base_type"]) ? $result["price_base_type"] : '');
2731 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2732 $this->multiprices_tva_tx[$i] = (!empty($result["tva_tx"]) ? $result["tva_tx"] : 0); // TODO Add ' ('.$result['default_vat_code'].')'
2733 $this->multiprices_recuperableonly[$i] = (!empty($result["recuperableonly"]) ? $result["recuperableonly"] : 0);
2734
2735 // Price by quantity
2736 $this->prices_by_qty[$i] = (!empty($result["price_by_qty"]) ? $result["price_by_qty"] : 0);
2737 $this->prices_by_qty_id[$i] = (!empty($result["rowid"]) ? $result["rowid"] : 0);
2738 // Récuperation de la liste des prix selon qty si flag positionné
2739 if ($this->prices_by_qty[$i] == 1) {
2740 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2741 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2742 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2743 $sql .= " ORDER BY quantity ASC";
2744 $resultat = array();
2745 $resql = $this->db->query($sql);
2746 if ($resql) {
2747 $ii = 0;
2748 while ($result = $this->db->fetch_array($resql)) {
2749 $resultat[$ii] = array();
2750 $resultat[$ii]["rowid"] = $result["rowid"];
2751 $resultat[$ii]["price"] = $result["price"];
2752 $resultat[$ii]["unitprice"] = $result["unitprice"];
2753 $resultat[$ii]["quantity"] = $result["quantity"];
2754 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2755 $resultat[$ii]["remise"] = $result["remise"]; // deprecated
2756 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2757 $ii++;
2758 }
2759 $this->prices_by_qty_list[$i] = $resultat;
2760 } else {
2761 $this->error = $this->db->lasterror;
2762 return -1;
2763 }
2764 }
2765 } else {
2766 $this->error = $this->db->lasterror;
2767 return -1;
2768 }
2769 }
2770 }
2771
2772 if (isModEnabled('dynamicprices') && !empty($this->fk_price_expression) && empty($ignore_expression)) {
2773 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2774 $priceparser = new PriceParser($this->db);
2775 $price_result = $priceparser->parseProduct($this);
2776 if ($price_result >= 0) {
2777 $this->price = $price_result;
2778 // Calculate the VAT
2779 $this->price_ttc = price2num($this->price) * (1 + ($this->tva_tx / 100));
2780 $this->price_ttc = price2num($this->price_ttc, 'MU');
2781 }
2782 }
2783
2784 // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
2785 // Instead we just init the stock_warehouse array
2786 $this->stock_warehouse = array();
2787
2788 return 1;
2789 } else {
2790 return 0;
2791 }
2792 } else {
2793 $this->error = $this->db->lasterror();
2794 return -1;
2795 }
2796 }
2797
2798 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2805 public function load_stats_mo($socid = 0)
2806 {
2807 // phpcs:enable
2808 global $user, $hookmanager, $action;
2809
2810 $error = 0;
2811
2812 foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
2813 $this->stats_mo['customers_'.$role] = 0;
2814 $this->stats_mo['nb_'.$role] = 0;
2815 $this->stats_mo['qty_'.$role] = 0;
2816
2817 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2818 $sql .= " SUM(mp.qty) as qty";
2819 $sql .= " FROM ".$this->db->prefix()."mrp_mo as c";
2820 $sql .= " INNER JOIN ".$this->db->prefix()."mrp_production as mp ON mp.fk_mo=c.rowid";
2821 if (empty($user->rights->societe->client->voir) && !$socid) {
2822 $sql .= "INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc=c.fk_soc AND sc.fk_user = ".((int) $user->id);
2823 }
2824 $sql .= " WHERE ";
2825 $sql .= " c.entity IN (".getEntity('mo').")";
2826
2827 $sql .= " AND mp.fk_product = ".((int) $this->id);
2828 $sql .= " AND mp.role ='".$this->db->escape($role)."'";
2829 if ($socid > 0) {
2830 $sql .= " AND c.fk_soc = ".((int) $socid);
2831 }
2832
2833 $result = $this->db->query($sql);
2834 if ($result) {
2835 $obj = $this->db->fetch_object($result);
2836 $this->stats_mo['customers_'.$role] = $obj->nb_customers ? $obj->nb_customers : 0;
2837 $this->stats_mo['nb_'.$role] = $obj->nb ? $obj->nb : 0;
2838 $this->stats_mo['qty_'.$role] = $obj->qty ? price2num($obj->qty, 'MS') : 0; // qty may be a float due to the SUM()
2839 } else {
2840 $this->error = $this->db->error();
2841 $error++;
2842 }
2843 }
2844
2845 if (!empty($error)) {
2846 return -1;
2847 }
2848
2849 $parameters = array('socid' => $socid);
2850 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
2851 if ($reshook > 0) {
2852 $this->stats_mo = $hookmanager->resArray['stats_mo'];
2853 }
2854
2855 return 1;
2856 }
2857
2858 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2865 public function load_stats_bom($socid = 0)
2866 {
2867 // phpcs:enable
2868 global $user, $hookmanager, $action;
2869
2870 $error = 0;
2871
2872 $this->stats_bom['nb_toproduce'] = 0;
2873 $this->stats_bom['nb_toconsume'] = 0;
2874 $this->stats_bom['qty_toproduce'] = 0;
2875 $this->stats_bom['qty_toconsume'] = 0;
2876
2877 $sql = "SELECT COUNT(DISTINCT b.rowid) as nb_toproduce,";
2878 $sql .= " SUM(b.qty) as qty_toproduce";
2879 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
2880 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
2881 $sql .= " WHERE ";
2882 $sql .= " b.entity IN (".getEntity('bom').")";
2883 $sql .= " AND b.fk_product =".((int) $this->id);
2884 $sql .= " GROUP BY b.rowid";
2885
2886 $result = $this->db->query($sql);
2887 if ($result) {
2888 $obj = $this->db->fetch_object($result);
2889 $this->stats_bom['nb_toproduce'] = !empty($obj->nb_toproduce) ? $obj->nb_toproduce : 0;
2890 $this->stats_bom['qty_toproduce'] = !empty($obj->qty_toproduce) ? price2num($obj->qty_toproduce) : 0;
2891 } else {
2892 $this->error = $this->db->error();
2893 $error++;
2894 }
2895
2896 $sql = "SELECT COUNT(DISTINCT bl.rowid) as nb_toconsume,";
2897 $sql .= " SUM(bl.qty) as qty_toconsume";
2898 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
2899 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
2900 $sql .= " WHERE ";
2901 $sql .= " b.entity IN (".getEntity('bom').")";
2902 $sql .= " AND bl.fk_product =".((int) $this->id);
2903
2904 $result = $this->db->query($sql);
2905 if ($result) {
2906 $obj = $this->db->fetch_object($result);
2907 $this->stats_bom['nb_toconsume'] = !empty($obj->nb_toconsume) ? $obj->nb_toconsume : 0;
2908 $this->stats_bom['qty_toconsume'] = !empty($obj->qty_toconsume) ? price2num($obj->qty_toconsume) : 0;
2909 } else {
2910 $this->error = $this->db->error();
2911 $error++;
2912 }
2913
2914 if (!empty($error)) {
2915 return -1;
2916 }
2917
2918 $parameters = array('socid' => $socid);
2919 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
2920 if ($reshook > 0) {
2921 $this->stats_bom = $hookmanager->resArray['stats_bom'];
2922 }
2923
2924 return 1;
2925 }
2926
2927 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2934 public function load_stats_propale($socid = 0)
2935 {
2936 // phpcs:enable
2937 global $conf, $user, $hookmanager, $action;
2938
2939 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
2940 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
2941 $sql .= " FROM ".$this->db->prefix()."propaldet as pd";
2942 $sql .= ", ".$this->db->prefix()."propal as p";
2943 $sql .= ", ".$this->db->prefix()."societe as s";
2944 if (empty($user->rights->societe->client->voir) && !$socid) {
2945 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
2946 }
2947 $sql .= " WHERE p.rowid = pd.fk_propal";
2948 $sql .= " AND p.fk_soc = s.rowid";
2949 $sql .= " AND p.entity IN (".getEntity('propal').")";
2950 $sql .= " AND pd.fk_product = ".((int) $this->id);
2951 if (empty($user->rights->societe->client->voir) && !$socid) {
2952 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2953 }
2954 //$sql.= " AND pr.fk_statut != 0";
2955 if ($socid > 0) {
2956 $sql .= " AND p.fk_soc = ".((int) $socid);
2957 }
2958
2959 $result = $this->db->query($sql);
2960 if ($result) {
2961 $obj = $this->db->fetch_object($result);
2962 $this->stats_propale['customers'] = $obj->nb_customers;
2963 $this->stats_propale['nb'] = $obj->nb;
2964 $this->stats_propale['rows'] = $obj->nb_rows;
2965 $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
2966
2967 // if it's a virtual product, maybe it is in proposal by extension
2968 if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
2969 $TFather = $this->getFather();
2970 if (is_array($TFather) && !empty($TFather)) {
2971 foreach ($TFather as &$fatherData) {
2972 $pFather = new Product($this->db);
2973 $pFather->id = $fatherData['id'];
2974 $qtyCoef = $fatherData['qty'];
2975
2976 if ($fatherData['incdec']) {
2977 $pFather->load_stats_propale($socid);
2978
2979 $this->stats_propale['customers'] += $pFather->stats_propale['customers'];
2980 $this->stats_propale['nb'] += $pFather->stats_propale['nb'];
2981 $this->stats_propale['rows'] += $pFather->stats_propale['rows'];
2982 $this->stats_propale['qty'] += $pFather->stats_propale['qty'] * $qtyCoef;
2983 }
2984 }
2985 }
2986 }
2987
2988 $parameters = array('socid' => $socid);
2989 $reshook = $hookmanager->executeHooks('loadStatsCustomerProposal', $parameters, $this, $action);
2990 if ($reshook > 0) {
2991 $this->stats_propale = $hookmanager->resArray['stats_propale'];
2992 }
2993
2994 return 1;
2995 } else {
2996 $this->error = $this->db->error();
2997 return -1;
2998 }
2999 }
3000
3001
3002 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3009 public function load_stats_proposal_supplier($socid = 0)
3010 {
3011 // phpcs:enable
3012 global $conf, $user, $hookmanager, $action;
3013
3014 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
3015 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3016 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as pd";
3017 $sql .= ", ".$this->db->prefix()."supplier_proposal as p";
3018 $sql .= ", ".$this->db->prefix()."societe as s";
3019 if (empty($user->rights->societe->client->voir) && !$socid) {
3020 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3021 }
3022 $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
3023 $sql .= " AND p.fk_soc = s.rowid";
3024 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
3025 $sql .= " AND pd.fk_product = ".((int) $this->id);
3026 if (empty($user->rights->societe->client->voir) && !$socid) {
3027 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3028 }
3029 //$sql.= " AND pr.fk_statut != 0";
3030 if ($socid > 0) {
3031 $sql .= " AND p.fk_soc = ".((int) $socid);
3032 }
3033
3034 $result = $this->db->query($sql);
3035 if ($result) {
3036 $obj = $this->db->fetch_object($result);
3037 $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
3038 $this->stats_proposal_supplier['nb'] = $obj->nb;
3039 $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
3040 $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
3041
3042 $parameters = array('socid' => $socid);
3043 $reshook = $hookmanager->executeHooks('loadStatsSupplierProposal', $parameters, $this, $action);
3044 if ($reshook > 0) {
3045 $this->stats_proposal_supplier = $hookmanager->resArray['stats_proposal_supplier'];
3046 }
3047
3048 return 1;
3049 } else {
3050 $this->error = $this->db->error();
3051 return -1;
3052 }
3053 }
3054
3055
3056 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3065 public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
3066 {
3067 // phpcs:enable
3068 global $conf, $user, $hookmanager, $action;
3069
3070 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3071 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3072 $sql .= " FROM ".$this->db->prefix()."commandedet as cd";
3073 $sql .= ", ".$this->db->prefix()."commande as c";
3074 $sql .= ", ".$this->db->prefix()."societe as s";
3075 if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
3076 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3077 }
3078 $sql .= " WHERE c.rowid = cd.fk_commande";
3079 $sql .= " AND c.fk_soc = s.rowid";
3080 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && !empty($conf->global->STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE) ? 'stock' : 'commande').")";
3081 $sql .= " AND cd.fk_product = ".((int) $this->id);
3082 if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
3083 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3084 }
3085 if ($socid > 0) {
3086 $sql .= " AND c.fk_soc = ".((int) $socid);
3087 }
3088 if ($filtrestatut <> '') {
3089 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")";
3090 }
3091
3092 $result = $this->db->query($sql);
3093 if ($result) {
3094 $obj = $this->db->fetch_object($result);
3095 $this->stats_commande['customers'] = $obj->nb_customers;
3096 $this->stats_commande['nb'] = $obj->nb;
3097 $this->stats_commande['rows'] = $obj->nb_rows;
3098 $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
3099
3100 // if it's a virtual product, maybe it is in order by extension
3101 if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
3102 $TFather = $this->getFather();
3103 if (is_array($TFather) && !empty($TFather)) {
3104 foreach ($TFather as &$fatherData) {
3105 $pFather = new Product($this->db);
3106 $pFather->id = $fatherData['id'];
3107 $qtyCoef = $fatherData['qty'];
3108
3109 if ($fatherData['incdec']) {
3110 $pFather->load_stats_commande($socid, $filtrestatut);
3111
3112 $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
3113 $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
3114 $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
3115 $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
3116 }
3117 }
3118 }
3119 }
3120
3121 // If stock decrease is on invoice validation, the theorical stock continue to
3122 // count the orders to ship in theorical stock when some are already removed by invoice validation.
3123 if ($forVirtualStock && !empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
3124 if (!empty($conf->global->DECREASE_ONLY_UNINVOICEDPRODUCTS)) {
3125 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation but only if order not yet invoice.
3126 $adeduire = 0;
3127 $sql = "SELECT SUM(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3128 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3129 $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'))";
3130 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_source = c.rowid";
3131 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND c.facture = 0 AND fd.fk_product = ".((int) $this->id);
3132
3133 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3134 $resql = $this->db->query($sql);
3135 if ($resql) {
3136 if ($this->db->num_rows($resql) > 0) {
3137 $obj = $this->db->fetch_object($resql);
3138 $adeduire += $obj->count;
3139 }
3140 }
3141
3142 $this->stats_commande['qty'] -= $adeduire;
3143 } else {
3144 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is off, we make a compensation with lines of invoices linked to the order
3145 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3146
3147 // For every order having invoice already validated we need to decrease stock cause it's in physical stock
3148 $adeduire = 0;
3149 $sql = "SELECT sum(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet as fd ";
3150 $sql .= " JOIN ".MAIN_DB_PREFIX."facture as f ON fd.fk_facture = f.rowid";
3151 $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'))";
3152 $sql .= " JOIN ".MAIN_DB_PREFIX."commande as c ON el.fk_source = c.rowid";
3153 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product = ".((int) $this->id);
3154
3155 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3156 $resql = $this->db->query($sql);
3157 if ($resql) {
3158 if ($this->db->num_rows($resql) > 0) {
3159 $obj = $this->db->fetch_object($resql);
3160 $adeduire += $obj->count;
3161 }
3162 } else {
3163 $this->error = $this->db->error();
3164 return -1;
3165 }
3166
3167 $this->stats_commande['qty'] -= $adeduire;
3168 }
3169 }
3170
3171 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3172 $reshook = $hookmanager->executeHooks('loadStatsCustomerOrder', $parameters, $this, $action);
3173 if ($reshook > 0) {
3174 $this->stats_commande = $hookmanager->resArray['stats_commande'];
3175 }
3176 return 1;
3177 } else {
3178 $this->error = $this->db->error();
3179 return -1;
3180 }
3181 }
3182
3183 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3193 public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3194 {
3195 // phpcs:enable
3196 global $conf, $user, $hookmanager, $action;
3197
3198 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
3199 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3200 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as cd";
3201 $sql .= ", ".$this->db->prefix()."commande_fournisseur as c";
3202 $sql .= ", ".$this->db->prefix()."societe as s";
3203 if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
3204 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3205 }
3206 $sql .= " WHERE c.rowid = cd.fk_commande";
3207 $sql .= " AND c.fk_soc = s.rowid";
3208 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && !empty($conf->global->STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE) ? 'stock' : 'supplier_order').")";
3209 $sql .= " AND cd.fk_product = ".((int) $this->id);
3210 if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
3211 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3212 }
3213 if ($socid > 0) {
3214 $sql .= " AND c.fk_soc = ".((int) $socid);
3215 }
3216 if ($filtrestatut != '') {
3217 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
3218 }
3219 if (!empty($dateofvirtualstock)) {
3220 $sql .= " AND c.date_livraison <= '".$this->db->idate($dateofvirtualstock)."'";
3221 }
3222
3223 $result = $this->db->query($sql);
3224 if ($result) {
3225 $obj = $this->db->fetch_object($result);
3226 $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
3227 $this->stats_commande_fournisseur['nb'] = $obj->nb;
3228 $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
3229 $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3230
3231 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3232 $reshook = $hookmanager->executeHooks('loadStatsSupplierOrder', $parameters, $this, $action);
3233 if ($reshook > 0) {
3234 $this->stats_commande_fournisseur = $hookmanager->resArray['stats_commande_fournisseur'];
3235 }
3236
3237 return 1;
3238 } else {
3239 $this->error = $this->db->error().' sql='.$sql;
3240 return -1;
3241 }
3242 }
3243
3244 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3254 public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $filterShipmentStatus = '')
3255 {
3256 // phpcs:enable
3257 global $conf, $user, $hookmanager, $action;
3258
3259 $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
3260 $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
3261 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
3262 $sql .= ", ".$this->db->prefix()."commandedet as cd";
3263 $sql .= ", ".$this->db->prefix()."commande as c";
3264 $sql .= ", ".$this->db->prefix()."expedition as e";
3265 $sql .= ", ".$this->db->prefix()."societe as s";
3266 if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
3267 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3268 }
3269 $sql .= " WHERE e.rowid = ed.fk_expedition";
3270 $sql .= " AND c.rowid = cd.fk_commande";
3271 $sql .= " AND e.fk_soc = s.rowid";
3272 $sql .= " AND e.entity IN (".getEntity($forVirtualStock && !empty($conf->global->STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE) ? 'stock' : 'expedition').")";
3273 $sql .= " AND ed.fk_origin_line = cd.rowid";
3274 $sql .= " AND cd.fk_product = ".((int) $this->id);
3275 if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
3276 $sql .= " AND e.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3277 }
3278 if ($socid > 0) {
3279 $sql .= " AND e.fk_soc = ".((int) $socid);
3280 }
3281 if ($filtrestatut <> '') {
3282 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3283 }
3284 if (!empty($filterShipmentStatus)) {
3285 $sql .= " AND e.fk_statut IN (".$this->db->sanitize($filterShipmentStatus).")";
3286 }
3287
3288 $result = $this->db->query($sql);
3289 if ($result) {
3290 $obj = $this->db->fetch_object($result);
3291 $this->stats_expedition['customers'] = $obj->nb_customers;
3292 $this->stats_expedition['nb'] = $obj->nb;
3293 $this->stats_expedition['rows'] = $obj->nb_rows;
3294 $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
3295
3296 // if it's a virtual product, maybe it is in sending by extension
3297 if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
3298 $TFather = $this->getFather();
3299 if (is_array($TFather) && !empty($TFather)) {
3300 foreach ($TFather as &$fatherData) {
3301 $pFather = new Product($this->db);
3302 $pFather->id = $fatherData['id'];
3303 $qtyCoef = $fatherData['qty'];
3304
3305 if ($fatherData['incdec']) {
3306 $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
3307
3308 $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
3309 $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
3310 $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
3311 $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
3312 }
3313 }
3314 }
3315 }
3316
3317 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock, 'filterShipmentStatus' => $filterShipmentStatus);
3318 $reshook = $hookmanager->executeHooks('loadStatsSending', $parameters, $this, $action);
3319 if ($reshook > 0) {
3320 $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
3321 }
3322
3323 return 1;
3324 } else {
3325 $this->error = $this->db->error();
3326 return -1;
3327 }
3328 }
3329
3330 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3340 public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3341 {
3342 // phpcs:enable
3343 global $conf, $user, $hookmanager, $action;
3344
3345 $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_suppliers, COUNT(DISTINCT cf.rowid) as nb,";
3346 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3347 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur_dispatch as fd";
3348 $sql .= ", ".$this->db->prefix()."commande_fournisseur as cf";
3349 $sql .= ", ".$this->db->prefix()."societe as s";
3350 if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
3351 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3352 }
3353 $sql .= " WHERE cf.rowid = fd.fk_commande";
3354 $sql .= " AND cf.fk_soc = s.rowid";
3355 $sql .= " AND cf.entity IN (".getEntity($forVirtualStock && !empty($conf->global->STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE) ? 'stock' : 'supplier_order').")";
3356 $sql .= " AND fd.fk_product = ".((int) $this->id);
3357 if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
3358 $sql .= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3359 }
3360 if ($socid > 0) {
3361 $sql .= " AND cf.fk_soc = ".((int) $socid);
3362 }
3363 if ($filtrestatut <> '') {
3364 $sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3365 }
3366 if (!empty($dateofvirtualstock)) {
3367 $sql .= " AND fd.datec <= '".$this->db->idate($dateofvirtualstock)."'";
3368 }
3369
3370 $result = $this->db->query($sql);
3371 if ($result) {
3372 $obj = $this->db->fetch_object($result);
3373 $this->stats_reception['suppliers'] = $obj->nb_suppliers;
3374 $this->stats_reception['nb'] = $obj->nb;
3375 $this->stats_reception['rows'] = $obj->nb_rows;
3376 $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
3377
3378 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3379 $reshook = $hookmanager->executeHooks('loadStatsReception', $parameters, $this, $action);
3380 if ($reshook > 0) {
3381 $this->stats_reception = $hookmanager->resArray['stats_reception'];
3382 }
3383
3384 return 1;
3385 } else {
3386 $this->error = $this->db->error();
3387 return -1;
3388 }
3389 }
3390
3391 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3401 public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3402 {
3403 // phpcs:enable
3404 global $conf, $user, $hookmanager, $action;
3405
3406 $sql = "SELECT COUNT(DISTINCT m.fk_soc) as nb_customers, COUNT(DISTINCT m.rowid) as nb,";
3407 $sql .= " COUNT(mp.rowid) as nb_rows, SUM(mp.qty) as qty, role";
3408 $sql .= " FROM ".$this->db->prefix()."mrp_production as mp";
3409 $sql .= ", ".$this->db->prefix()."mrp_mo as m";
3410 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON s.rowid = m.fk_soc";
3411 if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
3412 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3413 }
3414 $sql .= " WHERE m.rowid = mp.fk_mo";
3415 $sql .= " AND m.entity IN (".getEntity($forVirtualStock && !empty($conf->global->STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE) ? 'stock' : 'mrp').")";
3416 $sql .= " AND mp.fk_product = ".((int) $this->id);
3417 if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
3418 $sql .= " AND m.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3419 }
3420 if ($socid > 0) {
3421 $sql .= " AND m.fk_soc = ".((int) $socid);
3422 }
3423 if ($filtrestatut <> '') {
3424 $sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
3425 }
3426 if (!empty($dateofvirtualstock)) {
3427 $sql .= " AND m.date_valid <= '".$this->db->idate($dateofvirtualstock)."'"; // better date to code ? end of production ?
3428 }
3429 $sql .= " GROUP BY role";
3430
3431 $this->stats_mrptoconsume['customers'] = 0;
3432 $this->stats_mrptoconsume['nb'] = 0;
3433 $this->stats_mrptoconsume['rows'] = 0;
3434 $this->stats_mrptoconsume['qty'] = 0;
3435 $this->stats_mrptoproduce['customers'] = 0;
3436 $this->stats_mrptoproduce['nb'] = 0;
3437 $this->stats_mrptoproduce['rows'] = 0;
3438 $this->stats_mrptoproduce['qty'] = 0;
3439
3440 $result = $this->db->query($sql);
3441 if ($result) {
3442 while ($obj = $this->db->fetch_object($result)) {
3443 if ($obj->role == 'toconsume') {
3444 $this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3445 $this->stats_mrptoconsume['nb'] += $obj->nb;
3446 $this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3447 $this->stats_mrptoconsume['qty'] += ($obj->qty ? $obj->qty : 0);
3448 }
3449 if ($obj->role == 'consumed') {
3450 //$this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3451 //$this->stats_mrptoconsume['nb'] += $obj->nb;
3452 //$this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3453 $this->stats_mrptoconsume['qty'] -= ($obj->qty ? $obj->qty : 0);
3454 }
3455 if ($obj->role == 'toproduce') {
3456 $this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3457 $this->stats_mrptoproduce['nb'] += $obj->nb;
3458 $this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3459 $this->stats_mrptoproduce['qty'] += ($obj->qty ? $obj->qty : 0);
3460 }
3461 if ($obj->role == 'produced') {
3462 //$this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3463 //$this->stats_mrptoproduce['nb'] += $obj->nb;
3464 //$this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3465 $this->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3466 }
3467 }
3468
3469 // Clean data
3470 if ($this->stats_mrptoconsume['qty'] < 0) {
3471 $this->stats_mrptoconsume['qty'] = 0;
3472 }
3473 if ($this->stats_mrptoproduce['qty'] < 0) {
3474 $this->stats_mrptoproduce['qty'] = 0;
3475 }
3476
3477 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3478 $reshook = $hookmanager->executeHooks('loadStatsInProduction', $parameters, $this, $action);
3479 if ($reshook > 0) {
3480 $this->stats_mrptoproduce = $hookmanager->resArray['stats_mrptoproduce'];
3481 }
3482
3483 return 1;
3484 } else {
3485 $this->error = $this->db->error();
3486 return -1;
3487 }
3488 }
3489
3490 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3497 public function load_stats_contrat($socid = 0)
3498 {
3499 // phpcs:enable
3500 global $conf, $user, $hookmanager, $action;
3501
3502 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3503 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3504 $sql .= " FROM ".$this->db->prefix()."contratdet as cd";
3505 $sql .= ", ".$this->db->prefix()."contrat as c";
3506 $sql .= ", ".$this->db->prefix()."societe as s";
3507 if (empty($user->rights->societe->client->voir) && !$socid) {
3508 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3509 }
3510 $sql .= " WHERE c.rowid = cd.fk_contrat";
3511 $sql .= " AND c.fk_soc = s.rowid";
3512 $sql .= " AND c.entity IN (".getEntity('contract').")";
3513 $sql .= " AND cd.fk_product = ".((int) $this->id);
3514 if (empty($user->rights->societe->client->voir) && !$socid) {
3515 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3516 }
3517 //$sql.= " AND c.statut != 0";
3518 if ($socid > 0) {
3519 $sql .= " AND c.fk_soc = ".((int) $socid);
3520 }
3521
3522 $result = $this->db->query($sql);
3523 if ($result) {
3524 $obj = $this->db->fetch_object($result);
3525 $this->stats_contrat['customers'] = $obj->nb_customers;
3526 $this->stats_contrat['nb'] = $obj->nb;
3527 $this->stats_contrat['rows'] = $obj->nb_rows;
3528 $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
3529
3530 // if it's a virtual product, maybe it is in contract by extension
3531 if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
3532 $TFather = $this->getFather();
3533 if (is_array($TFather) && !empty($TFather)) {
3534 foreach ($TFather as &$fatherData) {
3535 $pFather = new Product($this->db);
3536 $pFather->id = $fatherData['id'];
3537 $qtyCoef = $fatherData['qty'];
3538
3539 if ($fatherData['incdec']) {
3540 $pFather->load_stats_contrat($socid);
3541
3542 $this->stats_contrat['customers'] += $pFather->stats_contrat['customers'];
3543 $this->stats_contrat['nb'] += $pFather->stats_contrat['nb'];
3544 $this->stats_contrat['rows'] += $pFather->stats_contrat['rows'];
3545 $this->stats_contrat['qty'] += $pFather->stats_contrat['qty'] * $qtyCoef;
3546 }
3547 }
3548 }
3549 }
3550
3551 $parameters = array('socid' => $socid);
3552 $reshook = $hookmanager->executeHooks('loadStatsContract', $parameters, $this, $action);
3553 if ($reshook > 0) {
3554 $this->stats_contrat = $hookmanager->resArray['stats_contrat'];
3555 }
3556
3557 return 1;
3558 } else {
3559 $this->error = $this->db->error().' sql='.$sql;
3560 return -1;
3561 }
3562 }
3563
3564 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3571 public function load_stats_facture($socid = 0)
3572 {
3573 // phpcs:enable
3574 global $db, $conf, $user, $hookmanager, $action;
3575
3576 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3577 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(".$this->db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
3578 $sql .= " FROM ".$this->db->prefix()."facturedet as fd";
3579 $sql .= ", ".$this->db->prefix()."facture as f";
3580 $sql .= ", ".$this->db->prefix()."societe as s";
3581 if (empty($user->rights->societe->client->voir) && !$socid) {
3582 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3583 }
3584 $sql .= " WHERE f.rowid = fd.fk_facture";
3585 $sql .= " AND f.fk_soc = s.rowid";
3586 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3587 $sql .= " AND fd.fk_product = ".((int) $this->id);
3588 if (empty($user->rights->societe->client->voir) && !$socid) {
3589 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3590 }
3591 //$sql.= " AND f.fk_statut != 0";
3592 if ($socid > 0) {
3593 $sql .= " AND f.fk_soc = ".((int) $socid);
3594 }
3595
3596 $result = $this->db->query($sql);
3597 if ($result) {
3598 $obj = $this->db->fetch_object($result);
3599 $this->stats_facture['customers'] = $obj->nb_customers;
3600 $this->stats_facture['nb'] = $obj->nb;
3601 $this->stats_facture['rows'] = $obj->nb_rows;
3602 $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
3603
3604 // if it's a virtual product, maybe it is in invoice by extension
3605 if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
3606 $TFather = $this->getFather();
3607 if (is_array($TFather) && !empty($TFather)) {
3608 foreach ($TFather as &$fatherData) {
3609 $pFather = new Product($this->db);
3610 $pFather->id = $fatherData['id'];
3611 $qtyCoef = $fatherData['qty'];
3612
3613 if ($fatherData['incdec']) {
3614 $pFather->load_stats_facture($socid);
3615
3616 $this->stats_facture['customers'] += $pFather->stats_facture['customers'];
3617 $this->stats_facture['nb'] += $pFather->stats_facture['nb'];
3618 $this->stats_facture['rows'] += $pFather->stats_facture['rows'];
3619 $this->stats_facture['qty'] += $pFather->stats_facture['qty'] * $qtyCoef;
3620 }
3621 }
3622 }
3623 }
3624
3625 $parameters = array('socid' => $socid);
3626 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoice', $parameters, $this, $action);
3627 if ($reshook > 0) {
3628 $this->stats_facture = $hookmanager->resArray['stats_facture'];
3629 }
3630
3631 return 1;
3632 } else {
3633 $this->error = $this->db->error();
3634 return -1;
3635 }
3636 }
3637
3638
3639 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3646 public function load_stats_facturerec($socid = 0)
3647 {
3648 // phpcs:enable
3649 global $db, $conf, $user, $hookmanager;
3650
3651 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3652 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3653 $sql .= " FROM ".MAIN_DB_PREFIX."facturedet_rec as fd";
3654 $sql .= ", ".MAIN_DB_PREFIX."facture_rec as f";
3655 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
3656 if (empty($user->rights->societe->client->voir) && !$socid) {
3657 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3658 }
3659 $sql .= " WHERE f.rowid = fd.fk_facture";
3660 $sql .= " AND f.fk_soc = s.rowid";
3661 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3662 $sql .= " AND fd.fk_product = ".((int) $this->id);
3663 if (empty($user->rights->societe->client->voir) && !$socid) {
3664 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3665 }
3666 //$sql.= " AND f.fk_statut != 0";
3667 if ($socid > 0) {
3668 $sql .= " AND f.fk_soc = ".((int) $socid);
3669 }
3670
3671 $result = $this->db->query($sql);
3672 if ($result) {
3673 $obj = $this->db->fetch_object($result);
3674 $this->stats_facturerec['customers'] = $obj->nb_customers;
3675 $this->stats_facturerec['nb'] = $obj->nb;
3676 $this->stats_facturerec['rows'] = $obj->nb_rows;
3677 $this->stats_facturerec['qty'] = $obj->qty ? $obj->qty : 0;
3678
3679 // if it's a virtual product, maybe it is in invoice by extension
3680 if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
3681 $TFather = $this->getFather();
3682 if (is_array($TFather) && !empty($TFather)) {
3683 foreach ($TFather as &$fatherData) {
3684 $pFather = new Product($this->db);
3685 $pFather->id = $fatherData['id'];
3686 $qtyCoef = $fatherData['qty'];
3687
3688 if ($fatherData['incdec']) {
3689 $pFather->load_stats_facture($socid);
3690
3691 $this->stats_facturerec['customers'] += $pFather->stats_facturerec['customers'];
3692 $this->stats_facturerec['nb'] += $pFather->stats_facturerec['nb'];
3693 $this->stats_facturerec['rows'] += $pFather->stats_facturerec['rows'];
3694 $this->stats_facturerec['qty'] += $pFather->stats_facturerec['qty'] * $qtyCoef;
3695 }
3696 }
3697 }
3698 }
3699
3700 $parameters = array('socid' => $socid);
3701 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoiceRec', $parameters, $this, $action);
3702 if ($reshook > 0) {
3703 $this->stats_facturerec = $hookmanager->resArray['stats_facturerec'];
3704 }
3705
3706 return 1;
3707 } else {
3708 $this->error = $this->db->error();
3709 return -1;
3710 }
3711 }
3712
3713 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3720 public function load_stats_facture_fournisseur($socid = 0)
3721 {
3722 // phpcs:enable
3723 global $conf, $user, $hookmanager, $action;
3724
3725 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
3726 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3727 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as fd";
3728 $sql .= ", ".$this->db->prefix()."facture_fourn as f";
3729 $sql .= ", ".$this->db->prefix()."societe as s";
3730 if (empty($user->rights->societe->client->voir) && !$socid) {
3731 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3732 }
3733 $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
3734 $sql .= " AND f.fk_soc = s.rowid";
3735 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
3736 $sql .= " AND fd.fk_product = ".((int) $this->id);
3737 if (empty($user->rights->societe->client->voir) && !$socid) {
3738 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3739 }
3740 //$sql.= " AND f.fk_statut != 0";
3741 if ($socid > 0) {
3742 $sql .= " AND f.fk_soc = ".((int) $socid);
3743 }
3744
3745 $result = $this->db->query($sql);
3746 if ($result) {
3747 $obj = $this->db->fetch_object($result);
3748 $this->stats_facture_fournisseur['suppliers'] = $obj->nb_suppliers;
3749 $this->stats_facture_fournisseur['nb'] = $obj->nb;
3750 $this->stats_facture_fournisseur['rows'] = $obj->nb_rows;
3751 $this->stats_facture_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3752
3753 $parameters = array('socid' => $socid);
3754 $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoice', $parameters, $this, $action);
3755 if ($reshook > 0) {
3756 $this->stats_facture_fournisseur = $hookmanager->resArray['stats_facture_fournisseur'];
3757 }
3758
3759 return 1;
3760 } else {
3761 $this->error = $this->db->error();
3762 return -1;
3763 }
3764 }
3765
3766 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3775 private function _get_stats($sql, $mode, $year = 0)
3776 {
3777 // phpcs:enable
3778 $tab = array();
3779
3780 $resql = $this->db->query($sql);
3781 if ($resql) {
3782 $num = $this->db->num_rows($resql);
3783 $i = 0;
3784 while ($i < $num) {
3785 $arr = $this->db->fetch_array($resql);
3786 $keyfortab = (string) $arr[1];
3787 if ($year == -1) {
3788 $keyfortab = substr($keyfortab, -2);
3789 }
3790
3791 if ($mode == 'byunit') {
3792 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[0]; // 1st field
3793 } elseif ($mode == 'bynumber') {
3794 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3795 } elseif ($mode == 'byamount') {
3796 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3797 } else {
3798 // Bad value for $mode
3799 return -1;
3800 }
3801 $i++;
3802 }
3803 } else {
3804 $this->error = $this->db->error().' sql='.$sql;
3805 return -1;
3806 }
3807
3808 if (empty($year)) {
3809 $year = strftime('%Y', time());
3810 $month = strftime('%m', time());
3811 } elseif ($year == -1) {
3812 $year = '';
3813 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3814 } else {
3815 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3816 }
3817
3818 $result = array();
3819
3820 for ($j = 0; $j < 12; $j++) {
3821 // $ids is 'D', 'N', 'O', 'S', ... (First letter of month in user language)
3822 $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, 1970), "%b"), 1, 'right', 'UTF-8', 1));
3823
3824 //print $idx.'-'.$year.'-'.$month.'<br>';
3825 $result[$j] = array($idx, isset($tab[$year.$month]) ? $tab[$year.$month] : 0);
3826 // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
3827
3828 $month = "0".($month - 1);
3829 if (dol_strlen($month) == 3) {
3830 $month = substr($month, 1);
3831 }
3832 if ($month == 0) {
3833 $month = 12;
3834 $year = $year - 1;
3835 }
3836 }
3837
3838 return array_reverse($result);
3839 }
3840
3841
3842 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3853 public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3854 {
3855 // phpcs:enable
3856 global $conf;
3857 global $user;
3858
3859 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
3860 if ($mode == 'bynumber') {
3861 $sql .= ", count(DISTINCT f.rowid)";
3862 }
3863 $sql .= ", sum(d.total_ht) as total_ht";
3864 $sql .= " FROM ".$this->db->prefix()."facturedet as d, ".$this->db->prefix()."facture as f, ".$this->db->prefix()."societe as s";
3865 if ($filteronproducttype >= 0) {
3866 $sql .= ", ".$this->db->prefix()."product as p";
3867 }
3868 if (empty($user->rights->societe->client->voir) && !$socid) {
3869 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3870 }
3871 $sql .= " WHERE f.rowid = d.fk_facture";
3872 if ($this->id > 0) {
3873 $sql .= " AND d.fk_product = ".((int) $this->id);
3874 } else {
3875 $sql .= " AND d.fk_product > 0";
3876 }
3877 if ($filteronproducttype >= 0) {
3878 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
3879 }
3880 $sql .= " AND f.fk_soc = s.rowid";
3881 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3882 if (empty($user->rights->societe->client->voir) && !$socid) {
3883 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3884 }
3885 if ($socid > 0) {
3886 $sql .= " AND f.fk_soc = $socid";
3887 }
3888 $sql .= $morefilter;
3889 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
3890 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
3891
3892 return $this->_get_stats($sql, $mode, $year);
3893 }
3894
3895
3896 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3907 public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3908 {
3909 // phpcs:enable
3910 global $conf;
3911 global $user;
3912
3913 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
3914 if ($mode == 'bynumber') {
3915 $sql .= ", count(DISTINCT f.rowid)";
3916 }
3917 $sql .= ", sum(d.total_ht) as total_ht";
3918 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as d, ".$this->db->prefix()."facture_fourn as f, ".$this->db->prefix()."societe as s";
3919 if ($filteronproducttype >= 0) {
3920 $sql .= ", ".$this->db->prefix()."product as p";
3921 }
3922 if (empty($user->rights->societe->client->voir) && !$socid) {
3923 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3924 }
3925 $sql .= " WHERE f.rowid = d.fk_facture_fourn";
3926 if ($this->id > 0) {
3927 $sql .= " AND d.fk_product = ".((int) $this->id);
3928 } else {
3929 $sql .= " AND d.fk_product > 0";
3930 }
3931 if ($filteronproducttype >= 0) {
3932 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
3933 }
3934 $sql .= " AND f.fk_soc = s.rowid";
3935 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
3936 if (empty($user->rights->societe->client->voir) && !$socid) {
3937 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3938 }
3939 if ($socid > 0) {
3940 $sql .= " AND f.fk_soc = $socid";
3941 }
3942 $sql .= $morefilter;
3943 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
3944 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
3945
3946 return $this->_get_stats($sql, $mode, $year);
3947 }
3948
3949 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3960 public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3961 {
3962 // phpcs:enable
3963 global $conf, $user;
3964
3965 $sql = "SELECT sum(d.qty) as qty, date_format(p.datep, '%Y%m')";
3966 if ($mode == 'bynumber') {
3967 $sql .= ", count(DISTINCT p.rowid)";
3968 }
3969 $sql .= ", sum(d.total_ht) as total_ht";
3970 $sql .= " FROM ".$this->db->prefix()."propaldet as d, ".$this->db->prefix()."propal as p, ".$this->db->prefix()."societe as s";
3971 if ($filteronproducttype >= 0) {
3972 $sql .= ", ".$this->db->prefix()."product as prod";
3973 }
3974 if (empty($user->rights->societe->client->voir) && !$socid) {
3975 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3976 }
3977 $sql .= " WHERE p.rowid = d.fk_propal";
3978 if ($this->id > 0) {
3979 $sql .= " AND d.fk_product = ".((int) $this->id);
3980 } else {
3981 $sql .= " AND d.fk_product > 0";
3982 }
3983 if ($filteronproducttype >= 0) {
3984 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
3985 }
3986 $sql .= " AND p.fk_soc = s.rowid";
3987 $sql .= " AND p.entity IN (".getEntity('propal').")";
3988 if (empty($user->rights->societe->client->voir) && !$socid) {
3989 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3990 }
3991 if ($socid > 0) {
3992 $sql .= " AND p.fk_soc = ".((int) $socid);
3993 }
3994 $sql .= $morefilter;
3995 $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
3996 $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
3997
3998 return $this->_get_stats($sql, $mode, $year);
3999 }
4000
4001 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4012 public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4013 {
4014 // phpcs:enable
4015 global $conf;
4016 global $user;
4017
4018 $sql = "SELECT sum(d.qty) as qty, date_format(p.date_valid, '%Y%m')";
4019 if ($mode == 'bynumber') {
4020 $sql .= ", count(DISTINCT p.rowid)";
4021 }
4022 $sql .= ", sum(d.total_ht) as total_ht";
4023 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as d, ".$this->db->prefix()."supplier_proposal as p, ".$this->db->prefix()."societe as s";
4024 if ($filteronproducttype >= 0) {
4025 $sql .= ", ".$this->db->prefix()."product as prod";
4026 }
4027 if (empty($user->rights->societe->client->voir) && !$socid) {
4028 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4029 }
4030 $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
4031 if ($this->id > 0) {
4032 $sql .= " AND d.fk_product = ".((int) $this->id);
4033 } else {
4034 $sql .= " AND d.fk_product > 0";
4035 }
4036 if ($filteronproducttype >= 0) {
4037 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4038 }
4039 $sql .= " AND p.fk_soc = s.rowid";
4040 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
4041 if (empty($user->rights->societe->client->voir) && !$socid) {
4042 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4043 }
4044 if ($socid > 0) {
4045 $sql .= " AND p.fk_soc = ".((int) $socid);
4046 }
4047 $sql .= $morefilter;
4048 $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
4049 $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
4050
4051 return $this->_get_stats($sql, $mode, $year);
4052 }
4053
4054 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4065 public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4066 {
4067 // phpcs:enable
4068 global $conf, $user;
4069
4070 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4071 if ($mode == 'bynumber') {
4072 $sql .= ", count(DISTINCT c.rowid)";
4073 }
4074 $sql .= ", sum(d.total_ht) as total_ht";
4075 $sql .= " FROM ".$this->db->prefix()."commandedet as d, ".$this->db->prefix()."commande as c, ".$this->db->prefix()."societe as s";
4076 if ($filteronproducttype >= 0) {
4077 $sql .= ", ".$this->db->prefix()."product as p";
4078 }
4079 if (empty($user->rights->societe->client->voir) && !$socid) {
4080 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4081 }
4082 $sql .= " WHERE c.rowid = d.fk_commande";
4083 if ($this->id > 0) {
4084 $sql .= " AND d.fk_product = ".((int) $this->id);
4085 } else {
4086 $sql .= " AND d.fk_product > 0";
4087 }
4088 if ($filteronproducttype >= 0) {
4089 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4090 }
4091 $sql .= " AND c.fk_soc = s.rowid";
4092 $sql .= " AND c.entity IN (".getEntity('commande').")";
4093 if (empty($user->rights->societe->client->voir) && !$socid) {
4094 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4095 }
4096 if ($socid > 0) {
4097 $sql .= " AND c.fk_soc = ".((int) $socid);
4098 }
4099 $sql .= $morefilter;
4100 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4101 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4102
4103 return $this->_get_stats($sql, $mode, $year);
4104 }
4105
4106 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4117 public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4118 {
4119 // phpcs:enable
4120 global $conf, $user;
4121
4122 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4123 if ($mode == 'bynumber') {
4124 $sql .= ", count(DISTINCT c.rowid)";
4125 }
4126 $sql .= ", sum(d.total_ht) as total_ht";
4127 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as d, ".$this->db->prefix()."commande_fournisseur as c, ".$this->db->prefix()."societe as s";
4128 if ($filteronproducttype >= 0) {
4129 $sql .= ", ".$this->db->prefix()."product as p";
4130 }
4131 if (empty($user->rights->societe->client->voir) && !$socid) {
4132 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4133 }
4134 $sql .= " WHERE c.rowid = d.fk_commande";
4135 if ($this->id > 0) {
4136 $sql .= " AND d.fk_product = ".((int) $this->id);
4137 } else {
4138 $sql .= " AND d.fk_product > 0";
4139 }
4140 if ($filteronproducttype >= 0) {
4141 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4142 }
4143 $sql .= " AND c.fk_soc = s.rowid";
4144 $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
4145 if (empty($user->rights->societe->client->voir) && !$socid) {
4146 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4147 }
4148 if ($socid > 0) {
4149 $sql .= " AND c.fk_soc = ".((int) $socid);
4150 }
4151 $sql .= $morefilter;
4152 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4153 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4154
4155 return $this->_get_stats($sql, $mode, $year);
4156 }
4157
4158 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4169 public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4170 {
4171 // phpcs:enable
4172 global $conf, $user;
4173
4174 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_contrat, '%Y%m')";
4175 if ($mode == 'bynumber') {
4176 $sql .= ", count(DISTINCT c.rowid)";
4177 }
4178 $sql .= ", sum(d.total_ht) as total_ht";
4179 $sql .= " FROM ".$this->db->prefix()."contratdet as d, ".$this->db->prefix()."contrat as c, ".$this->db->prefix()."societe as s";
4180 if ($filteronproducttype >= 0) {
4181 $sql .= ", ".$this->db->prefix()."product as p";
4182 }
4183 if (empty($user->rights->societe->client->voir) && !$socid) {
4184 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4185 }
4186
4187 $sql .= " WHERE c.entity IN (".getEntity('contract').")";
4188 $sql .= " AND c.rowid = d.fk_contrat";
4189
4190 if ($this->id > 0) {
4191 $sql .= " AND d.fk_product = ".((int) $this->id);
4192 } else {
4193 $sql .= " AND d.fk_product > 0";
4194 }
4195 if ($filteronproducttype >= 0) {
4196 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4197 }
4198 $sql .= " AND c.fk_soc = s.rowid";
4199
4200 if (empty($user->rights->societe->client->voir) && !$socid) {
4201 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4202 }
4203 if ($socid > 0) {
4204 $sql .= " AND c.fk_soc = ".((int) $socid);
4205 }
4206 $sql .= $morefilter;
4207 $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
4208 $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
4209
4210 return $this->_get_stats($sql, $mode, $year);
4211 }
4212
4213 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4224 public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4225 {
4226 // phpcs:enable
4227 global $conf, $user;
4228
4229 $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
4230 if ($mode == 'bynumber') {
4231 $sql .= ", count(DISTINCT d.rowid)";
4232 }
4233 $sql .= " FROM ".$this->db->prefix()."mrp_mo as d LEFT JOIN ".$this->db->prefix()."societe as s ON d.fk_soc = s.rowid";
4234 if ($filteronproducttype >= 0) {
4235 $sql .= ", ".$this->db->prefix()."product as p";
4236 }
4237 if (empty($user->rights->societe->client->voir) && !$socid) {
4238 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4239 }
4240
4241 $sql .= " WHERE d.entity IN (".getEntity('mo').")";
4242 $sql .= " AND d.status > 0";
4243
4244 if ($this->id > 0) {
4245 $sql .= " AND d.fk_product = ".((int) $this->id);
4246 } else {
4247 $sql .= " AND d.fk_product > 0";
4248 }
4249 if ($filteronproducttype >= 0) {
4250 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4251 }
4252
4253 if (empty($user->rights->societe->client->voir) && !$socid) {
4254 $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4255 }
4256 if ($socid > 0) {
4257 $sql .= " AND d.fk_soc = ".((int) $socid);
4258 }
4259 $sql .= $morefilter;
4260 $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
4261 $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
4262
4263 return $this->_get_stats($sql, $mode, $year);
4264 }
4265
4266 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4276 public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1)
4277 {
4278 // phpcs:enable
4279 // Clean parameters
4280 if (!is_numeric($id_pere)) {
4281 $id_pere = 0;
4282 }
4283 if (!is_numeric($id_fils)) {
4284 $id_fils = 0;
4285 }
4286 if (!is_numeric($incdec)) {
4287 $incdec = 0;
4288 }
4289
4290 $result = $this->del_sousproduit($id_pere, $id_fils);
4291 if ($result < 0) {
4292 return $result;
4293 }
4294
4295 // Check not already father of id_pere (to avoid father -> child -> father links)
4296 $sql = "SELECT fk_product_pere from ".$this->db->prefix()."product_association";
4297 $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere);
4298 if (!$this->db->query($sql)) {
4299 dol_print_error($this->db);
4300 return -1;
4301 } else {
4302 //Selection of the highest row
4303 $sql = "SELECT MAX(rang) as max_rank FROM ".$this->db->prefix()."product_association";
4304 $sql .= " WHERE fk_product_pere = ".((int) $id_pere);
4305 $resql = $this->db->query($sql);
4306 if ($resql) {
4307 $obj = $this->db->fetch_object($resql);
4308 $rank = $obj->max_rank + 1;
4309 //Addition of a product with the highest rank +1
4310 $sql = "INSERT INTO ".$this->db->prefix()."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)";
4311 $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".price2num($qty, 'MS').", ".price2num($incdec, 'MS').", ".((int) $rank).")";
4312 if (! $this->db->query($sql)) {
4313 dol_print_error($this->db);
4314 return -1;
4315 } else {
4316 return 1;
4317 }
4318 } else {
4319 dol_print_error($this->db);
4320 return -1;
4321 }
4322 }
4323 }
4324
4325 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4335 public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1)
4336 {
4337 // phpcs:enable
4338 // Clean parameters
4339 if (!is_numeric($id_pere)) {
4340 $id_pere = 0;
4341 }
4342 if (!is_numeric($id_fils)) {
4343 $id_fils = 0;
4344 }
4345 if (!is_numeric($incdec)) {
4346 $incdec = 1;
4347 }
4348 if (!is_numeric($qty)) {
4349 $qty = 1;
4350 }
4351
4352 $sql = 'UPDATE '.$this->db->prefix().'product_association SET ';
4353 $sql .= 'qty = '.price2num($qty, 'MS');
4354 $sql .= ',incdec = '.price2num($incdec, 'MS');
4355 $sql .= ' WHERE fk_product_pere = '.((int) $id_pere).' AND fk_product_fils = '.((int) $id_fils);
4356
4357 if (!$this->db->query($sql)) {
4358 dol_print_error($this->db);
4359 return -1;
4360 } else {
4361 return 1;
4362 }
4363 }
4364
4365 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4373 public function del_sousproduit($fk_parent, $fk_child)
4374 {
4375 // phpcs:enable
4376 if (!is_numeric($fk_parent)) {
4377 $fk_parent = 0;
4378 }
4379 if (!is_numeric($fk_child)) {
4380 $fk_child = 0;
4381 }
4382
4383 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
4384 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4385 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4386
4387 dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
4388 if (!$this->db->query($sql)) {
4389 dol_print_error($this->db);
4390 return -1;
4391 }
4392
4393 // Updated ranks so that none are missing
4394 $sqlrank = "SELECT rowid, rang FROM ".$this->db->prefix()."product_association";
4395 $sqlrank.= " WHERE fk_product_pere = ".((int) $fk_parent);
4396 $sqlrank.= " ORDER BY rang";
4397 $resqlrank = $this->db->query($sqlrank);
4398 if ($resqlrank) {
4399 $cpt = 0;
4400 while ($objrank = $this->db->fetch_object($resqlrank)) {
4401 $cpt++;
4402 $sql = "UPDATE ".$this->db->prefix()."product_association";
4403 $sql.= " SET rang = ".((int) $cpt);
4404 $sql.= " WHERE rowid = ".((int) $objrank->rowid);
4405 if (! $this->db->query($sql)) {
4406 dol_print_error($this->db);
4407 return -1;
4408 }
4409 }
4410 }
4411 return 1;
4412 }
4413
4414 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4422 public function is_sousproduit($fk_parent, $fk_child)
4423 {
4424 // phpcs:enable
4425 $sql = "SELECT fk_product_pere, qty, incdec";
4426 $sql .= " FROM ".$this->db->prefix()."product_association";
4427 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4428 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4429
4430 $result = $this->db->query($sql);
4431 if ($result) {
4432 $num = $this->db->num_rows($result);
4433
4434 if ($num > 0) {
4435 $obj = $this->db->fetch_object($result);
4436
4437 $this->is_sousproduit_qty = $obj->qty;
4438 $this->is_sousproduit_incdec = $obj->incdec;
4439
4440 return true;
4441 } else {
4442 return false;
4443 }
4444 } else {
4445 dol_print_error($this->db);
4446 return -1;
4447 }
4448 }
4449
4450
4451 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4462 public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
4463 {
4464 // phpcs:enable
4465 global $conf;
4466
4467 $now = dol_now();
4468
4469 dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
4470
4471 // Clean parameters
4472 $quantity = price2num($quantity, 'MS');
4473
4474 if ($ref_fourn) {
4475 $sql = "SELECT rowid, fk_product";
4476 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4477 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4478 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4479 $sql .= " AND fk_product <> ".((int) $this->id);
4480 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4481
4482 $resql = $this->db->query($sql);
4483 if ($resql) {
4484 $obj = $this->db->fetch_object($resql);
4485 if ($obj) {
4486 // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
4487 $this->product_id_already_linked = $obj->fk_product;
4488 return -3;
4489 }
4490 $this->db->free($resql);
4491 }
4492 }
4493
4494 $sql = "SELECT rowid";
4495 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4496 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4497 if ($ref_fourn) {
4498 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4499 } else {
4500 $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
4501 }
4502 $sql .= " AND quantity = ".((float) $quantity);
4503 $sql .= " AND fk_product = ".((int) $this->id);
4504 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4505
4506 $resql = $this->db->query($sql);
4507 if ($resql) {
4508 $obj = $this->db->fetch_object($resql);
4509
4510 // The reference supplier does not exist, we create it for this product.
4511 if (empty($obj)) {
4512 $sql = "INSERT INTO ".$this->db->prefix()."product_fournisseur_price(";
4513 $sql .= "datec";
4514 $sql .= ", entity";
4515 $sql .= ", fk_product";
4516 $sql .= ", fk_soc";
4517 $sql .= ", ref_fourn";
4518 $sql .= ", quantity";
4519 $sql .= ", fk_user";
4520 $sql .= ", tva_tx";
4521 $sql .= ") VALUES (";
4522 $sql .= "'".$this->db->idate($now)."'";
4523 $sql .= ", ".$conf->entity;
4524 $sql .= ", ".$this->id;
4525 $sql .= ", ".$id_fourn;
4526 $sql .= ", '".$this->db->escape($ref_fourn)."'";
4527 $sql .= ", ".$quantity;
4528 $sql .= ", ".$user->id;
4529 $sql .= ", 0";
4530 $sql .= ")";
4531
4532 if ($this->db->query($sql)) {
4533 $this->product_fourn_price_id = $this->db->last_insert_id($this->db->prefix()."product_fournisseur_price");
4534 return 1;
4535 } else {
4536 $this->error = $this->db->lasterror();
4537 return -1;
4538 }
4539 } else {
4540 // If the supplier price already exists for this product and quantity
4541 $this->product_fourn_price_id = $obj->rowid;
4542 return 0;
4543 }
4544 } else {
4545 $this->error = $this->db->lasterror();
4546 return -2;
4547 }
4548 }
4549
4550
4551 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4557 public function list_suppliers()
4558 {
4559 // phpcs:enable
4560 global $conf;
4561
4562 $list = array();
4563
4564 $sql = "SELECT DISTINCT p.fk_soc";
4565 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as p";
4566 $sql .= " WHERE p.fk_product = ".((int) $this->id);
4567 $sql .= " AND p.entity = ".((int) $conf->entity);
4568
4569 $result = $this->db->query($sql);
4570 if ($result) {
4571 $num = $this->db->num_rows($result);
4572 $i = 0;
4573 while ($i < $num) {
4574 $obj = $this->db->fetch_object($result);
4575 $list[$i] = $obj->fk_soc;
4576 $i++;
4577 }
4578 }
4579
4580 return $list;
4581 }
4582
4583 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4591 public function clone_price($fromId, $toId)
4592 {
4593 global $conf, $user;
4594
4595 $now = dol_now();
4596
4597 $this->db->begin();
4598
4599 // prices
4600 $sql = "INSERT INTO ".$this->db->prefix()."product_price (";
4601 $sql .= " entity";
4602 $sql .= ", fk_product";
4603 $sql .= ", date_price";
4604 $sql .= ", price_level";
4605 $sql .= ", price";
4606 $sql .= ", price_ttc";
4607 $sql .= ", price_min";
4608 $sql .= ", price_min_ttc";
4609 $sql .= ", price_base_type";
4610 $sql .= ", default_vat_code";
4611 $sql .= ", tva_tx";
4612 $sql .= ", recuperableonly";
4613 $sql .= ", localtax1_tx";
4614 $sql .= ", localtax1_type";
4615 $sql .= ", localtax2_tx";
4616 $sql .= ", localtax2_type";
4617 $sql .= ", fk_user_author";
4618 $sql .= ", tosell";
4619 $sql .= ", price_by_qty";
4620 $sql .= ", fk_price_expression";
4621 $sql .= ", fk_multicurrency";
4622 $sql .= ", multicurrency_code";
4623 $sql .= ", multicurrency_tx";
4624 $sql .= ", multicurrency_price";
4625 $sql .= ", multicurrency_price_ttc";
4626 $sql .= ")";
4627 $sql .= " SELECT";
4628 $sql .= " entity";
4629 $sql .= ", ".$toId;
4630 $sql .= ", '".$this->db->idate($now)."'";
4631 $sql .= ", price_level";
4632 $sql .= ", price";
4633 $sql .= ", price_ttc";
4634 $sql .= ", price_min";
4635 $sql .= ", price_min_ttc";
4636 $sql .= ", price_base_type";
4637 $sql .= ", default_vat_code";
4638 $sql .= ", tva_tx";
4639 $sql .= ", recuperableonly";
4640 $sql .= ", localtax1_tx";
4641 $sql .= ", localtax1_type";
4642 $sql .= ", localtax2_tx";
4643 $sql .= ", localtax2_type";
4644 $sql .= ", ".$user->id;
4645 $sql .= ", tosell";
4646 $sql .= ", price_by_qty";
4647 $sql .= ", fk_price_expression";
4648 $sql .= ", fk_multicurrency";
4649 $sql .= ", multicurrency_code";
4650 $sql .= ", multicurrency_tx";
4651 $sql .= ", multicurrency_price";
4652 $sql .= ", multicurrency_price_ttc";
4653 $sql .= " FROM ".$this->db->prefix()."product_price";
4654 $sql .= " WHERE fk_product = ".((int) $fromId);
4655 $sql .= " ORDER BY date_price DESC";
4656 if ($conf->global->PRODUIT_MULTIPRICES_LIMIT > 0) {
4657 $sql .= " LIMIT ".$conf->global->PRODUIT_MULTIPRICES_LIMIT;
4658 }
4659
4660 dol_syslog(__METHOD__, LOG_DEBUG);
4661 $resql = $this->db->query($sql);
4662 if (!$resql) {
4663 $this->db->rollback();
4664 return -1;
4665 }
4666
4667 $this->db->commit();
4668 return 1;
4669 }
4670
4671 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4679 public function clone_associations($fromId, $toId)
4680 {
4681 // phpcs:enable
4682 $this->db->begin();
4683
4684 $sql = 'INSERT INTO '.$this->db->prefix().'product_association (fk_product_pere, fk_product_fils, qty)';
4685 $sql .= " SELECT ".$toId.", fk_product_fils, qty FROM ".$this->db->prefix()."product_association";
4686 $sql .= " WHERE fk_product_pere = ".((int) $fromId);
4687
4688 dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
4689 if (!$this->db->query($sql)) {
4690 $this->db->rollback();
4691 return -1;
4692 }
4693
4694 $this->db->commit();
4695 return 1;
4696 }
4697
4698 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4706 public function clone_fournisseurs($fromId, $toId)
4707 {
4708 // phpcs:enable
4709 $this->db->begin();
4710
4711 $now = dol_now();
4712
4713 // les fournisseurs
4714 /*$sql = "INSERT ".$this->db->prefix()."product_fournisseur ("
4715 . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
4716 . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
4717 . " FROM ".$this->db->prefix()."product_fournisseur"
4718 . " WHERE fk_product = ".((int) $fromId);
4719
4720 if ( ! $this->db->query($sql ) )
4721 {
4722 $this->db->rollback();
4723 return -1;
4724 }*/
4725
4726 // les prix de fournisseurs.
4727 $sql = "INSERT ".$this->db->prefix()."product_fournisseur_price (";
4728 $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user, tva_tx)";
4729 $sql .= " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, price, quantity, fk_user, tva_tx";
4730 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4731 $sql .= " WHERE fk_product = ".((int) $fromId);
4732
4733 dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
4734 $resql = $this->db->query($sql);
4735 if (!$resql) {
4736 $this->db->rollback();
4737 return -1;
4738 } else {
4739 $this->db->commit();
4740 return 1;
4741 }
4742 }
4743
4744 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4757 public function fetch_prod_arbo($prod, $compl_path = '', $multiply = 1, $level = 1, $id_parent = 0, $ignore_stock_load = 0)
4758 {
4759 // phpcs:enable
4760 global $conf, $langs;
4761
4762 $tmpproduct = null;
4763 //var_dump($prod);
4764 foreach ($prod as $id_product => $desc_pere) { // $id_product is 0 (first call starting with root top) or an id of a sub_product
4765 if (is_array($desc_pere)) { // If desc_pere is an array, this means it's a child
4766 $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
4767 $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
4768 $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
4769 $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
4770 $incdec = (!empty($desc_pere[4]) ? $desc_pere[4] : 0);
4771
4772 if ($multiply < 1) {
4773 $multiply = 1;
4774 }
4775
4776 //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
4777 if (is_null($tmpproduct)) {
4778 $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
4779 }
4780 $tmpproduct->fetch($id); // Load product to get ->ref
4781
4782 if (empty($ignore_stock_load) && ($tmpproduct->isProduct() || !empty($conf->global->STOCK_SUPPORTS_SERVICES))) {
4783 $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
4784 }
4785
4786 $this->res[] = array(
4787 'id'=>$id, // Id product
4788 'id_parent'=>$id_parent,
4789 'ref'=>$tmpproduct->ref, // Ref product
4790 'nb'=>$nb, // Nb of units that compose parent product
4791 'nb_total'=>$nb * $multiply, // Nb of units for all nb of product
4792 'stock'=>$tmpproduct->stock_reel, // Stock
4793 'stock_alert'=>$tmpproduct->seuil_stock_alerte, // Stock alert
4794 'label'=>$label,
4795 'fullpath'=>$compl_path.$label, // Label
4796 'type'=>$type, // Nb of units that compose parent product
4797 'desiredstock'=>$tmpproduct->desiredstock,
4798 'level'=>$level,
4799 'incdec'=>$incdec,
4800 'entity'=>$tmpproduct->entity
4801 );
4802
4803 // Recursive call if there is childs to child
4804 if (isset($desc_pere['childs']) && is_array($desc_pere['childs'])) {
4805 //print 'YYY We go down for '.$desc_pere[3]." -> \n";
4806 $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1] * $multiply, $level + 1, $id, $ignore_stock_load);
4807 }
4808 }
4809 }
4810 }
4811
4812 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4821 public function get_arbo_each_prod($multiply = 1, $ignore_stock_load = 0)
4822 {
4823 // phpcs:enable
4824 $this->res = array();
4825 if (isset($this->sousprods) && is_array($this->sousprods)) {
4826 foreach ($this->sousprods as $prod_name => $desc_product) {
4827 if (is_array($desc_product)) {
4828 $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id, $ignore_stock_load); // This set $this->res
4829 }
4830 }
4831 }
4832 //var_dump($res);
4833 return $this->res;
4834 }
4835
4843 public function hasFatherOrChild($mode = 0)
4844 {
4845 $nb = 0;
4846
4847 $sql = "SELECT COUNT(pa.rowid) as nb";
4848 $sql .= " FROM ".$this->db->prefix()."product_association as pa";
4849 if ($mode == 0) {
4850 $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id)." OR pa.fk_product_pere = ".((int) $this->id);
4851 } elseif ($mode == -1) {
4852 $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)
4853 } elseif ($mode == 1) {
4854 $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)
4855 }
4856
4857 $resql = $this->db->query($sql);
4858 if ($resql) {
4859 $obj = $this->db->fetch_object($resql);
4860 if ($obj) {
4861 $nb = $obj->nb;
4862 }
4863 } else {
4864 return -1;
4865 }
4866
4867 return $nb;
4868 }
4869
4875 public function hasVariants()
4876 {
4877 $nb = 0;
4878 $sql = "SELECT count(rowid) as nb FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_parent = ".((int) $this->id);
4879 $sql .= " AND entity IN (".getEntity('product').")";
4880
4881 $resql = $this->db->query($sql);
4882 if ($resql) {
4883 $obj = $this->db->fetch_object($resql);
4884 if ($obj) {
4885 $nb = $obj->nb;
4886 }
4887 }
4888
4889 return $nb;
4890 }
4891
4892
4898 public function isVariant()
4899 {
4900 global $conf;
4901 if (isModEnabled('variants')) {
4902 $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_child = ".((int) $this->id)." AND entity IN (".getEntity('product').")";
4903
4904 $query = $this->db->query($sql);
4905
4906 if ($query) {
4907 if (!$this->db->num_rows($query)) {
4908 return false;
4909 }
4910 return true;
4911 } else {
4912 dol_print_error($this->db);
4913 return -1;
4914 }
4915 } else {
4916 return false;
4917 }
4918 }
4919
4926 public function getFather()
4927 {
4928 $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";
4929 $sql .= ", p.tosell as status, p.tobuy as status_buy";
4930 $sql .= " FROM ".$this->db->prefix()."product_association as pa,";
4931 $sql .= " ".$this->db->prefix()."product as p";
4932 $sql .= " WHERE p.rowid = pa.fk_product_pere";
4933 $sql .= " AND pa.fk_product_fils = ".((int) $this->id);
4934
4935 $res = $this->db->query($sql);
4936 if ($res) {
4937 $prods = array();
4938 while ($record = $this->db->fetch_array($res)) {
4939 // $record['id'] = $record['rowid'] = id of father
4940 $prods[$record['id']]['id'] = $record['rowid'];
4941 $prods[$record['id']]['ref'] = $record['ref'];
4942 $prods[$record['id']]['label'] = $record['label'];
4943 $prods[$record['id']]['qty'] = $record['qty'];
4944 $prods[$record['id']]['incdec'] = $record['incdec'];
4945 $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
4946 $prods[$record['id']]['entity'] = $record['entity'];
4947 $prods[$record['id']]['status'] = $record['status'];
4948 $prods[$record['id']]['status_buy'] = $record['status_buy'];
4949 }
4950 return $prods;
4951 } else {
4952 dol_print_error($this->db);
4953 return -1;
4954 }
4955 }
4956
4957
4967 public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array())
4968 {
4969 global $alreadyfound;
4970
4971 if (empty($id)) {
4972 return array();
4973 }
4974
4975 $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
4976 $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
4977 $sql .= " pa.rowid as fk_association, pa.rang";
4978 $sql .= " FROM ".$this->db->prefix()."product as p,";
4979 $sql .= " ".$this->db->prefix()."product_association as pa";
4980 $sql .= " WHERE p.rowid = pa.fk_product_fils";
4981 $sql .= " AND pa.fk_product_pere = ".((int) $id);
4982 $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
4983 $sql.= " ORDER BY pa.rang";
4984
4985 dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents)?implode(',', $parents):$parents), LOG_DEBUG);
4986
4987 if ($level == 1) {
4988 $alreadyfound = array($id=>1); // We init array of found object to start of tree, so if we found it later (should not happened), we stop immediatly
4989 }
4990 // Protection against infinite loop
4991 if ($level > 30) {
4992 return array();
4993 }
4994
4995 $res = $this->db->query($sql);
4996 if ($res) {
4997 $prods = array();
4998 while ($rec = $this->db->fetch_array($res)) {
4999 if (!empty($alreadyfound[$rec['rowid']])) {
5000 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);
5001 if (in_array($rec['id'], $parents)) {
5002 continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5003 }
5004 }
5005 $alreadyfound[$rec['rowid']] = 1;
5006 $prods[$rec['rowid']] = array(
5007 0=>$rec['rowid'],
5008 1=>$rec['qty'],
5009 2=>$rec['fk_product_type'],
5010 3=>$this->db->escape($rec['label']),
5011 4=>$rec['incdec'],
5012 5=>$rec['ref'],
5013 6=>$rec['fk_association'],
5014 7=>$rec['rang']
5015 );
5016 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5017 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5018 if (empty($firstlevelonly)) {
5019 $parents[] = $rec['rowid'];
5020 $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents);
5021 foreach ($listofchilds as $keyChild => $valueChild) {
5022 $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5023 }
5024 }
5025 }
5026
5027 return $prods;
5028 } else {
5029 dol_print_error($this->db);
5030 return -1;
5031 }
5032 }
5033
5034 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5041 public function get_sousproduits_arbo()
5042 {
5043 // phpcs:enable
5044 $parent = array();
5045
5046 foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5047 $parent[$this->label][$keyChild] = $valueChild;
5048 }
5049 foreach ($parent as $key => $value) { // key=label, value is array of childs
5050 $this->sousprods[$key] = $value;
5051 }
5052 }
5053
5060 public function getTooltipContentArray($params)
5061 {
5062 global $conf, $langs;
5063
5064 $langs->loadLangs(array('products', 'other'));
5065
5066 $datas = array();
5067 $nofetch = !empty($params['nofetch']);
5068
5069 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
5070 return ['optimize' => $langs->trans("ShowProduct")];
5071 }
5072
5073 if (!empty($this->entity)) {
5074 $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, 1);
5075 if ($this->nbphoto > 0) {
5076 $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5077 }
5078 }
5079
5080 if ($this->type == Product::TYPE_PRODUCT) {
5081 $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5082 } elseif ($this->type == Product::TYPE_SERVICE) {
5083 $datas['picto']= img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5084 }
5085 if (isset($this->status) && isset($this->status_buy)) {
5086 $datas['status']= ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5087 }
5088
5089 if (!empty($this->ref)) {
5090 $datas['ref']= '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5091 }
5092 if (!empty($this->label)) {
5093 $datas['label']= '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label;
5094 }
5095 if ($this->type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
5096 if (isModEnabled('productbatch')) {
5097 $langs->load("productbatch");
5098 $datas['batchstatus']= "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5099 }
5100 }
5101 if (isModEnabled('barcode')) {
5102 $datas['barcode']= '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5103 }
5104
5105 if ($this->type == Product::TYPE_PRODUCT) {
5106 if ($this->weight) {
5107 $datas['weight']= "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5108 }
5109 $labelsize = "";
5110 if ($this->length) {
5111 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5112 }
5113 if ($this->width) {
5114 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5115 }
5116 if ($this->height) {
5117 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5118 }
5119 if ($labelsize) {
5120 $datas['size']= "<br>".$labelsize;
5121 }
5122
5123 $labelsurfacevolume = "";
5124 if ($this->surface) {
5125 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5126 }
5127 if ($this->volume) {
5128 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5129 }
5130 if ($labelsurfacevolume) {
5131 $datas['surface']= "<br>" . $labelsurfacevolume;
5132 }
5133 }
5134 if (!empty($this->pmp) && $this->pmp) {
5135 $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5136 }
5137
5138 if (isModEnabled('accounting')) {
5139 if ($this->status && isset($this->accountancy_code_sell)) {
5140 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5141 $selllabel = '<br>';
5142 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5143 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5144 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5145 $datas['accountancysell'] = $selllabel;
5146 }
5147 if ($this->status_buy && isset($this->accountancy_code_buy)) {
5148 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5149 $buylabel = '';
5150 if (empty($this->status)) {
5151 $buylabel .= '<br>';
5152 }
5153 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5154 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5155 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5156 $datas['accountancybuy'] = $buylabel;
5157 }
5158 }
5159 // show categories for this record only in ajax to not overload lists
5160 if (isModEnabled('categorie') && !$nofetch) {
5161 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5162 $form = new Form($this->db);
5163 $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5164 }
5165
5166 return $datas;
5167 }
5168
5182 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5183 {
5184 global $conf, $langs, $hookmanager;
5185 include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5186
5187 $result = '';
5188
5189 $newref = $this->ref;
5190 if ($maxlength) {
5191 $newref = dol_trunc($newref, $maxlength, 'middle');
5192 }
5193 $params = [
5194 'id' => $this->id,
5195 'objecttype' => (isset($this->type) ? ($this->type == 1 ? 'service' : 'product') : $this->element),
5196 'option' => $option,
5197 'nofetch' => 1,
5198 ];
5199 $classfortooltip = 'classfortooltip';
5200 $dataparams = '';
5201 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5202 $classfortooltip = 'classforajaxtooltip';
5203 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5204 $label = '';
5205 } else {
5206 $label = implode($this->getTooltipContentArray($params));
5207 }
5208
5209 $linkclose = '';
5210 if (empty($notooltip)) {
5211 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
5212 $label = $langs->trans("ShowProduct");
5213 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5214 }
5215 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5216 $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5217 } else {
5218 $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5219 }
5220
5221 if ($option == 'supplier' || $option == 'category') {
5222 $url = DOL_URL_ROOT.'/product/fournisseurs.php?id='.$this->id;
5223 } elseif ($option == 'stock') {
5224 $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5225 } elseif ($option == 'composition') {
5226 $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5227 } else {
5228 $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5229 }
5230
5231 if ($option !== 'nolink') {
5232 // Add param to save lastsearch_values or not
5233 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
5234 if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
5235 $add_save_lastsearch_values = 1;
5236 }
5237 if ($add_save_lastsearch_values) {
5238 $url .= '&save_lastsearch_values=1';
5239 }
5240 }
5241
5242 $linkstart = '<a href="'.$url.'"';
5243 $linkstart .= $linkclose.'>';
5244 $linkend = '</a>';
5245
5246 $result .= $linkstart;
5247 if ($withpicto) {
5248 if ($this->type == Product::TYPE_PRODUCT) {
5249 $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5250 }
5251 if ($this->type == Product::TYPE_SERVICE) {
5252 $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5253 }
5254 }
5255 $result .= dol_escape_htmltag($newref);
5256 $result .= $linkend;
5257 if ($withpicto != 2) {
5258 $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
5259 }
5260
5261 global $action;
5262 $hookmanager->initHooks(array('productdao'));
5263 $parameters = array('id'=>$this->id, 'getnomurl' => &$result, 'label' => &$label);
5264 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5265 if ($reshook > 0) {
5266 $result = $hookmanager->resPrint;
5267 } else {
5268 $result .= $hookmanager->resPrint;
5269 }
5270
5271 return $result;
5272 }
5273
5274
5285 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
5286 {
5287 global $conf, $user, $langs;
5288
5289 $langs->load("products");
5290 $outputlangs->load("products");
5291
5292 // Positionne le modele sur le nom du modele a utiliser
5293 if (!dol_strlen($modele)) {
5294 $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
5295 }
5296
5297 $modelpath = "core/modules/product/doc/";
5298
5299 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
5300 }
5301
5309 public function getLibStatut($mode = 0, $type = 0)
5310 {
5311 switch ($type) {
5312 case 0:
5313 return $this->LibStatut($this->status, $mode, $type);
5314 case 1:
5315 return $this->LibStatut($this->status_buy, $mode, $type);
5316 case 2:
5317 return $this->LibStatut($this->status_batch, $mode, $type);
5318 default:
5319 //Simulate previous behavior but should return an error string
5320 return $this->LibStatut($this->status_buy, $mode, $type);
5321 }
5322 }
5323
5324 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5333 public function LibStatut($status, $mode = 0, $type = 0)
5334 {
5335 // phpcs:enable
5336 global $conf, $langs;
5337
5338 $labelStatus = $labelStatusShort = '';
5339
5340 $langs->load('products');
5341 if (isModEnabled('productbatch')) {
5342 $langs->load("productbatch");
5343 }
5344
5345 if ($type == 2) {
5346 switch ($mode) {
5347 case 0:
5348 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
5349 return dolGetStatus($label);
5350 case 1:
5351 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
5352 return dolGetStatus($label);
5353 case 2:
5354 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
5355 case 3:
5356 return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
5357 case 4:
5358 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
5359 case 5:
5360 return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
5361 default:
5362 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
5363 }
5364 }
5365
5366 $statuttrans = empty($status) ? 'status5' : 'status4';
5367
5368 if ($status == 0) {
5369 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5370 if ($type == 0) {
5371 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
5372 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
5373 } elseif ($type == 1) {
5374 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
5375 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
5376 } elseif ($type == 2) {
5377 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
5378 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
5379 }
5380 } elseif ($status == 1) {
5381 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5382 if ($type == 0) {
5383 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
5384 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
5385 } elseif ($type == 1) {
5386 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
5387 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
5388 } elseif ($type == 2) {
5389 $labelStatus = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial'));
5390 $labelStatusShort = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort'));
5391 }
5392 } elseif ( $type == 2 && $status == 2 ) {
5393 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
5394 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
5395 }
5396
5397 if ($mode > 6) {
5398 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
5399 } else {
5400 return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
5401 }
5402 }
5403
5404
5410 public function getLibFinished()
5411 {
5412 global $langs;
5413 $langs->load('products');
5414
5415 if (isset($this->finished) && $this->finished >= 0) {
5416 $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
5417 $resql = $this->db->query($sql);
5418 if ($resql && $this->db->num_rows($resql) > 0) {
5419 $res = $this->db->fetch_array($resql);
5420 $label = $langs->trans($res['label']);
5421 $this->db->free($resql);
5422 return $label;
5423 } else {
5424 $this->error = $this->db->error().' sql='.$sql;
5425 dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
5426 return -1;
5427 }
5428 }
5429
5430 return '';
5431 }
5432
5433
5434 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5451 public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
5452 {
5453 // phpcs:enable
5454 if ($id_entrepot) {
5455 $this->db->begin();
5456
5457 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5458
5459 if ($nbpiece < 0) {
5460 if (!$movement) {
5461 $movement = 1;
5462 }
5463 $nbpiece = abs($nbpiece);
5464 }
5465
5466 $op[0] = "+".trim($nbpiece);
5467 $op[1] = "-".trim($nbpiece);
5468
5469 $movementstock = new MouvementStock($this->db);
5470 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->origin_id
5471 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
5472
5473 if ($result >= 0) {
5474 if ($extrafields) {
5475 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5476 $movementstock->array_options = $array_options;
5477 $movementstock->insertExtraFields();
5478 }
5479 $this->db->commit();
5480 return 1;
5481 } else {
5482 $this->error = $movementstock->error;
5483 $this->errors = $movementstock->errors;
5484
5485 $this->db->rollback();
5486 return -1;
5487 }
5488 }
5489
5490 return -1;
5491 }
5492
5493 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5513 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)
5514 {
5515 // phpcs:enable
5516 if ($id_entrepot) {
5517 $this->db->begin();
5518
5519 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5520
5521 if ($nbpiece < 0) {
5522 if (!$movement) {
5523 $movement = 1;
5524 }
5525 $nbpiece = abs($nbpiece);
5526 }
5527
5528 $op[0] = "+".trim($nbpiece);
5529 $op[1] = "-".trim($nbpiece);
5530
5531 $movementstock = new MouvementStock($this->db);
5532 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->fk_origin
5533 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct);
5534
5535 if ($result >= 0) {
5536 if ($extrafields) {
5537 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5538 $movementstock->array_options = $array_options;
5539 $movementstock->insertExtraFields();
5540 }
5541 $this->db->commit();
5542 return 1;
5543 } else {
5544 $this->error = $movementstock->error;
5545 $this->errors = $movementstock->errors;
5546
5547 $this->db->rollback();
5548 return -1;
5549 }
5550 }
5551 return -1;
5552 }
5553
5554 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5567 public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
5568 {
5569 // phpcs:enable
5570 global $conf;
5571
5572 $this->stock_reel = 0;
5573 $this->stock_warehouse = array();
5574 $this->stock_theorique = 0;
5575
5576 // Set filter on warehouse status
5577 $warehouseStatus = array();
5578 if (preg_match('/warehouseclosed/', $option)) {
5580 }
5581 if (preg_match('/warehouseopen/', $option)) {
5583 }
5584 if (preg_match('/warehouseinternal/', $option)) {
5585 if (!empty($conf->global->ENTREPOT_EXTRA_STATUS)) {
5587 } else {
5589 }
5590 }
5591
5592 $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
5593 $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
5594 $sql .= ", ".$this->db->prefix()."entrepot as w";
5595 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
5596 $sql .= " AND w.rowid = ps.fk_entrepot";
5597 $sql .= " AND ps.fk_product = ".((int) $this->id);
5598 if (count($warehouseStatus)) {
5599 $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
5600 }
5601
5602 $sql .= " ORDER BY ps.reel ".(!empty($conf->global->DO_NOT_TRY_TO_DEFRAGMENT_STOCKS_WAREHOUSE)?'DESC':'ASC'); // Note : qty ASC is important for expedition card, to avoid stock fragmentation;
5603
5604 dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
5605 $result = $this->db->query($sql);
5606 if ($result) {
5607 $num = $this->db->num_rows($result);
5608 $i = 0;
5609 if ($num > 0) {
5610 while ($i < $num) {
5611 $row = $this->db->fetch_object($result);
5612 $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
5613 $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
5614 $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
5615 if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
5616 $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
5617 }
5618 $this->stock_reel += $row->reel;
5619 $i++;
5620 }
5621 }
5622 $this->db->free($result);
5623
5624 if (!preg_match('/novirtual/', $option)) {
5625 $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
5626 }
5627
5628 return 1;
5629 } else {
5630 $this->error = $this->db->lasterror();
5631 return -1;
5632 }
5633 }
5634
5635
5636 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5646 public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
5647 {
5648 // phpcs:enable
5649 global $conf, $hookmanager, $action;
5650
5651 $stock_commande_client = 0;
5652 $stock_commande_fournisseur = 0;
5653 $stock_sending_client = 0;
5654 $stock_reception_fournisseur = 0;
5655 $stock_inproduction = 0;
5656
5657 //dol_syslog("load_virtual_stock");
5658
5659 if (isModEnabled('commande')) {
5660 $result = $this->load_stats_commande(0, '1,2', 1);
5661 if ($result < 0) {
5662 dol_print_error($this->db, $this->error);
5663 }
5664 $stock_commande_client = $this->stats_commande['qty'];
5665 }
5666 if (isModEnabled("expedition")) {
5667 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
5668 $filterShipmentStatus = '';
5669 if (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT)) {
5670 $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
5671 } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
5672 $filterShipmentStatus = Expedition::STATUS_CLOSED;
5673 }
5674 $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
5675 if ($result < 0) {
5676 dol_print_error($this->db, $this->error);
5677 }
5678 $stock_sending_client = $this->stats_expedition['qty'];
5679 }
5680 if (isModEnabled("supplier_order")) {
5681 $filterStatus = empty($conf->global->SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK) ? '3,4' : $conf->global->SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK;
5682 if (isset($includedraftpoforvirtual)) {
5683 $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
5684 }
5685 $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
5686 if ($result < 0) {
5687 dol_print_error($this->db, $this->error);
5688 }
5689 $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
5690 }
5691 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && empty($conf->reception->enabled)) {
5692 // Case module reception is not used
5693 $filterStatus = '4';
5694 if (isset($includedraftpoforvirtual)) {
5695 $filterStatus = '0,'.$filterStatus;
5696 }
5697 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
5698 if ($result < 0) {
5699 dol_print_error($this->db, $this->error);
5700 }
5701 $stock_reception_fournisseur = $this->stats_reception['qty'];
5702 }
5703 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && isModEnabled("reception")) {
5704 // Case module reception is used
5705 $filterStatus = '4';
5706 if (isset($includedraftpoforvirtual)) {
5707 $filterStatus = '0,'.$filterStatus;
5708 }
5709 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock); // Use same tables than when module reception is not used.
5710 if ($result < 0) {
5711 dol_print_error($this->db, $this->error);
5712 }
5713 $stock_reception_fournisseur = $this->stats_reception['qty'];
5714 }
5715 if (isModEnabled('mrp')) {
5716 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
5717 if ($result < 0) {
5718 dol_print_error($this->db, $this->error);
5719 }
5720 $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
5721 }
5722
5723 $this->stock_theorique = $this->stock_reel + $stock_inproduction;
5724
5725 // Stock decrease mode
5726 if (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
5727 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
5728 } elseif (!empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER)) {
5729 $this->stock_theorique += 0;
5730 } elseif (!empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
5731 $this->stock_theorique -= $stock_commande_client;
5732 }
5733 // Stock Increase mode
5734 if (!empty($conf->global->STOCK_CALCULATE_ON_RECEPTION) || !empty($conf->global->STOCK_CALCULATE_ON_RECEPTION_CLOSE)) {
5735 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5736 } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER)) {
5737 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5738 } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER)) {
5739 $this->stock_theorique -= $stock_reception_fournisseur;
5740 } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_BILL)) {
5741 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5742 }
5743
5744 $hookmanager->initHooks(array('productdao'));
5745 $parameters = array('id'=>$this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
5746 // Note that $action and $object may have been modified by some hooks
5747 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
5748 if ($reshook > 0) {
5749 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
5750 }
5751
5752 return 1;
5753 }
5754
5755
5763 public function loadBatchInfo($batch)
5764 {
5765 $result = array();
5766
5767 $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";
5768 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
5769 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
5770 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
5771 $resql = $this->db->query($sql);
5772 if ($resql) {
5773 $num = $this->db->num_rows($resql);
5774 $i = 0;
5775 while ($i < $num) {
5776 $obj = $this->db->fetch_object($resql);
5777 $result[] = array('batch'=>$batch, 'eatby'=>$this->db->jdate($obj->eatby), 'sellby'=>$this->db->jdate($obj->sellby), 'qty'=>$obj->qty);
5778 $i++;
5779 }
5780 return $result;
5781 } else {
5782 dol_print_error($this->db);
5783 $this->db->rollback();
5784 return array();
5785 }
5786 }
5787
5788 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5796 public function add_photo($sdir, $file)
5797 {
5798 // phpcs:enable
5799 global $conf;
5800
5801 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5802
5803 $result = 0;
5804
5805 $dir = $sdir;
5806 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5807 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
5808 } else {
5809 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
5810 }
5811
5812 dol_mkdir($dir);
5813
5814 $dir_osencoded = $dir;
5815
5816 if (is_dir($dir_osencoded)) {
5817 $originImage = $dir.'/'.$file['name'];
5818
5819 // Cree fichier en taille origine
5820 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
5821
5822 if (file_exists(dol_osencode($originImage))) {
5823 // Create thumbs
5824 $this->addThumbs($originImage);
5825 }
5826 }
5827
5828 if (is_numeric($result) && $result > 0) {
5829 return 1;
5830 } else {
5831 return -1;
5832 }
5833 }
5834
5835 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5842 public function is_photo_available($sdir)
5843 {
5844 // phpcs:enable
5845 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5846 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5847
5848 global $conf;
5849
5850 $dir = $sdir;
5851 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5852 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
5853 } else {
5854 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
5855 }
5856
5857 $nbphoto = 0;
5858
5859 $dir_osencoded = dol_osencode($dir);
5860 if (file_exists($dir_osencoded)) {
5861 $handle = opendir($dir_osencoded);
5862 if (is_resource($handle)) {
5863 while (($file = readdir($handle)) !== false) {
5864 if (!utf8_check($file)) {
5865 $file = utf8_encode($file); // To be sure data is stored in UTF8 in memory
5866 }
5867 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
5868 return true;
5869 }
5870 }
5871 }
5872 }
5873 return false;
5874 }
5875
5876 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5884 public function liste_photos($dir, $nbmax = 0)
5885 {
5886 // phpcs:enable
5887 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5888 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5889
5890 $nbphoto = 0;
5891 $tabobj = array();
5892
5893 $dir_osencoded = dol_osencode($dir);
5894 $handle = @opendir($dir_osencoded);
5895 if (is_resource($handle)) {
5896 while (($file = readdir($handle)) !== false) {
5897 if (!utf8_check($file)) {
5898 $file = utf8_encode($file); // readdir returns ISO
5899 }
5900 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
5901 $nbphoto++;
5902
5903 // We forge name of thumb.
5904 $photo = $file;
5905 $photo_vignette = '';
5906 $regs = array();
5907 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
5908 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
5909 }
5910
5911 $dirthumb = $dir.'thumbs/';
5912
5913 // Objet
5914 $obj = array();
5915 $obj['photo'] = $photo;
5916 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
5917 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
5918 } else {
5919 $obj['photo_vignette'] = "";
5920 }
5921
5922 $tabobj[$nbphoto - 1] = $obj;
5923
5924 // Do we have to continue with next photo ?
5925 if ($nbmax && $nbphoto >= $nbmax) {
5926 break;
5927 }
5928 }
5929 }
5930
5931 closedir($handle);
5932 }
5933
5934 return $tabobj;
5935 }
5936
5937 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5944 public function delete_photo($file)
5945 {
5946 // phpcs:enable
5947 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5948 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5949
5950 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
5951 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
5952 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
5953
5954 // On efface l'image d'origine
5955 dol_delete_file($file, 0, 0, 0, $this); // For triggers
5956
5957 // Si elle existe, on efface la vignette
5958 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
5959 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
5960 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
5961 dol_delete_file($dirthumb.$photo_vignette);
5962 }
5963
5964 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
5965 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
5966 dol_delete_file($dirthumb.$photo_vignette);
5967 }
5968 }
5969 }
5970
5971 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5978 public function get_image_size($file)
5979 {
5980 // phpcs:enable
5981 $file_osencoded = dol_osencode($file);
5982 $infoImg = getimagesize($file_osencoded); // Get information on image
5983 $this->imgWidth = $infoImg[0]; // Largeur de l'image
5984 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
5985 }
5986
5987 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5993 public function load_state_board()
5994 {
5995 // phpcs:enable
5996 global $hookmanager;
5997
5998 $this->nb = array();
5999
6000 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6001 $sql .= " FROM ".$this->db->prefix()."product as p";
6002 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6003 // Add where from hooks
6004 if (is_object($hookmanager)) {
6005 $parameters = array();
6006 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6007 $sql .= $hookmanager->resPrint;
6008 }
6009 $sql .= ' GROUP BY fk_product_type';
6010
6011 $resql = $this->db->query($sql);
6012 if ($resql) {
6013 while ($obj = $this->db->fetch_object($resql)) {
6014 if ($obj->fk_product_type == 1) {
6015 $this->nb["services"] = $obj->nb;
6016 } else {
6017 $this->nb["products"] = $obj->nb;
6018 }
6019 }
6020 $this->db->free($resql);
6021 return 1;
6022 } else {
6023 dol_print_error($this->db);
6024 $this->error = $this->db->error();
6025 return -1;
6026 }
6027 }
6028
6034 public function isProduct()
6035 {
6036 return ($this->type == Product::TYPE_PRODUCT ? true : false);
6037 }
6038
6044 public function isService()
6045 {
6046 return ($this->type == Product::TYPE_SERVICE ? true : false);
6047 }
6048
6049
6055 public function isMandatoryPeriod()
6056 {
6057 return ($this->mandatory_period == 1 ? true : false);
6058 }
6059 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6068 public function get_barcode($object, $type = '')
6069 {
6070 // phpcs:enable
6071 global $conf;
6072
6073 $result = '';
6074 if (!empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
6075 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6076 foreach ($dirsociete as $dirroot) {
6077 $res = dol_include_once($dirroot.$conf->global->BARCODE_PRODUCT_ADDON_NUM.'.php');
6078 if ($res) {
6079 break;
6080 }
6081 }
6082 $var = $conf->global->BARCODE_PRODUCT_ADDON_NUM;
6083 $mod = new $var;
6084
6085 $result = $mod->getNextValue($object, $type);
6086
6087 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6088 }
6089 return $result;
6090 }
6091
6099 public function initAsSpecimen()
6100 {
6101 global $user, $langs, $conf, $mysoc;
6102
6103 $now = dol_now();
6104
6105 // Initialize parameters
6106 $this->specimen = 1;
6107 $this->id = 0;
6108 $this->ref = 'PRODUCT_SPEC';
6109 $this->label = 'PRODUCT SPECIMEN';
6110 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
6111 $this->specimen = 1;
6112 $this->country_id = 1;
6113 $this->status = 1;
6114 $this->status_buy = 1;
6115 $this->tobatch = 0;
6116 $this->note_private = 'This is a comment (private)';
6117 $this->note_public = 'This is a comment (public)';
6118 $this->date_creation = $now;
6119 $this->date_modification = $now;
6120
6121 $this->weight = 4;
6122 $this->weight_units = 3;
6123
6124 $this->length = 5;
6125 $this->length_units = 1;
6126 $this->width = 6;
6127 $this->width_units = 0;
6128 $this->height = null;
6129 $this->height_units = null;
6130
6131 $this->surface = 30;
6132 $this->surface_units = 0;
6133 $this->volume = 300;
6134 $this->volume_units = 0;
6135
6136 $this->barcode = -1; // Create barcode automatically
6137 }
6138
6145 public function getLabelOfUnit($type = 'long')
6146 {
6147 global $langs;
6148
6149 if (!$this->fk_unit) {
6150 return '';
6151 }
6152
6153 $langs->load('products');
6154
6155 $label_type = 'label';
6156 if ($type == 'short') {
6157 $label_type = 'short_label';
6158 }
6159
6160 $sql = "SELECT ".$label_type.", code from ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
6161
6162 $resql = $this->db->query($sql);
6163 if ($resql && $this->db->num_rows($resql) > 0) {
6164 $res = $this->db->fetch_array($resql);
6165 $label = ($label_type == 'short_label' ? $res[$label_type] : 'unit'.$res['code']);
6166 $this->db->free($resql);
6167 return $label;
6168 } else {
6169 $this->error = $this->db->error();
6170 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
6171 return -1;
6172 }
6173 }
6174
6180 public function hasbatch()
6181 {
6182 return ($this->status_batch > 0 ? true : false);
6183 }
6184
6185
6186 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6192 public function min_recommended_price()
6193 {
6194 // phpcs:enable
6195 global $conf;
6196
6197 $maxpricesupplier = 0;
6198
6199 if (!empty($conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE)) {
6200 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6201 $product_fourn = new ProductFournisseur($this->db);
6202 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
6203
6204 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
6205 foreach ($product_fourn_list as $productfourn) {
6206 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
6207 $maxpricesupplier = $productfourn->fourn_unitprice;
6208 }
6209 }
6210
6211 $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
6212 }
6213 }
6214
6215 return $maxpricesupplier;
6216 }
6217
6218
6229 public function setCategories($categories)
6230 {
6231 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
6232 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
6233 }
6234
6243 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
6244 {
6245 $tables = array(
6246 'product_customer_price',
6247 'product_customer_price_log'
6248 );
6249
6250 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
6251 }
6252
6264 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
6265 {
6266 global $conf, $db;
6267
6268 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
6269 $query = $this->db->query($sql);
6270
6271 $rules = array();
6272
6273 while ($result = $this->db->fetch_object($query)) {
6274 $rules[$result->level] = $result;
6275 }
6276
6277 //Because prices can be based on other level's prices, we temporarily store them
6278 $prices = array(
6279 1 => $baseprice
6280 );
6281
6282 for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
6283 $price = $baseprice;
6284 $price_min = $baseprice;
6285
6286 //We have to make sure it does exist and it is > 0
6287 //First price level only allows changing min_price
6288 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
6289 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
6290 }
6291
6292 $prices[$i] = $price;
6293
6294 //We have to make sure it does exist and it is > 0
6295 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
6296 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
6297 }
6298
6299 //Little check to make sure the price is modified before triggering generation
6300 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
6301 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
6302
6303 if ($check_amount && $check_type) {
6304 continue;
6305 }
6306
6307 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
6308 return -1;
6309 }
6310 }
6311
6312 return 1;
6313 }
6314
6320 public function getRights()
6321 {
6322 global $user;
6323
6324 if ($this->isProduct()) {
6325 return $user->rights->produit;
6326 } else {
6327 return $user->rights->service;
6328 }
6329 }
6330
6337 public function info($id)
6338 {
6339 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
6340 $sql .= " p.fk_user_author, p.fk_user_modif";
6341 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
6342 $sql .= " WHERE p.rowid = ".((int) $id);
6343
6344 $result = $this->db->query($sql);
6345 if ($result) {
6346 if ($this->db->num_rows($result)) {
6347 $obj = $this->db->fetch_object($result);
6348
6349 $this->id = $obj->rowid;
6350
6351 if ($obj->fk_user_author) {
6352 $cuser = new User($this->db);
6353 $cuser->fetch($obj->fk_user_author);
6354 $this->user_creation = $cuser;
6355 }
6356
6357 if ($obj->fk_user_modif) {
6358 $muser = new User($this->db);
6359 $muser->fetch($obj->fk_user_modif);
6360 $this->user_modification = $muser;
6361 }
6362
6363 $this->ref = $obj->ref;
6364 $this->date_creation = $this->db->jdate($obj->date_creation);
6365 $this->date_modification = $this->db->jdate($obj->date_modification);
6366 }
6367
6368 $this->db->free($result);
6369 } else {
6370 dol_print_error($this->db);
6371 }
6372 }
6373
6374
6379 public function getProductDurationHours()
6380 {
6381 global $langs;
6382
6383 if (empty($this->duration_value)) {
6384 $this->errors[]='ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
6385 return -1;
6386 }
6387
6388 if ($this->duration_unit == 'i') {
6389 $prodDurationHours = 1. / 60;
6390 }
6391 if ($this->duration_unit == 'h') {
6392 $prodDurationHours = 1.;
6393 }
6394 if ($this->duration_unit == 'd') {
6395 $prodDurationHours = 24.;
6396 }
6397 if ($this->duration_unit == 'w') {
6398 $prodDurationHours = 24. * 7;
6399 }
6400 if ($this->duration_unit == 'm') {
6401 $prodDurationHours = 24. * 30;
6402 }
6403 if ($this->duration_unit == 'y') {
6404 $prodDurationHours = 24. * 365;
6405 }
6406 $prodDurationHours *= $this->duration_value;
6407
6408 return $prodDurationHours;
6409 }
6410
6411
6419 public function getKanbanView($option = '', $arraydata = null)
6420 {
6421 global $langs,$conf;
6422
6423 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6424
6425 $return = '<div class="box-flex-item box-flex-grow-zero">';
6426 $return .= '<div class="info-box info-box-sm">';
6427 $return .= '<div class="info-box-img">';
6428 $label = '';
6429 if ($this->is_photo_available($conf->product->multidir_output[$this->entity])) {
6430 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
6431 $return .= $label;
6432 } else {
6433 if ($this->type == Product::TYPE_PRODUCT) {
6434 $label .= img_picto('', 'product');
6435 } elseif ($this->type == Product::TYPE_SERVICE) {
6436 $label .= img_picto('', 'service');
6437 }
6438 $return .= $label;
6439 }
6440 $return .= '</div>';
6441 $return .= '<div class="info-box-content">';
6442 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
6443 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
6444 if (property_exists($this, 'label')) {
6445 $return .= '<br><span class="info-box-label opacitymedium">'.$this->label.'</span>';
6446 }
6447 if (property_exists($this, 'price') && property_exists($this, 'price_ttc')) {
6448 if ($this->price_base_type == 'TTC') {
6449 $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
6450 } else {
6451 if ($this->status) {
6452 $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
6453 }
6454 }
6455 }
6456 if (property_exists($this, 'stock_reel')) {
6457 $return .= '<br><span class="info-box-status opacitymedium">'.$langs->trans('PhysicalStock').' : <span class="bold">'.$this->stock_reel.'</span></span>';
6458 }
6459 if (method_exists($this, 'getLibStatut')) {
6460 $return .='<br><span class="info-box-status margintoponly">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</span>';
6461 }
6462 $return .= '</div>';
6463 $return .= '</div>';
6464 $return .= '</div>';
6465 return $return;
6466 }
6467}
6468
6474{
6475 public $picto = 'service';
6476}
length_accountg($account)
Return General accounting account with defined length (used for product and miscellaneous)
$object ref
Definition info.php:78
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.
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.
$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.
correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $dlc='', $dluo='', $lot='', $inventorycode='', $origin_element='', $origin_id=null, $disablestockchangeforsubproduct=0, $extrafields=null)
Adjust stock in a warehouse for product with batch number.
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_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
Add a supplier price for the product.
hasFatherOrChild($mode=0)
Count all parent and children products for current product (first level only)
load_stats_facturerec($socid=0)
Charge tableau des stats facture recurrentes pour le produit/service.
$product_id_already_linked
Product ID already linked to a reference supplier.
get_nb_propalsupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in proposals in which product is included.
get_nb_contract($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
load_stats_facture_fournisseur($socid=0)
Charge tableau des stats facture pour le produit/service.
get_nb_ordersupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
getMultiLangs()
Load array this->multilangs.
get_nb_mos($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
clone_associations($fromId, $toId)
Clone links between products.
create($user, $notrigger=0)
Insert product into database.
load_stats_contrat($socid=0)
Charge tableau des stats contrat pour le produit/service.
isService()
Return if object is a product.
getRights()
Returns the rights used for this class.
loadBatchInfo($batch)
Load existing information about a serial.
$pmp
Average price value for product entry into stock (PMP)
load_stock($option='', $includedraftpoforvirtual=null, $dateofvirtualstock=null)
Load information about stock of a product into ->stock_reel, ->stock_warehouse[] (including stock_war...
getProductDurationHours()
Return the duration 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_sousproduit($id_pere, $id_fils, $qty, $incdec=1)
Modify composed product.
update($id, $user, $notrigger=false, $action='update', $updatetype=false)
Update a record into database.
info($id)
Load information for tab info.
const TYPE_STOCKKIT
Advanced feature: stock kit.
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.
$tva_npr
int French VAT NPR (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.
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.
updatePrice($newprice, $newpricebase, $user, $newvat='', $newminprice=0, $level=0, $newnpr=0, $newpbq=0, $ignore_autogen=0, $localtaxes_array=array(), $newdefaultvatcode='')
Modify customer price of a product/Service for a given level.
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 clicable link of object (with eventually picto)
$product_fourn_id
Id du fournisseur.
$desiredstock
Ask for replenishment when $desiredstock < $stock_reel.
add_sousproduit($id_pere, $id_fils, $qty, $incdec=1)
Link a product/service to a parent product/service.
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.
del_sousproduit($fk_parent, $fk_child)
Remove a link between a subproduct and a parent product/service.
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
const TYPE_ASSEMBLYKIT
Advanced feature: assembly kit.
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.
File of class to manage predefined price products or services by customer.
Class to manage Dolibarr users.
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 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)
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
measuringUnitString($unit, $measuring_style='', $scale='', $use_short_label=0, $outputlangs=null)
Return translation label of a unit key.
measuring_units_squared($unit)
Transform a given unit scale into the square of that unit, if known.
measuring_units_cubed($unit)
Transform a given unit scale into the cube of that unit, if known.
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:120