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 $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, array_push($parents, $rec['rowid']));
5020 foreach ($listofchilds as $keyChild => $valueChild) {
5021 $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5022 }
5023 }
5024 }
5025
5026 return $prods;
5027 } else {
5028 dol_print_error($this->db);
5029 return -1;
5030 }
5031 }
5032
5033 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5040 public function get_sousproduits_arbo()
5041 {
5042 // phpcs:enable
5043 $parent = array();
5044
5045 foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5046 $parent[$this->label][$keyChild] = $valueChild;
5047 }
5048 foreach ($parent as $key => $value) { // key=label, value is array of childs
5049 $this->sousprods[$key] = $value;
5050 }
5051 }
5052
5059 public function getTooltipContentArray($params)
5060 {
5061 global $conf, $langs;
5062
5063 $langs->loadLangs(array('products', 'other'));
5064
5065 $datas = array();
5066 $nofetch = !empty($params['nofetch']);
5067
5068 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
5069 return ['optimize' => $langs->trans("ShowProduct")];
5070 }
5071
5072 if (!empty($this->entity)) {
5073 $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, 1);
5074 if ($this->nbphoto > 0) {
5075 $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5076 }
5077 }
5078
5079 if ($this->type == Product::TYPE_PRODUCT) {
5080 $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5081 } elseif ($this->type == Product::TYPE_SERVICE) {
5082 $datas['picto']= img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5083 }
5084 if (isset($this->status) && isset($this->status_buy)) {
5085 $datas['status']= ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5086 }
5087
5088 if (!empty($this->ref)) {
5089 $datas['ref']= '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5090 }
5091 if (!empty($this->label)) {
5092 $datas['label']= '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label;
5093 }
5094 if ($this->type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
5095 if (isModEnabled('productbatch')) {
5096 $langs->load("productbatch");
5097 $datas['batchstatus']= "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5098 }
5099 }
5100 if (isModEnabled('barcode')) {
5101 $datas['barcode']= '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5102 }
5103
5104 if ($this->type == Product::TYPE_PRODUCT) {
5105 if ($this->weight) {
5106 $datas['weight']= "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5107 }
5108 $labelsize = "";
5109 if ($this->length) {
5110 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5111 }
5112 if ($this->width) {
5113 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5114 }
5115 if ($this->height) {
5116 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5117 }
5118 if ($labelsize) {
5119 $datas['size']= "<br>".$labelsize;
5120 }
5121
5122 $labelsurfacevolume = "";
5123 if ($this->surface) {
5124 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5125 }
5126 if ($this->volume) {
5127 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5128 }
5129 if ($labelsurfacevolume) {
5130 $datas['surface']= "<br>" . $labelsurfacevolume;
5131 }
5132 }
5133 if (!empty($this->pmp) && $this->pmp) {
5134 $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5135 }
5136
5137 if (isModEnabled('accounting')) {
5138 if ($this->status && isset($this->accountancy_code_sell)) {
5139 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5140 $selllabel = '<br>';
5141 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5142 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5143 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5144 $datas['accountancysell'] = $selllabel;
5145 }
5146 if ($this->status_buy && isset($this->accountancy_code_buy)) {
5147 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5148 $buylabel = '';
5149 if (empty($this->status)) {
5150 $buylabel .= '<br>';
5151 }
5152 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5153 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5154 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5155 $datas['accountancybuy'] = $buylabel;
5156 }
5157 }
5158 // show categories for this record only in ajax to not overload lists
5159 if (isModEnabled('categorie') && !$nofetch) {
5160 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5161 $form = new Form($this->db);
5162 $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5163 }
5164
5165 return $datas;
5166 }
5167
5181 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5182 {
5183 global $conf, $langs, $hookmanager;
5184 include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5185
5186 $result = '';
5187
5188 $newref = $this->ref;
5189 if ($maxlength) {
5190 $newref = dol_trunc($newref, $maxlength, 'middle');
5191 }
5192 $params = [
5193 'id' => $this->id,
5194 'objecttype' => (isset($this->type) ? ($this->type == 1 ? 'service' : 'product') : $this->element),
5195 'option' => $option,
5196 'nofetch' => 1,
5197 ];
5198 $classfortooltip = 'classfortooltip';
5199 $dataparams = '';
5200 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5201 $classfortooltip = 'classforajaxtooltip';
5202 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5203 $label = '';
5204 } else {
5205 $label = implode($this->getTooltipContentArray($params));
5206 }
5207
5208 $linkclose = '';
5209 if (empty($notooltip)) {
5210 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
5211 $label = $langs->trans("ShowProduct");
5212 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5213 }
5214 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5215 $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5216 } else {
5217 $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5218 }
5219
5220 if ($option == 'supplier' || $option == 'category') {
5221 $url = DOL_URL_ROOT.'/product/fournisseurs.php?id='.$this->id;
5222 } elseif ($option == 'stock') {
5223 $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5224 } elseif ($option == 'composition') {
5225 $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5226 } else {
5227 $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5228 }
5229
5230 if ($option !== 'nolink') {
5231 // Add param to save lastsearch_values or not
5232 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
5233 if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
5234 $add_save_lastsearch_values = 1;
5235 }
5236 if ($add_save_lastsearch_values) {
5237 $url .= '&save_lastsearch_values=1';
5238 }
5239 }
5240
5241 $linkstart = '<a href="'.$url.'"';
5242 $linkstart .= $linkclose.'>';
5243 $linkend = '</a>';
5244
5245 $result .= $linkstart;
5246 if ($withpicto) {
5247 if ($this->type == Product::TYPE_PRODUCT) {
5248 $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5249 }
5250 if ($this->type == Product::TYPE_SERVICE) {
5251 $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5252 }
5253 }
5254 $result .= dol_escape_htmltag($newref);
5255 $result .= $linkend;
5256 if ($withpicto != 2) {
5257 $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
5258 }
5259
5260 global $action;
5261 $hookmanager->initHooks(array('productdao'));
5262 $parameters = array('id'=>$this->id, 'getnomurl' => &$result, 'label' => &$label);
5263 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5264 if ($reshook > 0) {
5265 $result = $hookmanager->resPrint;
5266 } else {
5267 $result .= $hookmanager->resPrint;
5268 }
5269
5270 return $result;
5271 }
5272
5273
5284 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
5285 {
5286 global $conf, $user, $langs;
5287
5288 $langs->load("products");
5289 $outputlangs->load("products");
5290
5291 // Positionne le modele sur le nom du modele a utiliser
5292 if (!dol_strlen($modele)) {
5293 $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
5294 }
5295
5296 $modelpath = "core/modules/product/doc/";
5297
5298 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
5299 }
5300
5308 public function getLibStatut($mode = 0, $type = 0)
5309 {
5310 switch ($type) {
5311 case 0:
5312 return $this->LibStatut($this->status, $mode, $type);
5313 case 1:
5314 return $this->LibStatut($this->status_buy, $mode, $type);
5315 case 2:
5316 return $this->LibStatut($this->status_batch, $mode, $type);
5317 default:
5318 //Simulate previous behavior but should return an error string
5319 return $this->LibStatut($this->status_buy, $mode, $type);
5320 }
5321 }
5322
5323 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5332 public function LibStatut($status, $mode = 0, $type = 0)
5333 {
5334 // phpcs:enable
5335 global $conf, $langs;
5336
5337 $labelStatus = $labelStatusShort = '';
5338
5339 $langs->load('products');
5340 if (isModEnabled('productbatch')) {
5341 $langs->load("productbatch");
5342 }
5343
5344 if ($type == 2) {
5345 switch ($mode) {
5346 case 0:
5347 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
5348 return dolGetStatus($label);
5349 case 1:
5350 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
5351 return dolGetStatus($label);
5352 case 2:
5353 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
5354 case 3:
5355 return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
5356 case 4:
5357 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
5358 case 5:
5359 return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
5360 default:
5361 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
5362 }
5363 }
5364
5365 $statuttrans = empty($status) ? 'status5' : 'status4';
5366
5367 if ($status == 0) {
5368 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5369 if ($type == 0) {
5370 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
5371 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
5372 } elseif ($type == 1) {
5373 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
5374 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
5375 } elseif ($type == 2) {
5376 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
5377 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
5378 }
5379 } elseif ($status == 1) {
5380 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5381 if ($type == 0) {
5382 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
5383 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
5384 } elseif ($type == 1) {
5385 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
5386 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
5387 } elseif ($type == 2) {
5388 $labelStatus = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial'));
5389 $labelStatusShort = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort'));
5390 }
5391 } elseif ( $type == 2 && $status == 2 ) {
5392 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
5393 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
5394 }
5395
5396 if ($mode > 6) {
5397 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
5398 } else {
5399 return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
5400 }
5401 }
5402
5403
5409 public function getLibFinished()
5410 {
5411 global $langs;
5412 $langs->load('products');
5413
5414 if (isset($this->finished) && $this->finished >= 0) {
5415 $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
5416 $resql = $this->db->query($sql);
5417 if ($resql && $this->db->num_rows($resql) > 0) {
5418 $res = $this->db->fetch_array($resql);
5419 $label = $langs->trans($res['label']);
5420 $this->db->free($resql);
5421 return $label;
5422 } else {
5423 $this->error = $this->db->error().' sql='.$sql;
5424 dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
5425 return -1;
5426 }
5427 }
5428
5429 return '';
5430 }
5431
5432
5433 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5450 public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
5451 {
5452 // phpcs:enable
5453 if ($id_entrepot) {
5454 $this->db->begin();
5455
5456 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5457
5458 if ($nbpiece < 0) {
5459 if (!$movement) {
5460 $movement = 1;
5461 }
5462 $nbpiece = abs($nbpiece);
5463 }
5464
5465 $op[0] = "+".trim($nbpiece);
5466 $op[1] = "-".trim($nbpiece);
5467
5468 $movementstock = new MouvementStock($this->db);
5469 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->origin_id
5470 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
5471
5472 if ($result >= 0) {
5473 if ($extrafields) {
5474 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5475 $movementstock->array_options = $array_options;
5476 $movementstock->insertExtraFields();
5477 }
5478 $this->db->commit();
5479 return 1;
5480 } else {
5481 $this->error = $movementstock->error;
5482 $this->errors = $movementstock->errors;
5483
5484 $this->db->rollback();
5485 return -1;
5486 }
5487 }
5488
5489 return -1;
5490 }
5491
5492 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5512 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)
5513 {
5514 // phpcs:enable
5515 if ($id_entrepot) {
5516 $this->db->begin();
5517
5518 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5519
5520 if ($nbpiece < 0) {
5521 if (!$movement) {
5522 $movement = 1;
5523 }
5524 $nbpiece = abs($nbpiece);
5525 }
5526
5527 $op[0] = "+".trim($nbpiece);
5528 $op[1] = "-".trim($nbpiece);
5529
5530 $movementstock = new MouvementStock($this->db);
5531 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->fk_origin
5532 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct);
5533
5534 if ($result >= 0) {
5535 if ($extrafields) {
5536 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5537 $movementstock->array_options = $array_options;
5538 $movementstock->insertExtraFields();
5539 }
5540 $this->db->commit();
5541 return 1;
5542 } else {
5543 $this->error = $movementstock->error;
5544 $this->errors = $movementstock->errors;
5545
5546 $this->db->rollback();
5547 return -1;
5548 }
5549 }
5550 return -1;
5551 }
5552
5553 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5566 public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
5567 {
5568 // phpcs:enable
5569 global $conf;
5570
5571 $this->stock_reel = 0;
5572 $this->stock_warehouse = array();
5573 $this->stock_theorique = 0;
5574
5575 // Set filter on warehouse status
5576 $warehouseStatus = array();
5577 if (preg_match('/warehouseclosed/', $option)) {
5579 }
5580 if (preg_match('/warehouseopen/', $option)) {
5582 }
5583 if (preg_match('/warehouseinternal/', $option)) {
5584 if (!empty($conf->global->ENTREPOT_EXTRA_STATUS)) {
5586 } else {
5588 }
5589 }
5590
5591 $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
5592 $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
5593 $sql .= ", ".$this->db->prefix()."entrepot as w";
5594 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
5595 $sql .= " AND w.rowid = ps.fk_entrepot";
5596 $sql .= " AND ps.fk_product = ".((int) $this->id);
5597 if (count($warehouseStatus)) {
5598 $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
5599 }
5600
5601 $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;
5602
5603 dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
5604 $result = $this->db->query($sql);
5605 if ($result) {
5606 $num = $this->db->num_rows($result);
5607 $i = 0;
5608 if ($num > 0) {
5609 while ($i < $num) {
5610 $row = $this->db->fetch_object($result);
5611 $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
5612 $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
5613 $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
5614 if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
5615 $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
5616 }
5617 $this->stock_reel += $row->reel;
5618 $i++;
5619 }
5620 }
5621 $this->db->free($result);
5622
5623 if (!preg_match('/novirtual/', $option)) {
5624 $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
5625 }
5626
5627 return 1;
5628 } else {
5629 $this->error = $this->db->lasterror();
5630 return -1;
5631 }
5632 }
5633
5634
5635 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5645 public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
5646 {
5647 // phpcs:enable
5648 global $conf, $hookmanager, $action;
5649
5650 $stock_commande_client = 0;
5651 $stock_commande_fournisseur = 0;
5652 $stock_sending_client = 0;
5653 $stock_reception_fournisseur = 0;
5654 $stock_inproduction = 0;
5655
5656 //dol_syslog("load_virtual_stock");
5657
5658 if (isModEnabled('commande')) {
5659 $result = $this->load_stats_commande(0, '1,2', 1);
5660 if ($result < 0) {
5661 dol_print_error($this->db, $this->error);
5662 }
5663 $stock_commande_client = $this->stats_commande['qty'];
5664 }
5665 if (isModEnabled("expedition")) {
5666 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
5667 $filterShipmentStatus = '';
5668 if (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT)) {
5669 $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
5670 } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
5671 $filterShipmentStatus = Expedition::STATUS_CLOSED;
5672 }
5673 $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
5674 if ($result < 0) {
5675 dol_print_error($this->db, $this->error);
5676 }
5677 $stock_sending_client = $this->stats_expedition['qty'];
5678 }
5679 if (isModEnabled("supplier_order")) {
5680 $filterStatus = empty($conf->global->SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK) ? '3,4' : $conf->global->SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK;
5681 if (isset($includedraftpoforvirtual)) {
5682 $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
5683 }
5684 $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
5685 if ($result < 0) {
5686 dol_print_error($this->db, $this->error);
5687 }
5688 $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
5689 }
5690 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && empty($conf->reception->enabled)) {
5691 // Case module reception is not used
5692 $filterStatus = '4';
5693 if (isset($includedraftpoforvirtual)) {
5694 $filterStatus = '0,'.$filterStatus;
5695 }
5696 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
5697 if ($result < 0) {
5698 dol_print_error($this->db, $this->error);
5699 }
5700 $stock_reception_fournisseur = $this->stats_reception['qty'];
5701 }
5702 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && isModEnabled("reception")) {
5703 // Case module reception is used
5704 $filterStatus = '4';
5705 if (isset($includedraftpoforvirtual)) {
5706 $filterStatus = '0,'.$filterStatus;
5707 }
5708 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock); // Use same tables than when module reception is not used.
5709 if ($result < 0) {
5710 dol_print_error($this->db, $this->error);
5711 }
5712 $stock_reception_fournisseur = $this->stats_reception['qty'];
5713 }
5714 if (isModEnabled('mrp')) {
5715 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
5716 if ($result < 0) {
5717 dol_print_error($this->db, $this->error);
5718 }
5719 $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
5720 }
5721
5722 $this->stock_theorique = $this->stock_reel + $stock_inproduction;
5723
5724 // Stock decrease mode
5725 if (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
5726 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
5727 } elseif (!empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER)) {
5728 $this->stock_theorique += 0;
5729 } elseif (!empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
5730 $this->stock_theorique -= $stock_commande_client;
5731 }
5732 // Stock Increase mode
5733 if (!empty($conf->global->STOCK_CALCULATE_ON_RECEPTION) || !empty($conf->global->STOCK_CALCULATE_ON_RECEPTION_CLOSE)) {
5734 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5735 } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER)) {
5736 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5737 } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER)) {
5738 $this->stock_theorique -= $stock_reception_fournisseur;
5739 } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_BILL)) {
5740 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5741 }
5742
5743 $hookmanager->initHooks(array('productdao'));
5744 $parameters = array('id'=>$this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
5745 // Note that $action and $object may have been modified by some hooks
5746 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
5747 if ($reshook > 0) {
5748 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
5749 }
5750
5751 return 1;
5752 }
5753
5754
5762 public function loadBatchInfo($batch)
5763 {
5764 $result = array();
5765
5766 $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";
5767 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
5768 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
5769 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
5770 $resql = $this->db->query($sql);
5771 if ($resql) {
5772 $num = $this->db->num_rows($resql);
5773 $i = 0;
5774 while ($i < $num) {
5775 $obj = $this->db->fetch_object($resql);
5776 $result[] = array('batch'=>$batch, 'eatby'=>$this->db->jdate($obj->eatby), 'sellby'=>$this->db->jdate($obj->sellby), 'qty'=>$obj->qty);
5777 $i++;
5778 }
5779 return $result;
5780 } else {
5781 dol_print_error($this->db);
5782 $this->db->rollback();
5783 return array();
5784 }
5785 }
5786
5787 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5795 public function add_photo($sdir, $file)
5796 {
5797 // phpcs:enable
5798 global $conf;
5799
5800 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5801
5802 $result = 0;
5803
5804 $dir = $sdir;
5805 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5806 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
5807 } else {
5808 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
5809 }
5810
5811 dol_mkdir($dir);
5812
5813 $dir_osencoded = $dir;
5814
5815 if (is_dir($dir_osencoded)) {
5816 $originImage = $dir.'/'.$file['name'];
5817
5818 // Cree fichier en taille origine
5819 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
5820
5821 if (file_exists(dol_osencode($originImage))) {
5822 // Create thumbs
5823 $this->addThumbs($originImage);
5824 }
5825 }
5826
5827 if (is_numeric($result) && $result > 0) {
5828 return 1;
5829 } else {
5830 return -1;
5831 }
5832 }
5833
5834 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5841 public function is_photo_available($sdir)
5842 {
5843 // phpcs:enable
5844 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5845 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5846
5847 global $conf;
5848
5849 $dir = $sdir;
5850 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5851 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
5852 } else {
5853 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
5854 }
5855
5856 $nbphoto = 0;
5857
5858 $dir_osencoded = dol_osencode($dir);
5859 if (file_exists($dir_osencoded)) {
5860 $handle = opendir($dir_osencoded);
5861 if (is_resource($handle)) {
5862 while (($file = readdir($handle)) !== false) {
5863 if (!utf8_check($file)) {
5864 $file = utf8_encode($file); // To be sure data is stored in UTF8 in memory
5865 }
5866 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
5867 return true;
5868 }
5869 }
5870 }
5871 }
5872 return false;
5873 }
5874
5875 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5883 public function liste_photos($dir, $nbmax = 0)
5884 {
5885 // phpcs:enable
5886 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5887 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5888
5889 $nbphoto = 0;
5890 $tabobj = array();
5891
5892 $dir_osencoded = dol_osencode($dir);
5893 $handle = @opendir($dir_osencoded);
5894 if (is_resource($handle)) {
5895 while (($file = readdir($handle)) !== false) {
5896 if (!utf8_check($file)) {
5897 $file = utf8_encode($file); // readdir returns ISO
5898 }
5899 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
5900 $nbphoto++;
5901
5902 // We forge name of thumb.
5903 $photo = $file;
5904 $photo_vignette = '';
5905 $regs = array();
5906 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
5907 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
5908 }
5909
5910 $dirthumb = $dir.'thumbs/';
5911
5912 // Objet
5913 $obj = array();
5914 $obj['photo'] = $photo;
5915 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
5916 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
5917 } else {
5918 $obj['photo_vignette'] = "";
5919 }
5920
5921 $tabobj[$nbphoto - 1] = $obj;
5922
5923 // Do we have to continue with next photo ?
5924 if ($nbmax && $nbphoto >= $nbmax) {
5925 break;
5926 }
5927 }
5928 }
5929
5930 closedir($handle);
5931 }
5932
5933 return $tabobj;
5934 }
5935
5936 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5943 public function delete_photo($file)
5944 {
5945 // phpcs:enable
5946 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5947 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5948
5949 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
5950 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
5951 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
5952
5953 // On efface l'image d'origine
5954 dol_delete_file($file, 0, 0, 0, $this); // For triggers
5955
5956 // Si elle existe, on efface la vignette
5957 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
5958 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
5959 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
5960 dol_delete_file($dirthumb.$photo_vignette);
5961 }
5962
5963 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
5964 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
5965 dol_delete_file($dirthumb.$photo_vignette);
5966 }
5967 }
5968 }
5969
5970 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5977 public function get_image_size($file)
5978 {
5979 // phpcs:enable
5980 $file_osencoded = dol_osencode($file);
5981 $infoImg = getimagesize($file_osencoded); // Get information on image
5982 $this->imgWidth = $infoImg[0]; // Largeur de l'image
5983 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
5984 }
5985
5986 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5992 public function load_state_board()
5993 {
5994 // phpcs:enable
5995 global $hookmanager;
5996
5997 $this->nb = array();
5998
5999 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6000 $sql .= " FROM ".$this->db->prefix()."product as p";
6001 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6002 // Add where from hooks
6003 if (is_object($hookmanager)) {
6004 $parameters = array();
6005 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6006 $sql .= $hookmanager->resPrint;
6007 }
6008 $sql .= ' GROUP BY fk_product_type';
6009
6010 $resql = $this->db->query($sql);
6011 if ($resql) {
6012 while ($obj = $this->db->fetch_object($resql)) {
6013 if ($obj->fk_product_type == 1) {
6014 $this->nb["services"] = $obj->nb;
6015 } else {
6016 $this->nb["products"] = $obj->nb;
6017 }
6018 }
6019 $this->db->free($resql);
6020 return 1;
6021 } else {
6022 dol_print_error($this->db);
6023 $this->error = $this->db->error();
6024 return -1;
6025 }
6026 }
6027
6033 public function isProduct()
6034 {
6035 return ($this->type == Product::TYPE_PRODUCT ? true : false);
6036 }
6037
6043 public function isService()
6044 {
6045 return ($this->type == Product::TYPE_SERVICE ? true : false);
6046 }
6047
6048
6054 public function isMandatoryPeriod()
6055 {
6056 return ($this->mandatory_period == 1 ? true : false);
6057 }
6058 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6067 public function get_barcode($object, $type = '')
6068 {
6069 // phpcs:enable
6070 global $conf;
6071
6072 $result = '';
6073 if (!empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
6074 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6075 foreach ($dirsociete as $dirroot) {
6076 $res = dol_include_once($dirroot.$conf->global->BARCODE_PRODUCT_ADDON_NUM.'.php');
6077 if ($res) {
6078 break;
6079 }
6080 }
6081 $var = $conf->global->BARCODE_PRODUCT_ADDON_NUM;
6082 $mod = new $var;
6083
6084 $result = $mod->getNextValue($object, $type);
6085
6086 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6087 }
6088 return $result;
6089 }
6090
6098 public function initAsSpecimen()
6099 {
6100 global $user, $langs, $conf, $mysoc;
6101
6102 $now = dol_now();
6103
6104 // Initialize parameters
6105 $this->specimen = 1;
6106 $this->id = 0;
6107 $this->ref = 'PRODUCT_SPEC';
6108 $this->label = 'PRODUCT SPECIMEN';
6109 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
6110 $this->specimen = 1;
6111 $this->country_id = 1;
6112 $this->status = 1;
6113 $this->status_buy = 1;
6114 $this->tobatch = 0;
6115 $this->note_private = 'This is a comment (private)';
6116 $this->note_public = 'This is a comment (public)';
6117 $this->date_creation = $now;
6118 $this->date_modification = $now;
6119
6120 $this->weight = 4;
6121 $this->weight_units = 3;
6122
6123 $this->length = 5;
6124 $this->length_units = 1;
6125 $this->width = 6;
6126 $this->width_units = 0;
6127 $this->height = null;
6128 $this->height_units = null;
6129
6130 $this->surface = 30;
6131 $this->surface_units = 0;
6132 $this->volume = 300;
6133 $this->volume_units = 0;
6134
6135 $this->barcode = -1; // Create barcode automatically
6136 }
6137
6144 public function getLabelOfUnit($type = 'long')
6145 {
6146 global $langs;
6147
6148 if (!$this->fk_unit) {
6149 return '';
6150 }
6151
6152 $langs->load('products');
6153
6154 $label_type = 'label';
6155 if ($type == 'short') {
6156 $label_type = 'short_label';
6157 }
6158
6159 $sql = "SELECT ".$label_type.", code from ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
6160
6161 $resql = $this->db->query($sql);
6162 if ($resql && $this->db->num_rows($resql) > 0) {
6163 $res = $this->db->fetch_array($resql);
6164 $label = ($label_type == 'short_label' ? $res[$label_type] : 'unit'.$res['code']);
6165 $this->db->free($resql);
6166 return $label;
6167 } else {
6168 $this->error = $this->db->error();
6169 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
6170 return -1;
6171 }
6172 }
6173
6179 public function hasbatch()
6180 {
6181 return ($this->status_batch > 0 ? true : false);
6182 }
6183
6184
6185 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6191 public function min_recommended_price()
6192 {
6193 // phpcs:enable
6194 global $conf;
6195
6196 $maxpricesupplier = 0;
6197
6198 if (!empty($conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE)) {
6199 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6200 $product_fourn = new ProductFournisseur($this->db);
6201 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
6202
6203 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
6204 foreach ($product_fourn_list as $productfourn) {
6205 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
6206 $maxpricesupplier = $productfourn->fourn_unitprice;
6207 }
6208 }
6209
6210 $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
6211 }
6212 }
6213
6214 return $maxpricesupplier;
6215 }
6216
6217
6228 public function setCategories($categories)
6229 {
6230 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
6231 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
6232 }
6233
6242 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
6243 {
6244 $tables = array(
6245 'product_customer_price',
6246 'product_customer_price_log'
6247 );
6248
6249 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
6250 }
6251
6263 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
6264 {
6265 global $conf, $db;
6266
6267 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
6268 $query = $this->db->query($sql);
6269
6270 $rules = array();
6271
6272 while ($result = $this->db->fetch_object($query)) {
6273 $rules[$result->level] = $result;
6274 }
6275
6276 //Because prices can be based on other level's prices, we temporarily store them
6277 $prices = array(
6278 1 => $baseprice
6279 );
6280
6281 for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
6282 $price = $baseprice;
6283 $price_min = $baseprice;
6284
6285 //We have to make sure it does exist and it is > 0
6286 //First price level only allows changing min_price
6287 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
6288 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
6289 }
6290
6291 $prices[$i] = $price;
6292
6293 //We have to make sure it does exist and it is > 0
6294 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
6295 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
6296 }
6297
6298 //Little check to make sure the price is modified before triggering generation
6299 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
6300 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
6301
6302 if ($check_amount && $check_type) {
6303 continue;
6304 }
6305
6306 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
6307 return -1;
6308 }
6309 }
6310
6311 return 1;
6312 }
6313
6319 public function getRights()
6320 {
6321 global $user;
6322
6323 if ($this->isProduct()) {
6324 return $user->rights->produit;
6325 } else {
6326 return $user->rights->service;
6327 }
6328 }
6329
6336 public function info($id)
6337 {
6338 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
6339 $sql .= " p.fk_user_author, p.fk_user_modif";
6340 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
6341 $sql .= " WHERE p.rowid = ".((int) $id);
6342
6343 $result = $this->db->query($sql);
6344 if ($result) {
6345 if ($this->db->num_rows($result)) {
6346 $obj = $this->db->fetch_object($result);
6347
6348 $this->id = $obj->rowid;
6349
6350 if ($obj->fk_user_author) {
6351 $cuser = new User($this->db);
6352 $cuser->fetch($obj->fk_user_author);
6353 $this->user_creation = $cuser;
6354 }
6355
6356 if ($obj->fk_user_modif) {
6357 $muser = new User($this->db);
6358 $muser->fetch($obj->fk_user_modif);
6359 $this->user_modification = $muser;
6360 }
6361
6362 $this->ref = $obj->ref;
6363 $this->date_creation = $this->db->jdate($obj->date_creation);
6364 $this->date_modification = $this->db->jdate($obj->date_modification);
6365 }
6366
6367 $this->db->free($result);
6368 } else {
6369 dol_print_error($this->db);
6370 }
6371 }
6372
6373
6378 public function getProductDurationHours()
6379 {
6380 global $langs;
6381
6382 if (empty($this->duration_value)) {
6383 $this->errors[]='ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
6384 return -1;
6385 }
6386
6387 if ($this->duration_unit == 'i') {
6388 $prodDurationHours = 1. / 60;
6389 }
6390 if ($this->duration_unit == 'h') {
6391 $prodDurationHours = 1.;
6392 }
6393 if ($this->duration_unit == 'd') {
6394 $prodDurationHours = 24.;
6395 }
6396 if ($this->duration_unit == 'w') {
6397 $prodDurationHours = 24. * 7;
6398 }
6399 if ($this->duration_unit == 'm') {
6400 $prodDurationHours = 24. * 30;
6401 }
6402 if ($this->duration_unit == 'y') {
6403 $prodDurationHours = 24. * 365;
6404 }
6405 $prodDurationHours *= $this->duration_value;
6406
6407 return $prodDurationHours;
6408 }
6409
6410
6418 public function getKanbanView($option = '', $arraydata = null)
6419 {
6420 global $langs,$conf;
6421
6422 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6423
6424 $return = '<div class="box-flex-item box-flex-grow-zero">';
6425 $return .= '<div class="info-box info-box-sm">';
6426 $return .= '<div class="info-box-img">';
6427 $label = '';
6428 if ($this->is_photo_available($conf->product->multidir_output[$this->entity])) {
6429 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
6430 $return .= $label;
6431 } else {
6432 if ($this->type == Product::TYPE_PRODUCT) {
6433 $label .= img_picto('', 'product');
6434 } elseif ($this->type == Product::TYPE_SERVICE) {
6435 $label .= img_picto('', 'service');
6436 }
6437 $return .= $label;
6438 }
6439 $return .= '</div>';
6440 $return .= '<div class="info-box-content">';
6441 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
6442 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
6443 if (property_exists($this, 'label')) {
6444 $return .= '<br><span class="info-box-label opacitymedium">'.$this->label.'</span>';
6445 }
6446 if (property_exists($this, 'price') && property_exists($this, 'price_ttc')) {
6447 if ($this->price_base_type == 'TTC') {
6448 $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
6449 } else {
6450 if ($this->status) {
6451 $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
6452 }
6453 }
6454 }
6455 if (property_exists($this, 'stock_reel')) {
6456 $return .= '<br><span class="info-box-status opacitymedium">'.$langs->trans('PhysicalStock').' : <span class="bold">'.$this->stock_reel.'</span></span>';
6457 }
6458 if (method_exists($this, 'getLibStatut')) {
6459 $return .='<br><span class="info-box-status margintoponly">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</span>';
6460 }
6461 $return .= '</div>';
6462 $return .= '</div>';
6463 $return .= '</div>';
6464 return $return;
6465 }
6466}
6467
6473{
6474 public $picto = 'service';
6475}
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