dolibarr  20.0.0-beta
product.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2001-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3  * Copyright (C) 2004-2014 Laurent Destailleur <eldy@users.sourceforge.net>
4  * Copyright (C) 2005-2015 Regis Houssin <regis.houssin@inodbox.com>
5  * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
6  * Copyright (C) 2007-2011 Jean Heimburger <jean@tiaris.info>
7  * Copyright (C) 2010-2018 Juanjo Menent <jmenent@2byte.es>
8  * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
9  * Copyright (C) 2013-2014 Cedric GROSS <c.gross@kreiz-it.fr>
10  * Copyright (C) 2013-2016 Marcos García <marcosgdf@gmail.com>
11  * Copyright (C) 2011-2021 Open-DSI <support@open-dsi.fr>
12  * Copyright (C) 2014 Henry Florian <florian.henry@open-concept.pro>
13  * Copyright (C) 2014-2016 Philippe Grand <philippe.grand@atoo-net.com>
14  * Copyright (C) 2014 Ion agorria <ion@agorria.com>
15  * Copyright (C) 2016-2024 Ferran Marcet <fmarcet@2byte.es>
16  * Copyright (C) 2017 Gustavo Novaro
17  * Copyright (C) 2019-2024 Frédéric France <frederic.france@free.fr>
18  * Copyright (C) 2023 Benjamin Falière <benjamin.faliere@altairis.fr>
19  * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
20  *
21  * This program is free software; you can redistribute it and/or modify
22  * it under the terms of the GNU General Public License as published by
23  * the Free Software Foundation; either version 3 of the License, or
24  * (at your option) any later version.
25  *
26  * This program is distributed in the hope that it will be useful,
27  * but WITHOUT ANY WARRANTY; without even the implied warranty of
28  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29  * GNU General Public License for more details.
30  *
31  * You should have received a copy of the GNU General Public License
32  * along with this program. If not, see <https://www.gnu.org/licenses/>.
33  */
34 
40 require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
41 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
42 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
43 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
44 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
45 
49 class Product extends CommonObject
50 {
55  const SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY = 1;
56  const SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY = 2;
57  const SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT = 3;
58 
62  public $element = 'product';
63 
67  public $table_element = 'product';
68 
72  public $fk_element = 'fk_product';
73 
77  public $oldcopy;
78 
82  protected $childtables = array(
83  'supplier_proposaldet' => array('name' => 'SupplierProposal', 'parent' => 'supplier_proposal', 'parentkey' => 'fk_supplier_proposal'),
84  'propaldet' => array('name' => 'Proposal', 'parent' => 'propal', 'parentkey' => 'fk_propal'),
85  'commandedet' => array('name' => 'Order', 'parent' => 'commande', 'parentkey' => 'fk_commande'),
86  'facturedet' => array('name' => 'Invoice', 'parent' => 'facture', 'parentkey' => 'fk_facture'),
87  'contratdet' => array('name' => 'Contract', 'parent' => 'contrat', 'parentkey' => 'fk_contrat'),
88  'facture_fourn_det' => array('name' => 'SupplierInvoice', 'parent' => 'facture_fourn', 'parentkey' => 'fk_facture_fourn'),
89  'commande_fournisseurdet' => array('name' => 'SupplierOrder', 'parent' => 'commande_fournisseur', 'parentkey' => 'fk_commande')
90  );
91 
95  public $picto = 'product';
96 
100  protected $table_ref_field = 'ref';
101 
102  public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.xpm|\.xbm'; // See also into images.lib.php
103 
108  public $libelle;
109 
115  public $label;
116 
122  public $description;
123 
129  public $other;
130 
136  public $type = self::TYPE_PRODUCT;
137 
143  public $price;
144 
145  public $price_formated; // used by takepos/ajax/ajax.php
146 
152  public $price_ttc;
153 
154  public $price_ttc_formated; // used by takepos/ajax/ajax.php
155 
161  public $price_min;
162 
168  public $price_min_ttc;
169 
174  public $price_base_type;
175  public $price_label;
176 
178  public $multiprices = array();
179  public $multiprices_ttc = array();
180  public $multiprices_base_type = array();
181  public $multiprices_default_vat_code = array();
182  public $multiprices_min = array();
183  public $multiprices_min_ttc = array();
184  public $multiprices_tva_tx = array();
185  public $multiprices_recuperableonly = array();
186 
189  public $prices_by_qty = array();
190  public $prices_by_qty_id = array();
191  public $prices_by_qty_list = array();
192 
196  public $level;
197 
199  public $multilangs = array();
200 
203 
205  public $tva_tx;
206 
210  public $tva_npr = 0;
211 
214 
217  public $localtax2_tx;
218  public $localtax1_type;
219  public $localtax2_type;
220 
221  // Properties set by get_buyprice() for return
222 
223  public $desc_supplier;
224  public $vatrate_supplier;
225  public $default_vat_code_supplier;
226  public $fourn_multicurrency_price;
227  public $fourn_multicurrency_unitprice;
228  public $fourn_multicurrency_tx;
229  public $fourn_multicurrency_id;
230  public $fourn_multicurrency_code;
231  public $packaging;
232 
233 
234  public $lifetime; // In seconds
235 
236  public $qc_frequency;
237 
243  public $stock_reel = 0;
244 
250  public $stock_theorique;
251 
257  public $cost_price;
258 
260  public $pmp;
261 
267  public $seuil_stock_alerte = 0;
268 
272  public $desiredstock = 0;
273 
285  public $duration;
286 
290  public $fk_default_workstation;
291 
297  public $status = 0;
298 
305  public $tosell;
306 
312  public $status_buy = 0;
313 
320  public $tobuy;
321 
327  public $finished;
328 
334  public $fk_default_bom;
335 
341  public $product_fourn_price_id;
342 
348  public $buyprice;
349 
355  public $tobatch;
356 
357 
363  public $status_batch = 0;
364 
370  public $sell_or_eat_by_mandatory = 0;
371 
377  public $batch_mask = '';
378 
384  public $customcode;
385 
391  public $url;
392 
394  public $weight;
395  public $weight_units; // scale -3, 0, 3, 6
396  public $length;
397  public $length_units; // scale -3, 0, 3, 6
398  public $width;
399  public $width_units; // scale -3, 0, 3, 6
400  public $height;
401  public $height_units; // scale -3, 0, 3, 6
402  public $surface;
403  public $surface_units; // scale -3, 0, 3, 6
404  public $volume;
405  public $volume_units; // scale -3, 0, 3, 6
406 
407  public $net_measure;
408  public $net_measure_units; // scale -3, 0, 3, 6
409 
410  public $accountancy_code_sell;
411  public $accountancy_code_sell_intra;
412  public $accountancy_code_sell_export;
413  public $accountancy_code_buy;
414  public $accountancy_code_buy_intra;
415  public $accountancy_code_buy_export;
416 
420  public $barcode;
421 
425  public $barcode_type;
426 
430  public $barcode_type_code;
431 
432  public $stats_propale = array();
433  public $stats_commande = array();
434  public $stats_contrat = array();
435  public $stats_facture = array();
436  public $stats_proposal_supplier = array();
437  public $stats_commande_fournisseur = array();
438  public $stats_expedition = array();
439  public $stats_reception = array();
440  public $stats_mo = array();
441  public $stats_bom = array();
442  public $stats_mrptoconsume = array();
443  public $stats_mrptoproduce = array();
444  public $stats_facturerec = array();
445  public $stats_facture_fournisseur = array();
446 
448  public $imgWidth;
449  public $imgHeight;
450 
454  public $date_creation;
455 
459  public $date_modification;
460 
463 
466 
467  public $nbphoto = 0;
468 
470  public $stock_warehouse = array();
471 
475  public $fk_default_warehouse;
479  public $fk_price_expression;
480 
481  /* To store supplier price found */
482  public $fourn_qty;
483  public $fourn_pu;
484  public $fourn_price_base_type;
485 
489  public $fourn_socid;
490 
495  public $ref_fourn;
496 
500  public $ref_supplier;
501 
507  public $fk_unit;
508 
514  public $price_autogen = 0;
515 
521  public $supplierprices;
522 
528  public $sousprods;
529 
533  public $res;
534 
535 
541  public $is_object_used;
542 
543  public $is_sousproduit_qty;
544  public $is_sousproduit_incdec;
545 
546  public $mandatory_period;
547 
548 
577  public $fields = array(
578  'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'index' => 1, 'position' => 1, 'comment' => 'Id'),
579  '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'),
580  'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'default' => '1', 'notnull' => 1, 'index' => 1, 'position' => 5),
581  'label' => array('type' => 'varchar(255)', 'label' => 'Label', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'showoncombobox' => 2, 'position' => 15, 'csslist' => 'tdoverflowmax250'),
582  'barcode' => array('type' => 'varchar(255)', 'label' => 'Barcode', 'enabled' => 'isModEnabled("barcode")', 'position' => 20, 'visible' => -1, 'showoncombobox' => 3, 'cssview' => 'tdwordbreak', 'csslist' => 'tdoverflowmax125'),
583  'fk_barcode_type' => array('type' => 'integer', 'label' => 'BarcodeType', 'enabled' => 1, 'position' => 21, 'notnull' => 0, 'visible' => -1,),
584  'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 61),
585  'note' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 62),
586  'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 500),
587  'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 501),
588  //'date_valid' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>502),
589  'fk_user_author' => array('type' => 'integer', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 510, 'foreignkey' => 'llx_user.rowid'),
590  'fk_user_modif' => array('type' => 'integer', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 511),
591  //'fk_user_valid' =>array('type'=>'integer', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>512),
592  'localtax1_tx' => array('type' => 'double(6,3)', 'label' => 'Localtax1tx', 'enabled' => 1, 'position' => 150, 'notnull' => 0, 'visible' => -1,),
593  'localtax1_type' => array('type' => 'varchar(10)', 'label' => 'Localtax1type', 'enabled' => 1, 'position' => 155, 'notnull' => 1, 'visible' => -1,),
594  'localtax2_tx' => array('type' => 'double(6,3)', 'label' => 'Localtax2tx', 'enabled' => 1, 'position' => 160, 'notnull' => 0, 'visible' => -1,),
595  'localtax2_type' => array('type' => 'varchar(10)', 'label' => 'Localtax2type', 'enabled' => 1, 'position' => 165, 'notnull' => 1, 'visible' => -1,),
596  'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 170),
597  'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'index' => 0, 'position' => 1000),
598  //'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')),
599  //'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')),
600  'mandatory_period' => array('type' => 'integer', 'label' => 'mandatoryperiod', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'default' => '0', 'index' => 1, 'position' => 1000),
601  );
602 
606  const TYPE_PRODUCT = 0;
610  const TYPE_SERVICE = 1;
611 
617  public function __construct($db)
618  {
619  $this->db = $db;
620 
621  $this->ismultientitymanaged = 1;
622  $this->isextrafieldmanaged = 1;
623 
624  $this->canvas = '';
625  }
626 
632  public function check()
633  {
634  if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
635  $this->ref = trim($this->ref);
636  } else {
637  $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
638  }
639 
640  $err = 0;
641  if (dol_strlen(trim($this->ref)) == 0) {
642  $err++;
643  }
644 
645  if (dol_strlen(trim($this->label)) == 0) {
646  $err++;
647  }
648 
649  if ($err > 0) {
650  return 0;
651  } else {
652  return 1;
653  }
654  }
655 
663  public function create($user, $notrigger = 0)
664  {
665  global $conf, $langs;
666 
667  $error = 0;
668 
669  // Clean parameters
670  if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
671  $this->ref = trim($this->ref);
672  } else {
673  $this->ref = dol_sanitizeFileName(dol_string_nospecial(trim($this->ref)));
674  }
675  $this->label = trim($this->label);
676  $this->price_ttc = (float) price2num($this->price_ttc);
677  $this->price = (float) price2num($this->price);
678  $this->price_min_ttc = (float) price2num($this->price_min_ttc);
679  $this->price_min = (float) price2num($this->price_min);
680  $this->price_label = trim($this->price_label);
681  if (empty($this->tva_tx)) {
682  $this->tva_tx = 0;
683  }
684  if (empty($this->tva_npr)) {
685  $this->tva_npr = 0;
686  }
687  //Local taxes
688  if (empty($this->localtax1_tx)) {
689  $this->localtax1_tx = 0;
690  }
691  if (empty($this->localtax2_tx)) {
692  $this->localtax2_tx = 0;
693  }
694  if (empty($this->localtax1_type)) {
695  $this->localtax1_type = '0';
696  }
697  if (empty($this->localtax2_type)) {
698  $this->localtax2_type = '0';
699  }
700  if (empty($this->price)) {
701  $this->price = 0;
702  }
703  if (empty($this->price_min)) {
704  $this->price_min = 0;
705  }
706  // Price by quantity
707  if (empty($this->price_by_qty)) {
708  $this->price_by_qty = 0;
709  }
710 
711  if (empty($this->status)) {
712  $this->status = 0;
713  }
714  if (empty($this->status_buy)) {
715  $this->status_buy = 0;
716  }
717 
718  $price_ht = 0;
719  $price_ttc = 0;
720  $price_min_ht = 0;
721  $price_min_ttc = 0;
722 
723  //
724  if ($this->price_base_type == 'TTC' && $this->price_ttc > 0) {
725  $price_ttc = price2num($this->price_ttc, 'MU');
726  $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)), 'MU');
727  }
728 
729  //
730  if ($this->price_base_type != 'TTC' && $this->price > 0) {
731  $price_ht = price2num($this->price, 'MU');
732  $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)), 'MU');
733  }
734 
735  //
736  if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC')) {
737  $price_min_ttc = price2num($this->price_min_ttc, 'MU');
738  $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)), 'MU');
739  }
740 
741  //
742  if (($this->price_min > 0) && ($this->price_base_type != 'TTC')) {
743  $price_min_ht = price2num($this->price_min, 'MU');
744  $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)), 'MU');
745  }
746 
747  $this->accountancy_code_buy = trim($this->accountancy_code_buy);
748  $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
749  $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
750  $this->accountancy_code_sell = trim($this->accountancy_code_sell);
751  $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
752  $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
753 
754  // Barcode value
755  $this->barcode = trim($this->barcode);
756  $this->mandatory_period = empty($this->mandatory_period) ? 0 : $this->mandatory_period;
757  // Check parameters
758  if (empty($this->label)) {
759  $this->error = 'ErrorMandatoryParametersNotProvided';
760  return -1;
761  }
762 
763  if (empty($this->ref) || $this->ref == 'auto') {
764  // Load object modCodeProduct
765  $module = getDolGlobalString('PRODUCT_CODEPRODUCT_ADDON', 'mod_codeproduct_leopard');
766  if ($module != 'mod_codeproduct_leopard') { // Do not load module file for leopard
767  if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
768  $module = substr($module, 0, dol_strlen($module) - 4);
769  }
770  dol_include_once('/core/modules/product/'.$module.'.php');
771  $modCodeProduct = new $module();
772  '@phan-var-force ModeleProductCode $modCodeProduct';
773  if (!empty($modCodeProduct->code_auto)) {
774  $this->ref = $modCodeProduct->getNextValue($this, $this->type);
775  }
776  unset($modCodeProduct);
777  }
778 
779  if (empty($this->ref)) {
780  $this->error = 'ProductModuleNotSetupForAutoRef';
781  return -2;
782  }
783  }
784 
785  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);
786 
787  $now = dol_now();
788 
789  if (empty($this->date_creation)) {
790  $this->date_creation = $now;
791  }
792 
793  $this->db->begin();
794 
795  // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
796  if ($this->barcode == -1) {
797  $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
798  }
799 
800  // Check more parameters
801  // If error, this->errors[] is filled
802  $result = $this->verify();
803 
804  if ($result >= 0) {
805  $sql = "SELECT count(*) as nb";
806  $sql .= " FROM ".$this->db->prefix()."product";
807  $sql .= " WHERE entity IN (".getEntity('product').")";
808  $sql .= " AND ref = '".$this->db->escape($this->ref)."'";
809 
810  $result = $this->db->query($sql);
811  if ($result) {
812  $obj = $this->db->fetch_object($result);
813  if ($obj->nb == 0) {
814  // Insert new product, no previous one found
815  $sql = "INSERT INTO ".$this->db->prefix()."product (";
816  $sql .= "datec";
817  $sql .= ", entity";
818  $sql .= ", ref";
819  $sql .= ", ref_ext";
820  $sql .= ", price_min";
821  $sql .= ", price_min_ttc";
822  $sql .= ", label";
823  $sql .= ", fk_user_author";
824  $sql .= ", fk_product_type";
825  $sql .= ", price";
826  $sql .= ", price_ttc";
827  $sql .= ", price_base_type";
828  $sql .= ", price_label";
829  $sql .= ", tobuy";
830  $sql .= ", tosell";
831  if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
832  $sql .= ", accountancy_code_buy";
833  $sql .= ", accountancy_code_buy_intra";
834  $sql .= ", accountancy_code_buy_export";
835  $sql .= ", accountancy_code_sell";
836  $sql .= ", accountancy_code_sell_intra";
837  $sql .= ", accountancy_code_sell_export";
838  }
839  $sql .= ", canvas";
840  $sql .= ", finished";
841  $sql .= ", tobatch";
842  $sql .= ", sell_or_eat_by_mandatory";
843  $sql .= ", batch_mask";
844  $sql .= ", fk_unit";
845  $sql .= ", mandatory_period";
846  $sql .= ") VALUES (";
847  $sql .= "'".$this->db->idate($this->date_creation)."'";
848  $sql .= ", ".(!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
849  $sql .= ", '".$this->db->escape($this->ref)."'";
850  $sql .= ", ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
851  $sql .= ", ".price2num($price_min_ht);
852  $sql .= ", ".price2num($price_min_ttc);
853  $sql .= ", ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
854  $sql .= ", ".((int) $user->id);
855  $sql .= ", ".((int) $this->type);
856  $sql .= ", ".price2num($price_ht, 'MT');
857  $sql .= ", ".price2num($price_ttc, 'MT');
858  $sql .= ", '".$this->db->escape($this->price_base_type)."'";
859  $sql .= ", ".(!empty($this->price_label) ? "'".$this->db->escape($this->price_label)."'" : "null");
860  $sql .= ", ".((int) $this->status);
861  $sql .= ", ".((int) $this->status_buy);
862  if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
863  $sql .= ", '".$this->db->escape($this->accountancy_code_buy)."'";
864  $sql .= ", '".$this->db->escape($this->accountancy_code_buy_intra)."'";
865  $sql .= ", '".$this->db->escape($this->accountancy_code_buy_export)."'";
866  $sql .= ", '".$this->db->escape($this->accountancy_code_sell)."'";
867  $sql .= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
868  $sql .= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
869  }
870  $sql .= ", '".$this->db->escape($this->canvas)."'";
871  $sql .= ", ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'NULL' : (int) $this->finished);
872  $sql .= ", ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : ((int) $this->status_batch));
873  $sql .= ", ".((empty($this->sell_or_eat_by_mandatory) || $this->sell_or_eat_by_mandatory < 0) ? 0 : ((int) $this->sell_or_eat_by_mandatory));
874  $sql .= ", '".$this->db->escape($this->batch_mask)."'";
875  $sql .= ", ".($this->fk_unit > 0 ? ((int) $this->fk_unit) : 'NULL');
876  $sql .= ", '".$this->db->escape($this->mandatory_period)."'";
877  $sql .= ")";
878 
879  dol_syslog(get_class($this)."::Create", LOG_DEBUG);
880 
881  $result = $this->db->query($sql);
882  if ($result) {
883  $id = $this->db->last_insert_id($this->db->prefix()."product");
884 
885  if ($id > 0) {
886  $this->id = $id;
887  $this->price = $price_ht;
888  $this->price_ttc = $price_ttc;
889  $this->price_min = $price_min_ht;
890  $this->price_min_ttc = $price_min_ttc;
891 
892  $result = $this->_log_price($user);
893  if ($result > 0) {
894  if ($this->update($id, $user, true, 'add') <= 0) {
895  $error++;
896  }
897  } else {
898  $error++;
899  $this->error = $this->db->lasterror();
900  }
901 
902  // update accountancy for this entity
903  if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
904  $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " .((int) $this->id) . " AND entity = " . ((int) $conf->entity));
905 
906  $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
907  $sql .= " fk_product";
908  $sql .= ", entity";
909  $sql .= ", accountancy_code_buy";
910  $sql .= ", accountancy_code_buy_intra";
911  $sql .= ", accountancy_code_buy_export";
912  $sql .= ", accountancy_code_sell";
913  $sql .= ", accountancy_code_sell_intra";
914  $sql .= ", accountancy_code_sell_export";
915  $sql .= ") VALUES (";
916  $sql .= $this->id;
917  $sql .= ", " . $conf->entity;
918  $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
919  $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
920  $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
921  $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
922  $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
923  $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
924  $sql .= ")";
925  $result = $this->db->query($sql);
926  if (!$result) {
927  $error++;
928  $this->error = 'ErrorFailedToInsertAccountancyForEntity';
929  }
930  }
931  } else {
932  $error++;
933  $this->error = 'ErrorFailedToGetInsertedId';
934  }
935  } else {
936  $error++;
937  $this->error = $this->db->lasterror();
938  }
939  } else {
940  // Product already exists with this ref
941  $langs->load("products");
942  $error++;
943  $this->error = "ErrorProductAlreadyExists";
944  dol_syslog(get_class($this)."::Create fails, ref ".$this->ref." already exists");
945  }
946  } else {
947  $error++;
948  $this->error = $this->db->lasterror();
949  }
950 
951  if (!$error && !$notrigger) {
952  // Call trigger
953  $result = $this->call_trigger('PRODUCT_CREATE', $user);
954  if ($result < 0) {
955  $error++;
956  }
957  // End call triggers
958  }
959 
960  if (!$error) {
961  $this->db->commit();
962  return $this->id;
963  } else {
964  $this->db->rollback();
965  return -$error;
966  }
967  } else {
968  $this->db->rollback();
969  dol_syslog(get_class($this)."::Create fails verify ".implode(',', $this->errors), LOG_WARNING);
970  return -3;
971  }
972  }
973 
974 
981  public function verify()
982  {
983  global $langs;
984 
985  $this->errors = array();
986 
987  $result = 0;
988  $this->ref = trim($this->ref);
989 
990  if (!$this->ref) {
991  $this->errors[] = 'ErrorBadRef';
992  $result = -2;
993  }
994 
995  $arrayofnonnegativevalue = array('weight' => 'Weight', 'width' => 'Width', 'height' => 'Height', 'length' => 'Length', 'surface' => 'Surface', 'volume' => 'Volume');
996  foreach ($arrayofnonnegativevalue as $key => $value) {
997  if (property_exists($this, $key) && !empty($this->$key) && ($this->$key < 0)) {
998  $langs->loadLangs(array("main", "other"));
999  $this->error = $langs->trans("FieldCannotBeNegative", $langs->transnoentitiesnoconv($value));
1000  $this->errors[] = $this->error;
1001  $result = -4;
1002  }
1003  }
1004 
1005  $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
1006  if ($rescode) {
1007  if ($rescode == -1) {
1008  $this->errors[] = 'ErrorBadBarCodeSyntax';
1009  } elseif ($rescode == -2) {
1010  $this->errors[] = 'ErrorBarCodeRequired';
1011  } elseif ($rescode == -3) {
1012  // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
1013  $this->errors[] = 'ErrorBarCodeAlreadyUsed';
1014  }
1015 
1016  $result = -3;
1017  }
1018 
1019  return $result;
1020  }
1021 
1022  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1033  public function check_barcode($valuetotest, $typefortest)
1034  {
1035  // phpcs:enable
1036  global $conf;
1037  if (isModEnabled('barcode') && getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
1038  $module = strtolower($conf->global->BARCODE_PRODUCT_ADDON_NUM);
1039 
1040  $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
1041  foreach ($dirsociete as $dirroot) {
1042  $res = dol_include_once($dirroot.$module.'.php');
1043  if ($res) {
1044  break;
1045  }
1046  }
1047 
1048  $mod = new $module();
1049  '@phan-var-force ModeleNumRefBarCode $mod';
1050 
1051  dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
1052  $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
1053  return $result;
1054  } else {
1055  return 0;
1056  }
1057  }
1058 
1070  public function update($id, $user, $notrigger = 0, $action = 'update', $updatetype = false)
1071  {
1072  global $langs, $conf, $hookmanager;
1073 
1074  $error = 0;
1075 
1076  // Check parameters
1077  if (!$this->label) {
1078  $this->label = 'MISSING LABEL';
1079  }
1080 
1081  // Clean parameters
1082  if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
1083  $this->ref = trim($this->ref);
1084  } else {
1085  $this->ref = dol_string_nospecial(trim($this->ref));
1086  }
1087  $this->label = trim($this->label);
1088  $this->description = trim($this->description);
1089  $this->note_private = (isset($this->note_private) ? trim($this->note_private) : null);
1090  $this->note_public = (isset($this->note_public) ? trim($this->note_public) : null);
1091  $this->net_measure = price2num($this->net_measure);
1092  $this->net_measure_units = (empty($this->net_measure_units) ? '' : trim($this->net_measure_units));
1093  $this->weight = price2num($this->weight);
1094  $this->weight_units = (empty($this->weight_units) ? '' : trim($this->weight_units));
1095  $this->length = price2num($this->length);
1096  $this->length_units = (empty($this->length_units) ? '' : trim($this->length_units));
1097  $this->width = price2num($this->width);
1098  $this->width_units = (empty($this->width_units) ? '' : trim($this->width_units));
1099  $this->height = price2num($this->height);
1100  $this->height_units = (empty($this->height_units) ? '' : trim($this->height_units));
1101  $this->surface = price2num($this->surface);
1102  $this->surface_units = (empty($this->surface_units) ? '' : trim($this->surface_units));
1103  $this->volume = price2num($this->volume);
1104  $this->volume_units = (empty($this->volume_units) ? '' : trim($this->volume_units));
1105 
1106  // set unit not defined
1107  if (is_numeric($this->length_units)) {
1108  $this->width_units = $this->length_units; // Not used yet
1109  }
1110  if (is_numeric($this->length_units)) {
1111  $this->height_units = $this->length_units; // Not used yet
1112  }
1113 
1114  // Automated compute surface and volume if not filled
1115  if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
1116  $this->surface = (float) $this->length * (float) $this->width;
1117  $this->surface_units = measuring_units_squared($this->length_units);
1118  }
1119  if (empty($this->volume) && !empty($this->surface) && !empty($this->height) && $this->length_units == $this->height_units) {
1120  $this->volume = $this->surface * (float) $this->height;
1121  $this->volume_units = measuring_units_cubed($this->height_units);
1122  }
1123 
1124  if (empty($this->tva_tx)) {
1125  $this->tva_tx = 0;
1126  }
1127  if (empty($this->tva_npr)) {
1128  $this->tva_npr = 0;
1129  }
1130  if (empty($this->localtax1_tx)) {
1131  $this->localtax1_tx = 0;
1132  }
1133  if (empty($this->localtax2_tx)) {
1134  $this->localtax2_tx = 0;
1135  }
1136  if (empty($this->localtax1_type)) {
1137  $this->localtax1_type = '0';
1138  }
1139  if (empty($this->localtax2_type)) {
1140  $this->localtax2_type = '0';
1141  }
1142  if (empty($this->status)) {
1143  $this->status = 0;
1144  }
1145  if (empty($this->status_buy)) {
1146  $this->status_buy = 0;
1147  }
1148 
1149  if (empty($this->country_id)) {
1150  $this->country_id = 0;
1151  }
1152 
1153  if (empty($this->state_id)) {
1154  $this->state_id = 0;
1155  }
1156 
1157  // Barcode value
1158  $this->barcode = (empty($this->barcode) ? '' : trim($this->barcode));
1159 
1160  $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1161  $this->accountancy_code_buy_intra = (!empty($this->accountancy_code_buy_intra) ? trim($this->accountancy_code_buy_intra) : '');
1162  $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1163  $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1164  $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1165  $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1166 
1167 
1168  $this->db->begin();
1169 
1170  $result = 0;
1171  // Check name is required and codes are ok or unique. If error, this->errors[] is filled
1172  if ($action != 'add') {
1173  $result = $this->verify(); // We don't check when update called during a create because verify was already done
1174  } else {
1175  // we can continue
1176  $result = 0;
1177  }
1178 
1179  if ($result >= 0) {
1180  // $this->oldcopy should have been set by the caller of update (here properties were already modified)
1181  if (empty($this->oldcopy)) {
1182  $this->oldcopy = dol_clone($this, 1);
1183  }
1184  // Test if batch management is activated on existing product
1185  // If yes, we create missing entries into product_batch
1186  if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
1187  //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
1188  $valueforundefinedlot = '000000';
1189  if (getDolGlobalString('STOCK_DEFAULT_BATCH')) {
1190  $valueforundefinedlot = getDolGlobalString('STOCK_DEFAULT_BATCH');
1191  }
1192 
1193  dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
1194 
1195  $this->load_stock();
1196  foreach ($this->stock_warehouse as $idW => $ObjW) { // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
1197  $qty_batch = 0;
1198  foreach ($ObjW->detail_batch as $detail) { // Each lines of detail in product_batch of the current $ObjW = product_stock
1199  if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
1200  // We discard this line, we will create it later
1201  $sqlclean = "DELETE FROM ".$this->db->prefix()."product_batch WHERE batch in('Undefined', '".$this->db->escape($valueforundefinedlot)."') AND fk_product_stock = ".((int) $ObjW->id);
1202  $result = $this->db->query($sqlclean);
1203  if (!$result) {
1204  dol_print_error($this->db);
1205  exit;
1206  }
1207  continue;
1208  }
1209 
1210  $qty_batch += $detail->qty;
1211  }
1212  // Quantities in batch details are not same as stock quantity,
1213  // so we add a default batch record to complete and get same qty in parent and child table
1214  if ($ObjW->real != $qty_batch) {
1215  $ObjBatch = new Productbatch($this->db);
1216  $ObjBatch->batch = $valueforundefinedlot;
1217  $ObjBatch->qty = ($ObjW->real - $qty_batch);
1218  $ObjBatch->fk_product_stock = $ObjW->id;
1219 
1220  if ($ObjBatch->create($user, 1) < 0) {
1221  $error++;
1222  $this->errors = $ObjBatch->errors;
1223  } else {
1224  // we also add lot record if not exist
1225  $ObjLot = new Productlot($this->db);
1226  // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
1227  if ($ObjLot->fetch(0, $this->id, $valueforundefinedlot) == 0) {
1228  $ObjLot->fk_product = $this->id;
1229  $ObjLot->entity = $this->entity;
1230  $ObjLot->fk_user_creat = $user->id;
1231  $ObjLot->batch = $valueforundefinedlot;
1232  if ($ObjLot->create($user, true) < 0) {
1233  $error++;
1234  $this->errors = $ObjLot->errors;
1235  }
1236  }
1237  }
1238  }
1239  }
1240  }
1241 
1242  // For automatic creation
1243  if ($this->barcode == -1) {
1244  $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1245  }
1246 
1247  $sql = "UPDATE ".$this->db->prefix()."product";
1248  $sql .= " SET label = '".$this->db->escape($this->label)."'";
1249 
1250  if ($updatetype && ($this->isProduct() || $this->isService())) {
1251  $sql .= ", fk_product_type = ".((int) $this->type);
1252  }
1253 
1254  $sql .= ", ref = '".$this->db->escape($this->ref)."'";
1255  $sql .= ", ref_ext = ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1256  $sql .= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
1257  $sql .= ", tva_tx = ".((float) $this->tva_tx);
1258  $sql .= ", recuperableonly = ".((int) $this->tva_npr);
1259  $sql .= ", localtax1_tx = ".((float) $this->localtax1_tx);
1260  $sql .= ", localtax2_tx = ".((float) $this->localtax2_tx);
1261  $sql .= ", localtax1_type = ".($this->localtax1_type != '' ? "'".$this->db->escape($this->localtax1_type)."'" : "'0'");
1262  $sql .= ", localtax2_type = ".($this->localtax2_type != '' ? "'".$this->db->escape($this->localtax2_type)."'" : "'0'");
1263 
1264  $sql .= ", barcode = ".(empty($this->barcode) ? "null" : "'".$this->db->escape($this->barcode)."'");
1265  $sql .= ", fk_barcode_type = ".(empty($this->barcode_type) ? "null" : $this->db->escape($this->barcode_type));
1266 
1267  $sql .= ", tosell = ".(int) $this->status;
1268  $sql .= ", tobuy = ".(int) $this->status_buy;
1269  $sql .= ", tobatch = ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
1270  $sql .= ", sell_or_eat_by_mandatory = ".((empty($this->sell_or_eat_by_mandatory) || $this->sell_or_eat_by_mandatory < 0) ? 0 : (int) $this->sell_or_eat_by_mandatory);
1271  $sql .= ", batch_mask = '".$this->db->escape($this->batch_mask)."'";
1272 
1273  $sql .= ", finished = ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? "null" : (int) $this->finished);
1274  $sql .= ", fk_default_bom = ".((!isset($this->fk_default_bom) || $this->fk_default_bom < 0 || $this->fk_default_bom == '') ? "null" : (int) $this->fk_default_bom);
1275  $sql .= ", net_measure = ".($this->net_measure != '' ? "'".$this->db->escape($this->net_measure)."'" : 'null');
1276  $sql .= ", net_measure_units = ".($this->net_measure_units != '' ? "'".$this->db->escape($this->net_measure_units)."'" : 'null');
1277  $sql .= ", weight = ".($this->weight != '' ? "'".$this->db->escape($this->weight)."'" : 'null');
1278  $sql .= ", weight_units = ".($this->weight_units != '' ? "'".$this->db->escape($this->weight_units)."'" : 'null');
1279  $sql .= ", length = ".($this->length != '' ? "'".$this->db->escape($this->length)."'" : 'null');
1280  $sql .= ", length_units = ".($this->length_units != '' ? "'".$this->db->escape($this->length_units)."'" : 'null');
1281  $sql .= ", width= ".($this->width != '' ? "'".$this->db->escape($this->width)."'" : 'null');
1282  $sql .= ", width_units = ".($this->width_units != '' ? "'".$this->db->escape($this->width_units)."'" : 'null');
1283  $sql .= ", height = ".($this->height != '' ? "'".$this->db->escape($this->height)."'" : 'null');
1284  $sql .= ", height_units = ".($this->height_units != '' ? "'".$this->db->escape($this->height_units)."'" : 'null');
1285  $sql .= ", surface = ".($this->surface != '' ? "'".$this->db->escape($this->surface)."'" : 'null');
1286  $sql .= ", surface_units = ".($this->surface_units != '' ? "'".$this->db->escape($this->surface_units)."'" : 'null');
1287  $sql .= ", volume = ".($this->volume != '' ? "'".$this->db->escape($this->volume)."'" : 'null');
1288  $sql .= ", volume_units = ".($this->volume_units != '' ? "'".$this->db->escape($this->volume_units)."'" : 'null');
1289  $sql .= ", fk_default_warehouse = ".($this->fk_default_warehouse > 0 ? ((int) $this->fk_default_warehouse) : 'null');
1290  $sql .= ", fk_default_workstation = ".($this->fk_default_workstation > 0 ? ((int) $this->fk_default_workstation) : 'null');
1291  $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (float) $this->seuil_stock_alerte : 'null');
1292  $sql .= ", description = '".$this->db->escape($this->description)."'";
1293  $sql .= ", url = ".($this->url ? "'".$this->db->escape($this->url)."'" : 'null');
1294  $sql .= ", customcode = '".$this->db->escape($this->customcode)."'";
1295  $sql .= ", fk_country = ".($this->country_id > 0 ? (int) $this->country_id : 'null');
1296  $sql .= ", fk_state = ".($this->state_id > 0 ? (int) $this->state_id : 'null');
1297  $sql .= ", lifetime = ".($this->lifetime > 0 ? (int) $this->lifetime : 'null');
1298  $sql .= ", qc_frequency = ".($this->qc_frequency > 0 ? (int) $this->qc_frequency : 'null');
1299  $sql .= ", note = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : 'null');
1300  $sql .= ", note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : 'null');
1301  $sql .= ", duration = '".$this->db->escape($this->duration_value.$this->duration_unit)."'";
1302  if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1303  $sql .= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy) . "'";
1304  $sql .= ", accountancy_code_buy_intra = '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1305  $sql .= ", accountancy_code_buy_export = '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1306  $sql .= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell) . "'";
1307  $sql .= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1308  $sql .= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1309  }
1310  $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (float) $this->desiredstock : "null");
1311  $sql .= ", cost_price = ".($this->cost_price != '' ? $this->db->escape($this->cost_price) : 'null');
1312  $sql .= ", fk_unit= ".(!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
1313  $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1);
1314  $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
1315  $sql .= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL');
1316  $sql .= ", mandatory_period = ".($this->mandatory_period);
1317  // stock field is not here because it is a denormalized value from product_stock.
1318  $sql .= " WHERE rowid = ".((int) $id);
1319 
1320  dol_syslog(get_class($this)."::update", LOG_DEBUG);
1321 
1322  $resql = $this->db->query($sql);
1323  if ($resql) {
1324  $this->id = $id;
1325 
1326  // Multilangs
1327  if (getDolGlobalInt('MAIN_MULTILANGS')) {
1328  if ($this->setMultiLangs($user) < 0) {
1329  $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1330  $this->db->rollback();
1331  return -2;
1332  }
1333  }
1334 
1335  $action = 'update';
1336 
1337  // update accountancy for this entity
1338  if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1339  $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " . ((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1340 
1341  $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1342  $sql .= " fk_product";
1343  $sql .= ", entity";
1344  $sql .= ", accountancy_code_buy";
1345  $sql .= ", accountancy_code_buy_intra";
1346  $sql .= ", accountancy_code_buy_export";
1347  $sql .= ", accountancy_code_sell";
1348  $sql .= ", accountancy_code_sell_intra";
1349  $sql .= ", accountancy_code_sell_export";
1350  $sql .= ") VALUES (";
1351  $sql .= $this->id;
1352  $sql .= ", " . $conf->entity;
1353  $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1354  $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1355  $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1356  $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1357  $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1358  $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1359  $sql .= ")";
1360  $result = $this->db->query($sql);
1361  if (!$result) {
1362  $error++;
1363  $this->error = 'ErrorFailedToUpdateAccountancyForEntity';
1364  }
1365  }
1366 
1367  if (!$this->hasbatch() && $this->oldcopy->hasbatch()) {
1368  // Selection of all product stock movements that contains batchs
1369  $sql = 'SELECT pb.qty, ps.fk_entrepot, pb.batch FROM '.MAIN_DB_PREFIX.'product_batch as pb';
1370  $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_stock as ps ON (ps.rowid = pb.fk_product_stock)';
1371  $sql .= ' WHERE ps.fk_product = '.(int) $this->id;
1372 
1373  $resql = $this->db->query($sql);
1374  if ($resql) {
1375  $inventorycode = dol_print_date(dol_now(), '%Y%m%d%H%M%S');
1376 
1377  while ($obj = $this->db->fetch_object($resql)) {
1378  $value = $obj->qty;
1379  $fk_entrepot = $obj->fk_entrepot;
1380  $price = 0;
1381  $dlc = '';
1382  $dluo = '';
1383  $batch = $obj->batch;
1384 
1385  // To know how to revert stockMouvement (add or remove)
1386  $addOremove = $value > 0 ? 1 : 0; // 1 if remove, 0 if add
1387  $label = $langs->trans('BatchStockMouvementAddInGlobal');
1388  $res = $this->correct_stock_batch($user, $fk_entrepot, abs($value), $addOremove, $label, $price, $dlc, $dluo, $batch, $inventorycode, '', null, 0, null, true);
1389 
1390  if ($res > 0) {
1391  $label = $langs->trans('BatchStockMouvementAddInGlobal');
1392  $res = $this->correct_stock($user, $fk_entrepot, abs($value), (int) empty($addOremove), $label, $price, $inventorycode, '', null, 0);
1393  if ($res < 0) {
1394  $error++;
1395  }
1396  } else {
1397  $error++;
1398  }
1399  }
1400  }
1401  }
1402 
1403  // Actions on extra fields
1404  if (!$error) {
1405  $result = $this->insertExtraFields();
1406  if ($result < 0) {
1407  $error++;
1408  }
1409  }
1410 
1411  if (!$error && !$notrigger) {
1412  // Call trigger
1413  $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1414  if ($result < 0) {
1415  $error++;
1416  }
1417  // End call triggers
1418  }
1419 
1420  if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
1421  // We remove directory
1422  if ($conf->product->dir_output) {
1423  $olddir = $conf->product->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
1424  $newdir = $conf->product->dir_output."/".dol_sanitizeFileName($this->ref);
1425  if (file_exists($olddir)) {
1426  //include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1427  //$res = dol_move($olddir, $newdir);
1428  // do not use dol_move with directory
1429  $res = @rename($olddir, $newdir);
1430  if (!$res) {
1431  $langs->load("errors");
1432  $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
1433  $error++;
1434  }
1435  }
1436  }
1437  }
1438 
1439  if (!$error) {
1440  if (isModEnabled('variants')) {
1441  include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1442 
1443  $comb = new ProductCombination($this->db);
1444 
1445  foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1446  $currcomb->updateProperties($this, $user);
1447  }
1448  }
1449 
1450  $this->db->commit();
1451  return 1;
1452  } else {
1453  $this->db->rollback();
1454  return -$error;
1455  }
1456  } else {
1457  if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1458  $langs->load("errors");
1459  if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1460  $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1461  } else {
1462  $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1463  }
1464  $this->errors[] = $this->error;
1465  $this->db->rollback();
1466  return -1;
1467  } else {
1468  $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1469  $this->errors[] = $this->error;
1470  $this->db->rollback();
1471  return -2;
1472  }
1473  }
1474  } else {
1475  $this->db->rollback();
1476  dol_syslog(get_class($this)."::Update fails verify ".implode(',', $this->errors), LOG_WARNING);
1477  return -3;
1478  }
1479  }
1480 
1488  public function delete(User $user, $notrigger = 0)
1489  {
1490  global $conf, $langs;
1491  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1492 
1493  $error = 0;
1494 
1495  // Check parameters
1496  if (empty($this->id)) {
1497  $this->error = "Object must be fetched before calling delete";
1498  return -1;
1499  }
1500  if (($this->type == Product::TYPE_PRODUCT && !$user->hasRight('produit', 'supprimer')) || ($this->type == Product::TYPE_SERVICE && !$user->hasRight('service', 'supprimer'))) {
1501  $this->error = "ErrorForbidden";
1502  return 0;
1503  }
1504 
1505  $objectisused = $this->isObjectUsed($this->id);
1506  if (empty($objectisused)) {
1507  $this->db->begin();
1508 
1509  if (!$error && empty($notrigger)) {
1510  // Call trigger
1511  $result = $this->call_trigger('PRODUCT_DELETE', $user);
1512  if ($result < 0) {
1513  $error++;
1514  }
1515  // End call triggers
1516  }
1517 
1518  // Delete from product_batch on product delete
1519  if (!$error) {
1520  $sql = "DELETE FROM ".$this->db->prefix().'product_batch';
1521  $sql .= " WHERE fk_product_stock IN (";
1522  $sql .= "SELECT rowid FROM ".$this->db->prefix().'product_stock';
1523  $sql .= " WHERE fk_product = ".((int) $this->id).")";
1524 
1525  $result = $this->db->query($sql);
1526  if (!$result) {
1527  $error++;
1528  $this->errors[] = $this->db->lasterror();
1529  }
1530  }
1531 
1532  // Delete all child tables
1533  if (!$error) {
1534  $elements = array('product_fournisseur_price', 'product_price', 'product_lang', 'categorie_product', 'product_stock', 'product_customer_price', 'product_lot'); // product_batch is done before
1535  foreach ($elements as $table) {
1536  if (!$error) {
1537  $sql = "DELETE FROM ".$this->db->prefix().$table;
1538  $sql .= " WHERE fk_product = ".(int) $this->id;
1539 
1540  $result = $this->db->query($sql);
1541  if (!$result) {
1542  $error++;
1543  $this->errors[] = $this->db->lasterror();
1544  }
1545  }
1546  }
1547  }
1548 
1549  if (!$error) {
1550  include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1551  include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1552 
1553  //If it is a parent product, then we remove the association with child products
1554  $prodcomb = new ProductCombination($this->db);
1555 
1556  if ($prodcomb->deleteByFkProductParent($user, $this->id) < 0) {
1557  $error++;
1558  $this->errors[] = 'Error deleting combinations';
1559  }
1560 
1561  //We also check if it is a child product
1562  if (!$error && ($prodcomb->fetchByFkProductChild($this->id) > 0) && ($prodcomb->delete($user) < 0)) {
1563  $error++;
1564  $this->errors[] = 'Error deleting child combination';
1565  }
1566  }
1567 
1568  // Delete from product_association
1569  if (!$error) {
1570  $sql = "DELETE FROM ".$this->db->prefix()."product_association";
1571  $sql .= " WHERE fk_product_pere = ".(int) $this->id." OR fk_product_fils = ".(int) $this->id;
1572 
1573  $result = $this->db->query($sql);
1574  if (!$result) {
1575  $error++;
1576  $this->errors[] = $this->db->lasterror();
1577  }
1578  }
1579 
1580  // Remove extrafields
1581  if (!$error) {
1582  $result = $this->deleteExtraFields();
1583  if ($result < 0) {
1584  $error++;
1585  dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1586  }
1587  }
1588 
1589  // Delete product
1590  if (!$error) {
1591  $sqlz = "DELETE FROM ".$this->db->prefix()."product";
1592  $sqlz .= " WHERE rowid = ".(int) $this->id;
1593 
1594  $resultz = $this->db->query($sqlz);
1595  if (!$resultz) {
1596  $error++;
1597  $this->errors[] = $this->db->lasterror();
1598  }
1599  }
1600 
1601  // Delete record into ECM index and physically
1602  if (!$error) {
1603  $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1604  $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1605  if (!$res) {
1606  $error++;
1607  }
1608  }
1609 
1610  if (!$error) {
1611  // We remove directory
1612  $ref = dol_sanitizeFileName($this->ref);
1613  if ($conf->product->dir_output) {
1614  $dir = $conf->product->dir_output."/".$ref;
1615  if (file_exists($dir)) {
1616  $res = @dol_delete_dir_recursive($dir);
1617  if (!$res) {
1618  $this->errors[] = 'ErrorFailToDeleteDir';
1619  $error++;
1620  }
1621  }
1622  }
1623  }
1624 
1625  if (!$error) {
1626  $this->db->commit();
1627  return 1;
1628  } else {
1629  foreach ($this->errors as $errmsg) {
1630  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1631  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1632  }
1633  $this->db->rollback();
1634  return -$error;
1635  }
1636  } else {
1637  $this->error = "ErrorRecordIsUsedCantDelete";
1638  return 0;
1639  }
1640  }
1641 
1647  public static function getSellOrEatByMandatoryList()
1648  {
1649  global $langs;
1650 
1651  $sellByLabel = $langs->trans('SellByDate');
1652  $eatByLabel = $langs->trans('EatByDate');
1653  return array(
1654  self::SELL_OR_EAT_BY_MANDATORY_ID_NONE => $langs->trans('BatchSellOrEatByMandatoryNone'),
1655  self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY => $sellByLabel,
1656  self::SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY => $eatByLabel,
1657  self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT => $langs->trans('BatchSellOrEatByMandatoryAll', $sellByLabel, $eatByLabel),
1658  );
1659  }
1660 
1667  {
1668  $sellOrEatByMandatoryLabel = '';
1669 
1670  $sellOrEatByMandatoryList = self::getSellOrEatByMandatoryList();
1671  if (isset($sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory])) {
1672  $sellOrEatByMandatoryLabel = $sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory];
1673  }
1674 
1675  return $sellOrEatByMandatoryLabel;
1676  }
1677 
1684  public function setMultiLangs($user)
1685  {
1686  global $conf, $langs;
1687 
1688  $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1689  $current_lang = $langs->getDefaultLang();
1690 
1691  foreach ($langs_available as $key => $value) {
1692  if ($key == $current_lang) {
1693  $sql = "SELECT rowid";
1694  $sql .= " FROM ".$this->db->prefix()."product_lang";
1695  $sql .= " WHERE fk_product = ".((int) $this->id);
1696  $sql .= " AND lang = '".$this->db->escape($key)."'";
1697 
1698  $result = $this->db->query($sql);
1699 
1700  if ($this->db->num_rows($result)) { // if there is already a description line for this language
1701  $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1702  $sql2 .= " SET ";
1703  $sql2 .= " label='".$this->db->escape($this->label)."',";
1704  $sql2 .= " description='".$this->db->escape($this->description)."'";
1705  if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1706  $sql2 .= ", note='".$this->db->escape($this->other)."'";
1707  }
1708  $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1709  } else {
1710  $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1711  if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1712  $sql2 .= ", note";
1713  }
1714  $sql2 .= ")";
1715  $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->label)."',";
1716  $sql2 .= " '".$this->db->escape($this->description)."'";
1717  if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1718  $sql2 .= ", '".$this->db->escape($this->other)."'";
1719  }
1720  $sql2 .= ")";
1721  }
1722  dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
1723  if (!$this->db->query($sql2)) {
1724  $this->error = $this->db->lasterror();
1725  return -1;
1726  }
1727  } elseif (isset($this->multilangs[$key])) {
1728  if (empty($this->multilangs["$key"]["label"])) {
1729  $this->errors[] = $key . ' : ' . $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Label"));
1730  return -1;
1731  }
1732 
1733  $sql = "SELECT rowid";
1734  $sql .= " FROM ".$this->db->prefix()."product_lang";
1735  $sql .= " WHERE fk_product = ".((int) $this->id);
1736  $sql .= " AND lang = '".$this->db->escape($key)."'";
1737 
1738  $result = $this->db->query($sql);
1739 
1740  if ($this->db->num_rows($result)) { // if there is already a description line for this language
1741  $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1742  $sql2 .= " SET ";
1743  $sql2 .= " label = '".$this->db->escape($this->multilangs["$key"]["label"])."',";
1744  $sql2 .= " description = '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1745  if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1746  $sql2 .= ", note = '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1747  }
1748  $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1749  } else {
1750  $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1751  if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1752  $sql2 .= ", note";
1753  }
1754  $sql2 .= ")";
1755  $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->multilangs["$key"]["label"])."',";
1756  $sql2 .= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1757  if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1758  $sql2 .= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1759  }
1760  $sql2 .= ")";
1761  }
1762 
1763  // We do not save if main fields are empty
1764  if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
1765  if (!$this->db->query($sql2)) {
1766  $this->error = $this->db->lasterror();
1767  return -1;
1768  }
1769  }
1770  } else {
1771  // language is not current language and we didn't provide a multilang description for this language
1772  }
1773  }
1774 
1775  // Call trigger
1776  $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
1777  if ($result < 0) {
1778  $this->error = $this->db->lasterror();
1779  return -1;
1780  }
1781  // End call triggers
1782 
1783  return 1;
1784  }
1785 
1794  public function delMultiLangs($langtodelete, $user)
1795  {
1796  $sql = "DELETE FROM ".$this->db->prefix()."product_lang";
1797  $sql .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
1798 
1799  dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
1800  $result = $this->db->query($sql);
1801  if ($result) {
1802  // Call trigger
1803  $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
1804  if ($result < 0) {
1805  $this->error = $this->db->lasterror();
1806  dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1807  return -1;
1808  }
1809  // End call triggers
1810  return 1;
1811  } else {
1812  $this->error = $this->db->lasterror();
1813  dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1814  return -1;
1815  }
1816  }
1817 
1826  public function setAccountancyCode($type, $value)
1827  {
1828  global $user, $langs, $conf;
1829 
1830  $error = 0;
1831 
1832  $this->db->begin();
1833 
1834  if ($type == 'buy') {
1835  $field = 'accountancy_code_buy';
1836  } elseif ($type == 'buy_intra') {
1837  $field = 'accountancy_code_buy_intra';
1838  } elseif ($type == 'buy_export') {
1839  $field = 'accountancy_code_buy_export';
1840  } elseif ($type == 'sell') {
1841  $field = 'accountancy_code_sell';
1842  } elseif ($type == 'sell_intra') {
1843  $field = 'accountancy_code_sell_intra';
1844  } elseif ($type == 'sell_export') {
1845  $field = 'accountancy_code_sell_export';
1846  } else {
1847  return -1;
1848  }
1849 
1850  $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ";
1851  $sql .= "$field = '".$this->db->escape($value)."'";
1852  $sql .= " WHERE rowid = ".((int) $this->id);
1853 
1854  dol_syslog(__METHOD__, LOG_DEBUG);
1855  $resql = $this->db->query($sql);
1856 
1857  if ($resql) {
1858  // Call trigger
1859  $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1860  if ($result < 0) {
1861  $error++;
1862  }
1863  // End call triggers
1864 
1865  if ($error) {
1866  $this->db->rollback();
1867  return -1;
1868  }
1869 
1870  $this->$field = $value;
1871 
1872  $this->db->commit();
1873  return 1;
1874  } else {
1875  $this->error = $this->db->lasterror();
1876  $this->db->rollback();
1877  return -1;
1878  }
1879  }
1880 
1886  public function getMultiLangs()
1887  {
1888  global $langs;
1889 
1890  $current_lang = $langs->getDefaultLang();
1891 
1892  $sql = "SELECT lang, label, description, note as other";
1893  $sql .= " FROM ".$this->db->prefix()."product_lang";
1894  $sql .= " WHERE fk_product = ".((int) $this->id);
1895 
1896  $result = $this->db->query($sql);
1897  if ($result) {
1898  while ($obj = $this->db->fetch_object($result)) {
1899  //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
1900  if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
1901  $this->label = $obj->label;
1902  $this->description = $obj->description;
1903  $this->other = $obj->other;
1904  }
1905  $this->multilangs[(string) $obj->lang]["label"] = $obj->label;
1906  $this->multilangs[(string) $obj->lang]["description"] = $obj->description;
1907  $this->multilangs[(string) $obj->lang]["other"] = $obj->other;
1908  }
1909  return 1;
1910  } else {
1911  $this->error = "Error: ".$this->db->lasterror()." - ".$sql;
1912  return -1;
1913  }
1914  }
1915 
1922  private function getArrayForPriceCompare($level = 0)
1923  {
1924  $testExit = array('multiprices','multiprices_ttc','multiprices_base_type','multiprices_min','multiprices_min_ttc','multiprices_tva_tx','multiprices_recuperableonly');
1925 
1926  foreach ($testExit as $field) {
1927  if (!isset($this->$field)) {
1928  return array();
1929  }
1930  $tmparray = $this->$field;
1931  if (!isset($tmparray[$level])) {
1932  return array();
1933  }
1934  }
1935 
1936  $lastPrice = array(
1937  'level' => $level ? $level : 1,
1938  'multiprices' => (float) $this->multiprices[$level],
1939  'multiprices_ttc' => (float) $this->multiprices_ttc[$level],
1940  'multiprices_base_type' => $this->multiprices_base_type[$level],
1941  'multiprices_min' => (float) $this->multiprices_min[$level],
1942  'multiprices_min_ttc' => (float) $this->multiprices_min_ttc[$level],
1943  'multiprices_tva_tx' => (float) $this->multiprices_tva_tx[$level],
1944  'multiprices_recuperableonly' => (float) $this->multiprices_recuperableonly[$level],
1945  );
1946 
1947  return $lastPrice;
1948  }
1949 
1950 
1951  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1959  private function _log_price($user, $level = 0)
1960  {
1961  // phpcs:enable
1962  global $conf;
1963 
1964  $now = dol_now();
1965 
1966  // Clean parameters
1967  if (empty($this->price_by_qty)) {
1968  $this->price_by_qty = 0;
1969  }
1970 
1971  // Add new price
1972  $sql = "INSERT INTO ".$this->db->prefix()."product_price(price_level,date_price, fk_product, fk_user_author, price_label, price, price_ttc, price_base_type,tosell, tva_tx, default_vat_code, recuperableonly,";
1973  $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
1974  $sql .= " VALUES(".($level ? ((int) $level) : 1).", '".$this->db->idate($now)."', ".((int) $this->id).", ".((int) $user->id).", ".(empty($this->price_label) ? "null" : "'".$this->db->escape($this->price_label)."'").", ".((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).",";
1975  $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');
1976  $sql .= ")";
1977 
1978  dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
1979  $resql = $this->db->query($sql);
1980  if (!$resql) {
1981  $this->error = $this->db->lasterror();
1982  dol_print_error($this->db);
1983  return -1;
1984  } else {
1985  return 1;
1986  }
1987  }
1988 
1989 
1990  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1998  public function log_price_delete($user, $rowid)
1999  {
2000  // phpcs:enable
2001  $sql = "DELETE FROM ".$this->db->prefix()."product_price_by_qty";
2002  $sql .= " WHERE fk_product_price = ".((int) $rowid);
2003  $resql = $this->db->query($sql);
2004 
2005  $sql = "DELETE FROM ".$this->db->prefix()."product_price";
2006  $sql .= " WHERE rowid=".((int) $rowid);
2007  $resql = $this->db->query($sql);
2008  if ($resql) {
2009  return 1;
2010  } else {
2011  $this->error = $this->db->lasterror();
2012  return -1;
2013  }
2014  }
2015 
2016 
2026  public function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp = 0)
2027  {
2028  global $conf, $hookmanager, $action;
2029 
2030  // Call hook if any
2031  if (is_object($hookmanager)) {
2032  $parameters = array('thirdparty_seller' => $thirdparty_seller, 'thirdparty_buyer' => $thirdparty_buyer, 'pqp' => $pqp);
2033  // Note that $action and $object may have been modified by some hooks
2034  $reshook = $hookmanager->executeHooks('getSellPrice', $parameters, $this, $action);
2035  if ($reshook > 0) {
2036  return $hookmanager->resArray;
2037  }
2038  }
2039 
2040  // Update if prices fields are defined
2041  $tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id);
2042  $tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id);
2043  if (empty($tva_tx)) {
2044  $tva_npr = 0;
2045  }
2046 
2047  $pu_ht = $this->price;
2048  $pu_ttc = $this->price_ttc;
2049  $price_min = $this->price_min;
2050  $price_base_type = $this->price_base_type;
2051 
2052  // If price per segment
2053  if (getDolGlobalString('PRODUIT_MULTIPRICES') && !empty($thirdparty_buyer->price_level)) {
2054  $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
2055  $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
2056  $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
2057  $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2058  if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
2059  if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2060  $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2061  }
2062  if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2063  $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2064  }
2065  if (empty($tva_tx)) {
2066  $tva_npr = 0;
2067  }
2068  }
2069  } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
2070  // If price per customer
2071  require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2072 
2073  $prodcustprice = new ProductCustomerPrice($this->db);
2074 
2075  $filter = array('t.fk_product' => $this->id, 't.fk_soc' => $thirdparty_buyer->id);
2076 
2077  $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2078  if ($result) {
2079  if (count($prodcustprice->lines) > 0) {
2080  $pu_ht = price($prodcustprice->lines[0]->price);
2081  $price_min = price($prodcustprice->lines[0]->price_min);
2082  $pu_ttc = price($prodcustprice->lines[0]->price_ttc);
2083  $price_base_type = $prodcustprice->lines[0]->price_base_type;
2084  $tva_tx = $prodcustprice->lines[0]->tva_tx;
2085  if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2086  $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
2087  }
2088  $tva_npr = $prodcustprice->lines[0]->recuperableonly;
2089  if (empty($tva_tx)) {
2090  $tva_npr = 0;
2091  }
2092  }
2093  }
2094  } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY')) {
2095  // If price per quantity
2096  if ($this->prices_by_qty[0]) {
2097  // yes, this product has some prices per quantity
2098  // Search price into product_price_by_qty from $this->id
2099  foreach ($this->prices_by_qty_list[0] as $priceforthequantityarray) {
2100  if ($priceforthequantityarray['rowid'] != $pqp) {
2101  continue;
2102  }
2103  // We found the price
2104  if ($priceforthequantityarray['price_base_type'] == 'HT') {
2105  $pu_ht = $priceforthequantityarray['unitprice'];
2106  } else {
2107  $pu_ttc = $priceforthequantityarray['unitprice'];
2108  }
2109  break;
2110  }
2111  }
2112  } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
2113  // If price per quantity and customer
2114  if ($this->prices_by_qty[$thirdparty_buyer->price_level]) {
2115  // yes, this product has some prices per quantity
2116  // Search price into product_price_by_qty from $this->id
2117  foreach ($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray) {
2118  if ($priceforthequantityarray['rowid'] != $pqp) {
2119  continue;
2120  }
2121  // We found the price
2122  if ($priceforthequantityarray['price_base_type'] == 'HT') {
2123  $pu_ht = $priceforthequantityarray['unitprice'];
2124  } else {
2125  $pu_ttc = $priceforthequantityarray['unitprice'];
2126  }
2127  break;
2128  }
2129  }
2130  }
2131 
2132  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);
2133  }
2134 
2135  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2149  public function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
2150  {
2151  // phpcs:enable
2152  global $action, $hookmanager;
2153 
2154  // Call hook if any
2155  if (is_object($hookmanager)) {
2156  $parameters = array(
2157  'prodfournprice' => $prodfournprice,
2158  'qty' => $qty,
2159  'product_id' => $product_id,
2160  'fourn_ref' => $fourn_ref,
2161  'fk_soc' => $fk_soc,
2162  );
2163  // Note that $action and $object may have been modified by some hooks
2164  $reshook = $hookmanager->executeHooks('getBuyPrice', $parameters, $this, $action);
2165  if ($reshook > 0) {
2166  return $hookmanager->resArray;
2167  }
2168  }
2169 
2170  $result = 0;
2171 
2172  // 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)
2173  $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2174  $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,";
2175  $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2176  $sql .= " pfp.packaging";
2177  $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2178  $sql .= " WHERE pfp.rowid = ".((int) $prodfournprice);
2179  if ($qty > 0) {
2180  $sql .= " AND pfp.quantity <= ".((float) $qty);
2181  }
2182  $sql .= " ORDER BY pfp.quantity DESC";
2183 
2184  dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
2185  $resql = $this->db->query($sql);
2186  if ($resql) {
2187  $obj = $this->db->fetch_object($resql);
2188  if ($obj && $obj->quantity > 0) { // If we found a supplier prices from the id of supplier price
2189  if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2190  $prod_supplier = new ProductFournisseur($this->db);
2191  $prod_supplier->product_fourn_price_id = $obj->rowid;
2192  $prod_supplier->id = $obj->fk_product;
2193  $prod_supplier->fourn_qty = $obj->quantity;
2194  $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2195  $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2196 
2197  include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2198  $priceparser = new PriceParser($this->db);
2199  $price_result = $priceparser->parseProductSupplier($prod_supplier);
2200  if ($price_result >= 0) {
2201  $obj->price = $price_result;
2202  }
2203  }
2204  $this->product_fourn_price_id = $obj->rowid;
2205  $this->buyprice = $obj->price; // deprecated
2206  $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
2207  $this->fourn_price_base_type = 'HT'; // Price base type
2208  $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2209  $this->ref_fourn = $obj->ref_supplier; // deprecated
2210  $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2211  $this->desc_supplier = $obj->desc_supplier; // desc supplier
2212  $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2213  $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2214  $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2215  $this->fourn_multicurrency_price = $obj->multicurrency_price;
2216  $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2217  $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2218  $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2219  $this->fourn_multicurrency_code = $obj->multicurrency_code;
2220  if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2221  $this->packaging = $obj->packaging;
2222  }
2223  $result = $obj->fk_product;
2224  return $result;
2225  } else { // If not found
2226  // 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.
2227  $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2228  $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,";
2229  $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2230  $sql .= " pfp.packaging";
2231  $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2232  $sql .= " WHERE 1 = 1";
2233  if ($product_id > 0) {
2234  $sql .= " AND pfp.fk_product = ".((int) $product_id);
2235  }
2236  if ($fourn_ref != 'none') {
2237  $sql .= " AND pfp.ref_fourn = '".$this->db->escape($fourn_ref)."'";
2238  }
2239  if ($fk_soc > 0) {
2240  $sql .= " AND pfp.fk_soc = ".((int) $fk_soc);
2241  }
2242  if ($qty > 0) {
2243  $sql .= " AND pfp.quantity <= ".((float) $qty);
2244  }
2245  $sql .= " ORDER BY pfp.quantity DESC";
2246  $sql .= " LIMIT 1";
2247 
2248  dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
2249  $resql = $this->db->query($sql);
2250  if ($resql) {
2251  $obj = $this->db->fetch_object($resql);
2252  if ($obj && $obj->quantity > 0) { // If found
2253  if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2254  $prod_supplier = new ProductFournisseur($this->db);
2255  $prod_supplier->product_fourn_price_id = $obj->rowid;
2256  $prod_supplier->id = $obj->fk_product;
2257  $prod_supplier->fourn_qty = $obj->quantity;
2258  $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2259  $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2260 
2261  include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2262  $priceparser = new PriceParser($this->db);
2263  $price_result = $priceparser->parseProductSupplier($prod_supplier);
2264  if ($result >= 0) {
2265  $obj->price = $price_result;
2266  }
2267  }
2268  $this->product_fourn_price_id = $obj->rowid;
2269  $this->buyprice = $obj->price; // deprecated
2270  $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
2271  $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
2272  $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
2273  $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2274  $this->ref_fourn = $obj->ref_supplier; // deprecated
2275  $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2276  $this->desc_supplier = $obj->desc_supplier; // desc supplier
2277  $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2278  $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2279  $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2280  $this->fourn_multicurrency_price = $obj->multicurrency_price;
2281  $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2282  $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2283  $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2284  $this->fourn_multicurrency_code = $obj->multicurrency_code;
2285  if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2286  $this->packaging = $obj->packaging;
2287  }
2288  $result = $obj->fk_product;
2289  return $result;
2290  } else {
2291  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é.
2292  }
2293  } else {
2294  $this->error = $this->db->lasterror();
2295  return -3;
2296  }
2297  }
2298  } else {
2299  $this->error = $this->db->lasterror();
2300  return -2;
2301  }
2302  }
2303 
2304 
2323  public function updatePrice($newprice, $newpricebase, $user, $newvat = null, $newminprice = 0, $level = 0, $newnpr = 0, $newpbq = 0, $ignore_autogen = 0, $localtaxes_array = array(), $newdefaultvatcode = '', $price_label = '', $notrigger = 0)
2324  {
2325  global $conf, $langs;
2326 
2327  $lastPriceData = $this->getArrayForPriceCompare($level); // temporary store current price before update
2328 
2329  $id = $this->id;
2330 
2331  dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
2332 
2333  // Clean parameters
2334  if (empty($this->tva_tx)) {
2335  $this->tva_tx = 0;
2336  }
2337  if (empty($newnpr)) {
2338  $newnpr = 0;
2339  }
2340  if (empty($newminprice)) {
2341  $newminprice = 0;
2342  }
2343 
2344  // Check parameters
2345  if ($newvat === null || $newvat == '') { // Maintain '' for backwards compatibility
2346  $newvat = $this->tva_tx;
2347  }
2348 
2349  // If multiprices are enabled, then we check if the current product is subject to price autogeneration
2350  // Price will be modified ONLY when the first one is the one that is being modified
2351  if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
2352  return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
2353  }
2354 
2355  if (!empty($newminprice) && ($newminprice > $newprice)) {
2356  $this->error = 'ErrorPriceCantBeLowerThanMinPrice';
2357  return -1;
2358  }
2359 
2360  if ($newprice !== '' || $newprice === 0) {
2361  if ($newpricebase == 'TTC') {
2362  $price_ttc = price2num($newprice, 'MU');
2363  $price = (float) price2num($newprice) / (1 + ((float) $newvat / 100));
2364  $price = price2num($price, 'MU');
2365 
2366  if ($newminprice != '' || $newminprice == 0) {
2367  $price_min_ttc = price2num($newminprice, 'MU');
2368  $price_min = (float) price2num($newminprice) / (1 + ($newvat / 100));
2369  $price_min = price2num($price_min, 'MU');
2370  } else {
2371  $price_min = 0;
2372  $price_min_ttc = 0;
2373  }
2374  } else {
2375  $price = (float) price2num($newprice, 'MU');
2376  $price_ttc = ($newnpr != 1) ? price2num($newprice) * (1 + ($newvat / 100)) : $price;
2377  $price_ttc = (float) price2num($price_ttc, 'MU');
2378 
2379  if ($newminprice !== '' || $newminprice === 0) {
2380  $price_min = price2num($newminprice, 'MU');
2381  $price_min_ttc = (float) price2num($newminprice) * (1 + ($newvat / 100));
2382  $price_min_ttc = price2num($price_min_ttc, 'MU');
2383  //print 'X'.$newminprice.'-'.$price_min;
2384  } else {
2385  $price_min = 0;
2386  $price_min_ttc = 0;
2387  }
2388  }
2389  //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
2390 
2391  if (count($localtaxes_array) > 0) {
2392  $localtaxtype1 = $localtaxes_array['0'];
2393  $localtax1 = $localtaxes_array['1'];
2394  $localtaxtype2 = $localtaxes_array['2'];
2395  $localtax2 = $localtaxes_array['3'];
2396  } else {
2397  // if array empty, we try to use the vat code
2398  if (!empty($newdefaultvatcode)) {
2399  global $mysoc;
2400  // Get record from code
2401  $sql = "SELECT t.rowid, t.code, t.recuperableonly as tva_npr, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
2402  $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
2403  $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$this->db->escape($mysoc->country_code)."'";
2404  $sql .= " AND t.taux = ".((float) $newdefaultvatcode)." AND t.active = 1";
2405  $sql .= " AND t.code = '".$this->db->escape($newdefaultvatcode)."'";
2406  $resql = $this->db->query($sql);
2407  if ($resql) {
2408  $obj = $this->db->fetch_object($resql);
2409  if ($obj) {
2410  $npr = $obj->tva_npr;
2411  $localtax1 = $obj->localtax1;
2412  $localtax2 = $obj->localtax2;
2413  $localtaxtype1 = $obj->localtax1_type;
2414  $localtaxtype2 = $obj->localtax2_type;
2415  }
2416  }
2417  } else {
2418  // old method. deprecated because we can't retrieve type
2419  $localtaxtype1 = '0';
2420  $localtax1 = get_localtax($newvat, 1);
2421  $localtaxtype2 = '0';
2422  $localtax2 = get_localtax($newvat, 2);
2423  }
2424  }
2425  if (empty($localtax1)) {
2426  $localtax1 = 0; // If = '' then = 0
2427  }
2428  if (empty($localtax2)) {
2429  $localtax2 = 0; // If = '' then = 0
2430  }
2431 
2432  $this->db->begin();
2433 
2434  // Ne pas mettre de quote sur les numeriques decimaux.
2435  // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
2436  $sql = "UPDATE ".$this->db->prefix()."product SET";
2437  $sql .= " price_base_type = '".$this->db->escape($newpricebase)."',";
2438  $sql .= " price = ".(float) $price.",";
2439  $sql .= " price_ttc = ".(float) $price_ttc.",";
2440  $sql .= " price_min = ".(float) $price_min.",";
2441  $sql .= " price_min_ttc = ".(float) $price_min_ttc.",";
2442  $sql .= " localtax1_tx = ".($localtax1 >= 0 ? (float) $localtax1 : 'NULL').",";
2443  $sql .= " localtax2_tx = ".($localtax2 >= 0 ? (float) $localtax2 : 'NULL').",";
2444  $sql .= " localtax1_type = ".($localtaxtype1 != '' ? "'".$this->db->escape($localtaxtype1)."'" : "'0'").",";
2445  $sql .= " localtax2_type = ".($localtaxtype2 != '' ? "'".$this->db->escape($localtaxtype2)."'" : "'0'").",";
2446  $sql .= " default_vat_code = ".($newdefaultvatcode ? "'".$this->db->escape($newdefaultvatcode)."'" : "null").",";
2447  $sql .= " price_label = ".(!empty($price_label) ? "'".$this->db->escape($price_label)."'" : "null").",";
2448  $sql .= " tva_tx = ".(float) price2num($newvat).",";
2449  $sql .= " recuperableonly = '".$this->db->escape($newnpr)."'";
2450  $sql .= " WHERE rowid = ".((int) $id);
2451 
2452  dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2453  $resql = $this->db->query($sql);
2454  if ($resql) {
2455  $this->multiprices[$level] = $price;
2456  $this->multiprices_ttc[$level] = $price_ttc;
2457  $this->multiprices_min[$level] = $price_min;
2458  $this->multiprices_min_ttc[$level] = $price_min_ttc;
2459  $this->multiprices_base_type[$level] = $newpricebase;
2460  $this->multiprices_default_vat_code[$level] = $newdefaultvatcode;
2461  $this->multiprices_tva_tx[$level] = $newvat;
2462  $this->multiprices_recuperableonly[$level] = $newnpr;
2463 
2464  $this->price = $price;
2465  $this->price_label = $price_label;
2466  $this->price_ttc = $price_ttc;
2467  $this->price_min = $price_min;
2468  $this->price_min_ttc = $price_min_ttc;
2469  $this->price_base_type = $newpricebase;
2470  $this->default_vat_code = $newdefaultvatcode;
2471  $this->tva_tx = $newvat;
2472  $this->tva_npr = $newnpr;
2473 
2474  //Local taxes
2475  $this->localtax1_tx = $localtax1;
2476  $this->localtax2_tx = $localtax2;
2477  $this->localtax1_type = $localtaxtype1;
2478  $this->localtax2_type = $localtaxtype2;
2479 
2480  // Price by quantity
2481  $this->price_by_qty = $newpbq;
2482 
2483  // check if price have really change before log
2484  $newPriceData = $this->getArrayForPriceCompare($level);
2485  if (!empty(array_diff_assoc($newPriceData, $lastPriceData)) || !getDolGlobalString('PRODUIT_MULTIPRICES')) {
2486  $this->_log_price($user, $level); // Save price for level into table product_price
2487  }
2488 
2489  $this->level = $level; // Store level of price edited for trigger
2490 
2491  // Call trigger
2492  if (!$notrigger) {
2493  $result = $this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
2494  if ($result < 0) {
2495  $this->db->rollback();
2496  return -1;
2497  }
2498  }
2499  // End call triggers
2500 
2501  $this->db->commit();
2502  } else {
2503  $this->db->rollback();
2504  $this->error = $this->db->lasterror();
2505  return -1;
2506  }
2507  }
2508 
2509  return 1;
2510  }
2511 
2519  public function setPriceExpression($expression_id)
2520  {
2521  global $user;
2522 
2523  $this->fk_price_expression = $expression_id;
2524 
2525  return $this->update($this->id, $user);
2526  }
2527 
2540  public function fetch($id = 0, $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0, $ignore_price_load = 0, $ignore_lang_load = 0)
2541  {
2542  include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2543 
2544  global $langs, $conf;
2545 
2546  dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
2547 
2548  // Check parameters
2549  if (!$id && !$ref && !$ref_ext && !$barcode) {
2550  $this->error = 'ErrorWrongParameters';
2551  dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2552  return -1;
2553  }
2554 
2555  $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,";
2556  $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,";
2557  $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,";
2558  $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.last_main_doc,";
2559  $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,";
2560  if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2561  $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,";
2562  } else {
2563  $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,";
2564  }
2565 
2566  //For MultiCompany
2567  //PMP per entity & Stocks Sharings stock_reel includes only stocks shared with this entity
2568  $separatedEntityPMP = false; // Set to true to get the AWP from table llx_product_perentity instead of field 'pmp' into llx_product.
2569  $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.
2570  $visibleWarehousesEntities = $conf->entity;
2571  if (getDolGlobalString('MULTICOMPANY_PRODUCT_SHARING_ENABLED')) {
2572  if (getDolGlobalString('MULTICOMPANY_PMP_PER_ENTITY_ENABLED')) {
2573  $checkPMPPerEntity = $this->db->query("SELECT pmp FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = ".((int) $id)." AND entity = ".(int) $conf->entity);
2574  if ($this->db->num_rows($checkPMPPerEntity) > 0) {
2575  $separatedEntityPMP = true;
2576  }
2577  }
2578  global $mc;
2579  $separatedStock = true;
2580  if (isset($mc->sharings['stock']) && !empty($mc->sharings['stock'])) {
2581  $visibleWarehousesEntities .= "," . implode(",", $mc->sharings['stock']);
2582  }
2583  }
2584  if ($separatedEntityPMP) {
2585  $sql .= " ppe.pmp,";
2586  } else {
2587  $sql .= " p.pmp,";
2588  }
2589  $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.sell_or_eat_by_mandatory, p.batch_mask, p.fk_unit,";
2590  $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf,";
2591  $sql .= " p.price_label,";
2592  if ($separatedStock) {
2593  $sql .= " SUM(sp.reel) as stock";
2594  } else {
2595  $sql .= " p.stock";
2596  }
2597  $sql .= " FROM ".$this->db->prefix()."product as p";
2598  if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') || $separatedEntityPMP) {
2599  $sql .= " LEFT JOIN " . $this->db->prefix() . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
2600  }
2601  if ($separatedStock) {
2602  $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)."))";
2603  }
2604 
2605  if ($id) {
2606  $sql .= " WHERE p.rowid = ".((int) $id);
2607  } else {
2608  $sql .= " WHERE p.entity IN (".getEntity($this->element).")";
2609  if ($ref) {
2610  $sql .= " AND p.ref = '".$this->db->escape($ref)."'";
2611  } elseif ($ref_ext) {
2612  $sql .= " AND p.ref_ext = '".$this->db->escape($ref_ext)."'";
2613  } elseif ($barcode) {
2614  $sql .= " AND p.barcode = '".$this->db->escape($barcode)."'";
2615  }
2616  }
2617  if ($separatedStock) {
2618  $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,";
2619  $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,";
2620  $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,";
2621  $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
2622  $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,";
2623  if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2624  $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,";
2625  } else {
2626  $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,";
2627  }
2628  if ($separatedEntityPMP) {
2629  $sql .= " ppe.pmp,";
2630  } else {
2631  $sql .= " p.pmp,";
2632  }
2633  $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.sell_or_eat_by_mandatory, p.batch_mask, p.fk_unit,";
2634  $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf";
2635  $sql .= " ,p.price_label";
2636  if (!$separatedStock) {
2637  $sql .= ", p.stock";
2638  }
2639  }
2640 
2641  $resql = $this->db->query($sql);
2642  if ($resql) {
2643  unset($this->oldcopy);
2644 
2645  if ($this->db->num_rows($resql) > 0) {
2646  $obj = $this->db->fetch_object($resql);
2647 
2648  $this->id = $obj->rowid;
2649  $this->ref = $obj->ref;
2650  $this->ref_ext = $obj->ref_ext;
2651  $this->label = $obj->label;
2652  $this->description = $obj->description;
2653  $this->url = $obj->url;
2654  $this->note_public = $obj->note_public;
2655  $this->note_private = $obj->note_private;
2656  $this->note = $obj->note_private; // deprecated
2657 
2658  $this->type = $obj->fk_product_type;
2659  $this->price_label = $obj->price_label;
2660  $this->status = $obj->tosell;
2661  $this->status_buy = $obj->tobuy;
2662  $this->status_batch = $obj->tobatch;
2663  $this->sell_or_eat_by_mandatory = $obj->sell_or_eat_by_mandatory;
2664  $this->batch_mask = $obj->batch_mask;
2665 
2666  $this->customcode = $obj->customcode;
2667  $this->country_id = $obj->fk_country;
2668  $this->country_code = getCountry($this->country_id, 2, $this->db);
2669  $this->state_id = $obj->fk_state;
2670  $this->lifetime = $obj->lifetime;
2671  $this->qc_frequency = $obj->qc_frequency;
2672  $this->price = $obj->price;
2673  $this->price_ttc = $obj->price_ttc;
2674  $this->price_min = $obj->price_min;
2675  $this->price_min_ttc = $obj->price_min_ttc;
2676  $this->price_base_type = $obj->price_base_type;
2677  $this->cost_price = $obj->cost_price;
2678  $this->default_vat_code = $obj->default_vat_code;
2679  $this->tva_tx = $obj->tva_tx;
2681  $this->tva_npr = $obj->tva_npr;
2683  $this->localtax1_tx = $obj->localtax1_tx;
2684  $this->localtax2_tx = $obj->localtax2_tx;
2685  $this->localtax1_type = $obj->localtax1_type;
2686  $this->localtax2_type = $obj->localtax2_type;
2687 
2688  $this->finished = $obj->finished;
2689  $this->fk_default_bom = $obj->fk_default_bom;
2690 
2691  $this->duration = $obj->duration;
2692  $this->duration_value = $obj->duration ? substr($obj->duration, 0, dol_strlen($obj->duration) - 1) : null;
2693  $this->duration_unit = $obj->duration ? substr($obj->duration, -1) : null;
2694  $this->canvas = $obj->canvas;
2695  $this->net_measure = $obj->net_measure;
2696  $this->net_measure_units = $obj->net_measure_units;
2697  $this->weight = $obj->weight;
2698  $this->weight_units = $obj->weight_units;
2699  $this->length = $obj->length;
2700  $this->length_units = $obj->length_units;
2701  $this->width = $obj->width;
2702  $this->width_units = $obj->width_units;
2703  $this->height = $obj->height;
2704  $this->height_units = $obj->height_units;
2705 
2706  $this->surface = $obj->surface;
2707  $this->surface_units = $obj->surface_units;
2708  $this->volume = $obj->volume;
2709  $this->volume_units = $obj->volume_units;
2710  $this->barcode = $obj->barcode;
2711  $this->barcode_type = $obj->fk_barcode_type;
2712 
2713  $this->accountancy_code_buy = $obj->accountancy_code_buy;
2714  $this->accountancy_code_buy_intra = $obj->accountancy_code_buy_intra;
2715  $this->accountancy_code_buy_export = $obj->accountancy_code_buy_export;
2716  $this->accountancy_code_sell = $obj->accountancy_code_sell;
2717  $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
2718  $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
2719 
2720  $this->fk_default_warehouse = $obj->fk_default_warehouse;
2721  $this->fk_default_workstation = $obj->fk_default_workstation;
2722  $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
2723  $this->desiredstock = $obj->desiredstock;
2724  $this->stock_reel = $obj->stock;
2725  $this->pmp = $obj->pmp;
2726 
2727  $this->date_creation = $obj->datec;
2728  $this->date_modification = $obj->tms;
2729  $this->import_key = $obj->import_key;
2730  $this->entity = $obj->entity;
2731 
2732  $this->ref_ext = $obj->ref_ext;
2733  $this->fk_price_expression = $obj->fk_price_expression;
2734  $this->fk_unit = $obj->fk_unit;
2735  $this->price_autogen = $obj->price_autogen;
2736  $this->model_pdf = $obj->model_pdf;
2737  $this->last_main_doc = $obj->last_main_doc;
2738 
2739  $this->mandatory_period = $obj->mandatory_period;
2740 
2741  $this->db->free($resql);
2742 
2743  // fetch optionals attributes and labels
2744  $this->fetch_optionals();
2745 
2746  // Multilangs
2747  if (getDolGlobalInt('MAIN_MULTILANGS') && empty($ignore_lang_load)) {
2748  $this->getMultiLangs();
2749  }
2750 
2751  // Load multiprices array
2752  if (getDolGlobalString('PRODUIT_MULTIPRICES') && empty($ignore_price_load)) { // prices per segment
2753  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
2754  $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2755  $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2756  $sql .= " ,price_label";
2757  $sql .= " FROM ".$this->db->prefix()."product_price";
2758  $sql .= " WHERE entity IN (".getEntity('productprice').")";
2759  $sql .= " AND price_level=".((int) $i);
2760  $sql .= " AND fk_product = ".((int) $this->id);
2761  $sql .= " ORDER BY date_price DESC, rowid DESC"; // Get the most recent line
2762  $sql .= " LIMIT 1"; // Only the first one
2763  $resql = $this->db->query($sql);
2764  if ($resql) {
2765  $result = $this->db->fetch_array($resql);
2766 
2767  $this->multiprices[$i] = $result ? $result["price"] : null;
2768  $this->multiprices_ttc[$i] = $result ? $result["price_ttc"] : null;
2769  $this->multiprices_min[$i] = $result ? $result["price_min"] : null;
2770  $this->multiprices_min_ttc[$i] = $result ? $result["price_min_ttc"] : null;
2771  $this->multiprices_base_type[$i] = $result ? $result["price_base_type"] : null;
2772  // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2773  $this->multiprices_tva_tx[$i] = $result ? $result["tva_tx"].($result ? ' ('.$result['default_vat_code'].')' : '') : null;
2774  $this->multiprices_recuperableonly[$i] = $result ? $result["recuperableonly"] : null;
2775 
2776  // Price by quantity
2777  /*
2778  $this->prices_by_qty[$i]=$result["price_by_qty"];
2779  $this->prices_by_qty_id[$i]=$result["rowid"];
2780  // Récuperation de la liste des prix selon qty si flag positionné
2781  if ($this->prices_by_qty[$i] == 1)
2782  {
2783  $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2784  $sql.= " FROM ".$this->db->prefix()."product_price_by_qty";
2785  $sql.= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2786  $sql.= " ORDER BY quantity ASC";
2787 
2788  $resql = $this->db->query($sql);
2789  if ($resql)
2790  {
2791  $resultat=array();
2792  $ii=0;
2793  while ($result= $this->db->fetch_array($resql)) {
2794  $resultat[$ii]=array();
2795  $resultat[$ii]["rowid"]=$result["rowid"];
2796  $resultat[$ii]["price"]= $result["price"];
2797  $resultat[$ii]["unitprice"]= $result["unitprice"];
2798  $resultat[$ii]["quantity"]= $result["quantity"];
2799  $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2800  $resultat[$ii]["remise"]= $result["remise"]; // deprecated
2801  $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2802  $ii++;
2803  }
2804  $this->prices_by_qty_list[$i]=$resultat;
2805  }
2806  else
2807  {
2808  dol_print_error($this->db);
2809  return -1;
2810  }
2811  }*/
2812  } else {
2813  $this->error = $this->db->lasterror;
2814  return -1;
2815  }
2816  }
2817  } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') && empty($ignore_price_load)) { // prices per customers
2818  // Nothing loaded by default. List may be very long.
2819  } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') && empty($ignore_price_load)) { // prices per quantity
2820  $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2821  $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
2822  $sql .= " FROM ".$this->db->prefix()."product_price";
2823  $sql .= " WHERE fk_product = ".((int) $this->id);
2824  $sql .= " ORDER BY date_price DESC, rowid DESC";
2825  $sql .= " LIMIT 1";
2826 
2827  $resql = $this->db->query($sql);
2828  if ($resql) {
2829  $result = $this->db->fetch_array($resql);
2830 
2831  if ($result) {
2832  // Price by quantity
2833  $this->prices_by_qty[0] = $result["price_by_qty"];
2834  $this->prices_by_qty_id[0] = $result["rowid"];
2835  // Récuperation de la liste des prix selon qty si flag positionné
2836  if ($this->prices_by_qty[0] == 1) {
2837  $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
2838  $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2839  $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[0]);
2840  $sql .= " ORDER BY quantity ASC";
2841 
2842  $resql = $this->db->query($sql);
2843  if ($resql) {
2844  $resultat = array();
2845  $ii = 0;
2846  while ($result = $this->db->fetch_array($resql)) {
2847  $resultat[$ii] = array();
2848  $resultat[$ii]["rowid"] = $result["rowid"];
2849  $resultat[$ii]["price"] = $result["price"];
2850  $resultat[$ii]["unitprice"] = $result["unitprice"];
2851  $resultat[$ii]["quantity"] = $result["quantity"];
2852  $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2853  //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
2854  $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2855  $ii++;
2856  }
2857  $this->prices_by_qty_list[0] = $resultat;
2858  } else {
2859  $this->error = $this->db->lasterror;
2860  return -1;
2861  }
2862  }
2863  }
2864  } else {
2865  $this->error = $this->db->lasterror;
2866  return -1;
2867  }
2868  } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') && empty($ignore_price_load)) { // prices per customer and quantity
2869  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
2870  $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2871  $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2872  $sql .= " FROM ".$this->db->prefix()."product_price";
2873  $sql .= " WHERE entity IN (".getEntity('productprice').")";
2874  $sql .= " AND price_level=".((int) $i);
2875  $sql .= " AND fk_product = ".((int) $this->id);
2876  $sql .= " ORDER BY date_price DESC, rowid DESC";
2877  $sql .= " LIMIT 1";
2878  $resql = $this->db->query($sql);
2879  if (!$resql) {
2880  $this->error = $this->db->lasterror;
2881  return -1;
2882  } elseif ($result = $this->db->fetch_array($resql)) {
2883  $this->multiprices[$i] = (!empty($result["price"]) ? $result["price"] : 0);
2884  $this->multiprices_ttc[$i] = (!empty($result["price_ttc"]) ? $result["price_ttc"] : 0);
2885  $this->multiprices_min[$i] = (!empty($result["price_min"]) ? $result["price_min"] : 0);
2886  $this->multiprices_min_ttc[$i] = (!empty($result["price_min_ttc"]) ? $result["price_min_ttc"] : 0);
2887  $this->multiprices_base_type[$i] = (!empty($result["price_base_type"]) ? $result["price_base_type"] : '');
2888  // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2889  $this->multiprices_tva_tx[$i] = (!empty($result["tva_tx"]) ? $result["tva_tx"] : 0); // TODO Add ' ('.$result['default_vat_code'].')'
2890  $this->multiprices_recuperableonly[$i] = (!empty($result["recuperableonly"]) ? $result["recuperableonly"] : 0);
2891 
2892  // Price by quantity
2893  $this->prices_by_qty[$i] = (!empty($result["price_by_qty"]) ? $result["price_by_qty"] : 0);
2894  $this->prices_by_qty_id[$i] = (!empty($result["rowid"]) ? $result["rowid"] : 0);
2895  // Récuperation de la liste des prix selon qty si flag positionné
2896  if ($this->prices_by_qty[$i] == 1) {
2897  $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2898  $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2899  $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2900  $sql .= " ORDER BY quantity ASC";
2901 
2902  $resql = $this->db->query($sql);
2903  if ($resql) {
2904  $resultat = array();
2905  $ii = 0;
2906  while ($result = $this->db->fetch_array($resql)) {
2907  $resultat[$ii] = array();
2908  $resultat[$ii]["rowid"] = $result["rowid"];
2909  $resultat[$ii]["price"] = $result["price"];
2910  $resultat[$ii]["unitprice"] = $result["unitprice"];
2911  $resultat[$ii]["quantity"] = $result["quantity"];
2912  $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2913  $resultat[$ii]["remise"] = $result["remise"]; // deprecated
2914  $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2915  $ii++;
2916  }
2917  $this->prices_by_qty_list[$i] = $resultat;
2918  } else {
2919  $this->error = $this->db->lasterror;
2920  return -1;
2921  }
2922  }
2923  }
2924  }
2925  }
2926 
2927  if (isModEnabled('dynamicprices') && !empty($this->fk_price_expression) && empty($ignore_expression)) {
2928  include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2929  $priceparser = new PriceParser($this->db);
2930  $price_result = $priceparser->parseProduct($this);
2931  if ($price_result >= 0) {
2932  $this->price = $price_result;
2933  // Calculate the VAT
2934  $this->price_ttc = (float) price2num($this->price) * (1 + ($this->tva_tx / 100));
2935  $this->price_ttc = (float) price2num($this->price_ttc, 'MU');
2936  }
2937  }
2938 
2939  // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
2940  // Instead we just init the stock_warehouse array
2941  $this->stock_warehouse = array();
2942 
2943  return 1;
2944  } else {
2945  return 0;
2946  }
2947  } else {
2948  $this->error = $this->db->lasterror();
2949  return -1;
2950  }
2951  }
2952 
2953  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2960  public function load_stats_mo($socid = 0)
2961  {
2962  // phpcs:enable
2963  global $user, $hookmanager, $action;
2964 
2965  $error = 0;
2966 
2967  foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
2968  $this->stats_mo['customers_'.$role] = 0;
2969  $this->stats_mo['nb_'.$role] = 0;
2970  $this->stats_mo['qty_'.$role] = 0;
2971 
2972  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2973  $sql .= " SUM(mp.qty) as qty";
2974  $sql .= " FROM ".$this->db->prefix()."mrp_mo as c";
2975  $sql .= " INNER JOIN ".$this->db->prefix()."mrp_production as mp ON mp.fk_mo=c.rowid";
2976  if (!$user->hasRight('societe', 'client', 'voir')) {
2977  $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc=c.fk_soc AND sc.fk_user = ".((int) $user->id);
2978  }
2979  $sql .= " WHERE ";
2980  $sql .= " c.entity IN (".getEntity('mo').")";
2981 
2982  $sql .= " AND mp.fk_product = ".((int) $this->id);
2983  $sql .= " AND mp.role ='".$this->db->escape($role)."'";
2984  if ($socid > 0) {
2985  $sql .= " AND c.fk_soc = ".((int) $socid);
2986  }
2987 
2988  $result = $this->db->query($sql);
2989  if ($result) {
2990  $obj = $this->db->fetch_object($result);
2991  $this->stats_mo['customers_'.$role] = $obj->nb_customers ? $obj->nb_customers : 0;
2992  $this->stats_mo['nb_'.$role] = $obj->nb ? $obj->nb : 0;
2993  $this->stats_mo['qty_'.$role] = $obj->qty ? price2num($obj->qty, 'MS') : 0; // qty may be a float due to the SUM()
2994  } else {
2995  $this->error = $this->db->error();
2996  $error++;
2997  }
2998  }
2999 
3000  if (!empty($error)) {
3001  return -1;
3002  }
3003 
3004  $parameters = array('socid' => $socid);
3005  $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3006  if ($reshook > 0) {
3007  $this->stats_mo = $hookmanager->resArray['stats_mo'];
3008  }
3009 
3010  return 1;
3011  }
3012 
3013  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3020  public function load_stats_bom($socid = 0)
3021  {
3022  // phpcs:enable
3023  global $user, $hookmanager, $action;
3024 
3025  $error = 0;
3026 
3027  $this->stats_bom['nb_toproduce'] = 0;
3028  $this->stats_bom['nb_toconsume'] = 0;
3029  $this->stats_bom['qty_toproduce'] = 0;
3030  $this->stats_bom['qty_toconsume'] = 0;
3031 
3032  $sql = "SELECT COUNT(DISTINCT b.rowid) as nb_toproduce,";
3033  $sql .= " SUM(b.qty) as qty_toproduce";
3034  $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3035  $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
3036  $sql .= " WHERE ";
3037  $sql .= " b.entity IN (".getEntity('bom').")";
3038  $sql .= " AND b.fk_product =".((int) $this->id);
3039  $sql .= " GROUP BY b.rowid";
3040 
3041  $result = $this->db->query($sql);
3042  if ($result) {
3043  $obj = $this->db->fetch_object($result);
3044  $this->stats_bom['nb_toproduce'] = !empty($obj->nb_toproduce) ? $obj->nb_toproduce : 0;
3045  $this->stats_bom['qty_toproduce'] = !empty($obj->qty_toproduce) ? price2num($obj->qty_toproduce) : 0;
3046  } else {
3047  $this->error = $this->db->error();
3048  $error++;
3049  }
3050 
3051  $sql = "SELECT COUNT(DISTINCT bl.rowid) as nb_toconsume,";
3052  $sql .= " SUM(bl.qty) as qty_toconsume";
3053  $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3054  $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
3055  $sql .= " WHERE ";
3056  $sql .= " b.entity IN (".getEntity('bom').")";
3057  $sql .= " AND bl.fk_product =".((int) $this->id);
3058 
3059  $result = $this->db->query($sql);
3060  if ($result) {
3061  $obj = $this->db->fetch_object($result);
3062  $this->stats_bom['nb_toconsume'] = !empty($obj->nb_toconsume) ? $obj->nb_toconsume : 0;
3063  $this->stats_bom['qty_toconsume'] = !empty($obj->qty_toconsume) ? price2num($obj->qty_toconsume) : 0;
3064  } else {
3065  $this->error = $this->db->error();
3066  $error++;
3067  }
3068 
3069  if (!empty($error)) {
3070  return -1;
3071  }
3072 
3073  $parameters = array('socid' => $socid);
3074  $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3075  if ($reshook > 0) {
3076  $this->stats_bom = $hookmanager->resArray['stats_bom'];
3077  }
3078 
3079  return 1;
3080  }
3081 
3082  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3089  public function load_stats_propale($socid = 0)
3090  {
3091  // phpcs:enable
3092  global $conf, $user, $hookmanager, $action;
3093 
3094  $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
3095  $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3096  $sql .= " FROM ".$this->db->prefix()."propaldet as pd";
3097  $sql .= ", ".$this->db->prefix()."propal as p";
3098  $sql .= ", ".$this->db->prefix()."societe as s";
3099  if (!$user->hasRight('societe', 'client', 'voir')) {
3100  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3101  }
3102  $sql .= " WHERE p.rowid = pd.fk_propal";
3103  $sql .= " AND p.fk_soc = s.rowid";
3104  $sql .= " AND p.entity IN (".getEntity('propal').")";
3105  $sql .= " AND pd.fk_product = ".((int) $this->id);
3106  if (!$user->hasRight('societe', 'client', 'voir')) {
3107  $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3108  }
3109  //$sql.= " AND pr.fk_statut != 0";
3110  if ($socid > 0) {
3111  $sql .= " AND p.fk_soc = ".((int) $socid);
3112  }
3113 
3114  $result = $this->db->query($sql);
3115  if ($result) {
3116  $obj = $this->db->fetch_object($result);
3117  $this->stats_propale['customers'] = $obj->nb_customers;
3118  $this->stats_propale['nb'] = $obj->nb;
3119  $this->stats_propale['rows'] = $obj->nb_rows;
3120  $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
3121 
3122  // if it's a virtual product, maybe it is in proposal by extension
3123  if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3124  $TFather = $this->getFather();
3125  if (is_array($TFather) && !empty($TFather)) {
3126  foreach ($TFather as &$fatherData) {
3127  $pFather = new Product($this->db);
3128  $pFather->id = $fatherData['id'];
3129  $qtyCoef = $fatherData['qty'];
3130 
3131  if ($fatherData['incdec']) {
3132  $pFather->load_stats_propale($socid);
3133 
3134  $this->stats_propale['customers'] += $pFather->stats_propale['customers'];
3135  $this->stats_propale['nb'] += $pFather->stats_propale['nb'];
3136  $this->stats_propale['rows'] += $pFather->stats_propale['rows'];
3137  $this->stats_propale['qty'] += $pFather->stats_propale['qty'] * $qtyCoef;
3138  }
3139  }
3140  }
3141  }
3142 
3143  $parameters = array('socid' => $socid);
3144  $reshook = $hookmanager->executeHooks('loadStatsCustomerProposal', $parameters, $this, $action);
3145  if ($reshook > 0) {
3146  $this->stats_propale = $hookmanager->resArray['stats_propale'];
3147  }
3148 
3149  return 1;
3150  } else {
3151  $this->error = $this->db->error();
3152  return -1;
3153  }
3154  }
3155 
3156 
3157  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3164  public function load_stats_proposal_supplier($socid = 0)
3165  {
3166  // phpcs:enable
3167  global $conf, $user, $hookmanager, $action;
3168 
3169  $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
3170  $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3171  $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as pd";
3172  $sql .= ", ".$this->db->prefix()."supplier_proposal as p";
3173  $sql .= ", ".$this->db->prefix()."societe as s";
3174  if (!$user->hasRight('societe', 'client', 'voir')) {
3175  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3176  }
3177  $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
3178  $sql .= " AND p.fk_soc = s.rowid";
3179  $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
3180  $sql .= " AND pd.fk_product = ".((int) $this->id);
3181  if (!$user->hasRight('societe', 'client', 'voir')) {
3182  $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3183  }
3184  //$sql.= " AND pr.fk_statut != 0";
3185  if ($socid > 0) {
3186  $sql .= " AND p.fk_soc = ".((int) $socid);
3187  }
3188 
3189  $result = $this->db->query($sql);
3190  if ($result) {
3191  $obj = $this->db->fetch_object($result);
3192  $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
3193  $this->stats_proposal_supplier['nb'] = $obj->nb;
3194  $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
3195  $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
3196 
3197  $parameters = array('socid' => $socid);
3198  $reshook = $hookmanager->executeHooks('loadStatsSupplierProposal', $parameters, $this, $action);
3199  if ($reshook > 0) {
3200  $this->stats_proposal_supplier = $hookmanager->resArray['stats_proposal_supplier'];
3201  }
3202 
3203  return 1;
3204  } else {
3205  $this->error = $this->db->error();
3206  return -1;
3207  }
3208  }
3209 
3210 
3211  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3220  public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
3221  {
3222  // phpcs:enable
3223  global $conf, $user, $hookmanager, $action;
3224 
3225  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3226  $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3227  $sql .= " FROM ".$this->db->prefix()."commandedet as cd";
3228  $sql .= ", ".$this->db->prefix()."commande as c";
3229  $sql .= ", ".$this->db->prefix()."societe as s";
3230  if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3231  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3232  }
3233  $sql .= " WHERE c.rowid = cd.fk_commande";
3234  $sql .= " AND c.fk_soc = s.rowid";
3235  $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'commande').")";
3236  $sql .= " AND cd.fk_product = ".((int) $this->id);
3237  if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3238  $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3239  }
3240  if ($socid > 0) {
3241  $sql .= " AND c.fk_soc = ".((int) $socid);
3242  }
3243  if ($filtrestatut != '') {
3244  $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")";
3245  }
3246 
3247  $result = $this->db->query($sql);
3248  if ($result) {
3249  $obj = $this->db->fetch_object($result);
3250  $this->stats_commande['customers'] = $obj->nb_customers;
3251  $this->stats_commande['nb'] = $obj->nb;
3252  $this->stats_commande['rows'] = $obj->nb_rows;
3253  $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
3254 
3255  // if it's a virtual product, maybe it is in order by extension
3256  if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3257  $TFather = $this->getFather();
3258  if (is_array($TFather) && !empty($TFather)) {
3259  foreach ($TFather as &$fatherData) {
3260  $pFather = new Product($this->db);
3261  $pFather->id = $fatherData['id'];
3262  $qtyCoef = $fatherData['qty'];
3263 
3264  if ($fatherData['incdec']) {
3265  $pFather->load_stats_commande($socid, $filtrestatut);
3266 
3267  $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
3268  $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
3269  $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
3270  $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
3271  }
3272  }
3273  }
3274  }
3275 
3276  // If stock decrease is on invoice validation, the theoretical stock continue to
3277  // count the orders to ship in theoretical stock when some are already removed by invoice validation.
3278  if ($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
3279  if (getDolGlobalString('DECREASE_ONLY_UNINVOICEDPRODUCTS')) {
3280  // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation but only if order not yet invoice.
3281  $adeduire = 0;
3282  $sql = "SELECT SUM(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3283  $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3284  $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'))";
3285  $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_source = c.rowid";
3286  $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND c.facture = 0 AND fd.fk_product = ".((int) $this->id);
3287 
3288  dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3289  $resql = $this->db->query($sql);
3290  if ($resql) {
3291  if ($this->db->num_rows($resql) > 0) {
3292  $obj = $this->db->fetch_object($resql);
3293  $adeduire += $obj->count;
3294  }
3295  }
3296 
3297  $this->stats_commande['qty'] -= $adeduire;
3298  } else {
3299  // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is off, we make a compensation with lines of invoices linked to the order
3300  include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3301 
3302  // For every order having invoice already validated we need to decrease stock cause it's in physical stock
3303  $adeduire = 0;
3304  $sql = "SELECT sum(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet as fd ";
3305  $sql .= " JOIN ".MAIN_DB_PREFIX."facture as f ON fd.fk_facture = f.rowid";
3306  $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'))";
3307  $sql .= " JOIN ".MAIN_DB_PREFIX."commande as c ON el.fk_source = c.rowid";
3308  $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product = ".((int) $this->id);
3309 
3310  dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3311  $resql = $this->db->query($sql);
3312  if ($resql) {
3313  if ($this->db->num_rows($resql) > 0) {
3314  $obj = $this->db->fetch_object($resql);
3315  $adeduire += $obj->count;
3316  }
3317  } else {
3318  $this->error = $this->db->error();
3319  return -1;
3320  }
3321 
3322  $this->stats_commande['qty'] -= $adeduire;
3323  }
3324  }
3325 
3326  $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3327  $reshook = $hookmanager->executeHooks('loadStatsCustomerOrder', $parameters, $this, $action);
3328  if ($reshook > 0) {
3329  $this->stats_commande = $hookmanager->resArray['stats_commande'];
3330  }
3331  return 1;
3332  } else {
3333  $this->error = $this->db->error();
3334  return -1;
3335  }
3336  }
3337 
3338  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3348  public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3349  {
3350  // phpcs:enable
3351  global $conf, $user, $hookmanager, $action;
3352 
3353  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
3354  $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3355  $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as cd";
3356  $sql .= ", ".$this->db->prefix()."commande_fournisseur as c";
3357  $sql .= ", ".$this->db->prefix()."societe as s";
3358  if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3359  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3360  }
3361  $sql .= " WHERE c.rowid = cd.fk_commande";
3362  $sql .= " AND c.fk_soc = s.rowid";
3363  $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3364  $sql .= " AND cd.fk_product = ".((int) $this->id);
3365  if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3366  $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3367  }
3368  if ($socid > 0) {
3369  $sql .= " AND c.fk_soc = ".((int) $socid);
3370  }
3371  if ($filtrestatut != '') {
3372  $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
3373  }
3374  if (!empty($dateofvirtualstock)) {
3375  $sql .= " AND c.date_livraison <= '".$this->db->idate($dateofvirtualstock)."'";
3376  }
3377 
3378  $result = $this->db->query($sql);
3379  if ($result) {
3380  $obj = $this->db->fetch_object($result);
3381  $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
3382  $this->stats_commande_fournisseur['nb'] = $obj->nb;
3383  $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
3384  $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3385 
3386  $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3387  $reshook = $hookmanager->executeHooks('loadStatsSupplierOrder', $parameters, $this, $action);
3388  if ($reshook > 0) {
3389  $this->stats_commande_fournisseur = $hookmanager->resArray['stats_commande_fournisseur'];
3390  }
3391 
3392  return 1;
3393  } else {
3394  $this->error = $this->db->error().' sql='.$sql;
3395  return -1;
3396  }
3397  }
3398 
3399  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3409  public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $filterShipmentStatus = '')
3410  {
3411  // phpcs:enable
3412  global $conf, $user, $hookmanager, $action;
3413 
3414  $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
3415  $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
3416  $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
3417  $sql .= ", ".$this->db->prefix()."commandedet as cd";
3418  $sql .= ", ".$this->db->prefix()."commande as c";
3419  $sql .= ", ".$this->db->prefix()."expedition as e";
3420  $sql .= ", ".$this->db->prefix()."societe as s";
3421  if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3422  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3423  }
3424  $sql .= " WHERE e.rowid = ed.fk_expedition";
3425  $sql .= " AND c.rowid = cd.fk_commande";
3426  $sql .= " AND e.fk_soc = s.rowid";
3427  $sql .= " AND e.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'expedition').")";
3428  $sql .= " AND ed.fk_elementdet = cd.rowid";
3429  $sql .= " AND cd.fk_product = ".((int) $this->id);
3430  if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3431  $sql .= " AND e.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3432  }
3433  if ($socid > 0) {
3434  $sql .= " AND e.fk_soc = ".((int) $socid);
3435  }
3436  if ($filtrestatut != '') {
3437  $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3438  }
3439  if (!empty($filterShipmentStatus)) {
3440  $sql .= " AND e.fk_statut IN (".$this->db->sanitize($filterShipmentStatus).")";
3441  }
3442 
3443  $result = $this->db->query($sql);
3444  if ($result) {
3445  $obj = $this->db->fetch_object($result);
3446  $this->stats_expedition['customers'] = $obj->nb_customers;
3447  $this->stats_expedition['nb'] = $obj->nb;
3448  $this->stats_expedition['rows'] = $obj->nb_rows;
3449  $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
3450 
3451  // if it's a virtual product, maybe it is in sending by extension
3452  if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3453  $TFather = $this->getFather();
3454  if (is_array($TFather) && !empty($TFather)) {
3455  foreach ($TFather as &$fatherData) {
3456  $pFather = new Product($this->db);
3457  $pFather->id = $fatherData['id'];
3458  $qtyCoef = $fatherData['qty'];
3459 
3460  if ($fatherData['incdec']) {
3461  $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
3462 
3463  $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
3464  $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
3465  $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
3466  $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
3467  }
3468  }
3469  }
3470  }
3471 
3472  $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock, 'filterShipmentStatus' => $filterShipmentStatus);
3473  $reshook = $hookmanager->executeHooks('loadStatsSending', $parameters, $this, $action);
3474  if ($reshook > 0) {
3475  $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
3476  }
3477 
3478  return 1;
3479  } else {
3480  $this->error = $this->db->error();
3481  return -1;
3482  }
3483  }
3484 
3485  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3495  public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3496  {
3497  // phpcs:enable
3498  global $conf, $user, $hookmanager, $action;
3499 
3500  $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_suppliers, COUNT(DISTINCT cf.rowid) as nb,";
3501  $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3502  $sql .= " FROM ".$this->db->prefix()."receptiondet_batch as fd";
3503  $sql .= ", ".$this->db->prefix()."commande_fournisseur as cf";
3504  $sql .= ", ".$this->db->prefix()."societe as s";
3505  if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3506  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3507  }
3508  $sql .= " WHERE cf.rowid = fd.fk_element";
3509  $sql .= " AND cf.fk_soc = s.rowid";
3510  $sql .= " AND cf.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3511  $sql .= " AND fd.fk_product = ".((int) $this->id);
3512  if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3513  $sql .= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3514  }
3515  if ($socid > 0) {
3516  $sql .= " AND cf.fk_soc = ".((int) $socid);
3517  }
3518  if ($filtrestatut != '') {
3519  $sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3520  }
3521  if (!empty($dateofvirtualstock)) {
3522  $sql .= " AND fd.datec <= '".$this->db->idate($dateofvirtualstock)."'";
3523  }
3524 
3525  $result = $this->db->query($sql);
3526  if ($result) {
3527  $obj = $this->db->fetch_object($result);
3528  $this->stats_reception['suppliers'] = $obj->nb_suppliers;
3529  $this->stats_reception['nb'] = $obj->nb;
3530  $this->stats_reception['rows'] = $obj->nb_rows;
3531  $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
3532 
3533  $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3534  $reshook = $hookmanager->executeHooks('loadStatsReception', $parameters, $this, $action);
3535  if ($reshook > 0) {
3536  $this->stats_reception = $hookmanager->resArray['stats_reception'];
3537  }
3538 
3539  return 1;
3540  } else {
3541  $this->error = $this->db->error();
3542  return -1;
3543  }
3544  }
3545 
3546  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3557  public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null, $warehouseid = 0)
3558  {
3559  // phpcs:enable
3560  global $user, $hookmanager, $action;
3561 
3562  $serviceStockIsEnabled = isModEnabled("service") && getDolGlobalString('STOCK_SUPPORTS_SERVICES');
3563 
3564  $sql = "SELECT COUNT(DISTINCT m.fk_soc) as nb_customers, COUNT(DISTINCT m.rowid) as nb,";
3565  $sql .= " COUNT(mp.rowid) as nb_rows, SUM(mp.qty) as qty, role";
3566  $sql .= " FROM ".$this->db->prefix()."mrp_production as mp";
3567  $sql .= ", ".$this->db->prefix()."mrp_mo as m";
3568  $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON s.rowid = m.fk_soc";
3569  if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3570  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3571  }
3572  $sql .= " WHERE m.rowid = mp.fk_mo";
3573  $sql .= " AND m.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'mrp').")";
3574  $sql .= " AND mp.fk_product = ".((int) $this->id);
3575  $sql .= " AND mp.disable_stock_change IN (0)";
3576  if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3577  $sql .= " AND m.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3578  }
3579  if ($socid > 0) {
3580  $sql .= " AND m.fk_soc = ".((int) $socid);
3581  }
3582  if ($filtrestatut != '') {
3583  $sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
3584  }
3585  if (!empty($dateofvirtualstock)) {
3586  $sql .= " AND m.date_valid <= '".$this->db->idate($dateofvirtualstock)."'"; // better date to code ? end of production ?
3587  }
3588  if (!$serviceStockIsEnabled) {
3589  $sql .= "AND EXISTS (SELECT p.rowid FROM ".$this->db->prefix()."product AS p WHERE p.rowid = ".((int) $this->id)." AND p.fk_product_type IN (0))";
3590  }
3591  if (!empty($warehouseid)) {
3592  $sql .= " AND m.fk_warehouse = ".((int) $warehouseid);
3593  }
3594  $sql .= " GROUP BY role";
3595 
3596  if ($warehouseid) {
3597  $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
3598  } else {
3599  $this->stats_mrptoconsume['customers'] = 0;
3600  $this->stats_mrptoconsume['nb'] = 0;
3601  $this->stats_mrptoconsume['rows'] = 0;
3602  $this->stats_mrptoconsume['qty'] = 0;
3603  $this->stats_mrptoproduce['customers'] = 0;
3604  $this->stats_mrptoproduce['nb'] = 0;
3605  $this->stats_mrptoproduce['rows'] = 0;
3606  $this->stats_mrptoproduce['qty'] = 0;
3607  }
3608 
3609  $result = $this->db->query($sql);
3610  if ($result) {
3611  while ($obj = $this->db->fetch_object($result)) {
3612  if ($obj->role == 'toconsume' && empty($warehouseid)) {
3613  $this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3614  $this->stats_mrptoconsume['nb'] += $obj->nb;
3615  $this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3616  $this->stats_mrptoconsume['qty'] += ($obj->qty ? $obj->qty : 0);
3617  }
3618  if ($obj->role == 'consumed' && empty($warehouseid)) {
3619  //$this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3620  //$this->stats_mrptoconsume['nb'] += $obj->nb;
3621  //$this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3622  $this->stats_mrptoconsume['qty'] -= ($obj->qty ? $obj->qty : 0);
3623  }
3624  if ($obj->role == 'toproduce') {
3625  if ($warehouseid) {
3626  $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] += ($obj->qty ? $obj->qty : 0);
3627  } else {
3628  $this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3629  $this->stats_mrptoproduce['nb'] += $obj->nb;
3630  $this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3631  $this->stats_mrptoproduce['qty'] += ($obj->qty ? $obj->qty : 0);
3632  }
3633  }
3634  if ($obj->role == 'produced') {
3635  //$this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3636  //$this->stats_mrptoproduce['nb'] += $obj->nb;
3637  //$this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3638  if ($warehouseid) {
3639  $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3640  } else {
3641  $this->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3642  }
3643  }
3644  }
3645 
3646  // Clean data
3647  if ($warehouseid) {
3648  if ($this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] < 0) {
3649  $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
3650  }
3651  } else {
3652  if ($this->stats_mrptoconsume['qty'] < 0) {
3653  $this->stats_mrptoconsume['qty'] = 0;
3654  }
3655  if ($this->stats_mrptoproduce['qty'] < 0) {
3656  $this->stats_mrptoproduce['qty'] = 0;
3657  }
3658  }
3659 
3660  $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3661  $reshook = $hookmanager->executeHooks('loadStatsInProduction', $parameters, $this, $action);
3662  if ($reshook > 0) {
3663  $this->stats_mrptoproduce = $hookmanager->resArray['stats_mrptoproduce'];
3664  }
3665 
3666  return 1;
3667  } else {
3668  $this->error = $this->db->error();
3669  return -1;
3670  }
3671  }
3672 
3673  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3680  public function load_stats_contrat($socid = 0)
3681  {
3682  // phpcs:enable
3683  global $conf, $user, $hookmanager, $action;
3684 
3685  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3686  $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3687  $sql .= " FROM ".$this->db->prefix()."contratdet as cd";
3688  $sql .= ", ".$this->db->prefix()."contrat as c";
3689  $sql .= ", ".$this->db->prefix()."societe as s";
3690  if (!$user->hasRight('societe', 'client', 'voir')) {
3691  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3692  }
3693  $sql .= " WHERE c.rowid = cd.fk_contrat";
3694  $sql .= " AND c.fk_soc = s.rowid";
3695  $sql .= " AND c.entity IN (".getEntity('contract').")";
3696  $sql .= " AND cd.fk_product = ".((int) $this->id);
3697  if (!$user->hasRight('societe', 'client', 'voir')) {
3698  $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3699  }
3700  //$sql.= " AND c.statut != 0";
3701  if ($socid > 0) {
3702  $sql .= " AND c.fk_soc = ".((int) $socid);
3703  }
3704 
3705  $result = $this->db->query($sql);
3706  if ($result) {
3707  $obj = $this->db->fetch_object($result);
3708  $this->stats_contrat['customers'] = $obj->nb_customers;
3709  $this->stats_contrat['nb'] = $obj->nb;
3710  $this->stats_contrat['rows'] = $obj->nb_rows;
3711  $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
3712 
3713  // if it's a virtual product, maybe it is in contract by extension
3714  if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3715  $TFather = $this->getFather();
3716  if (is_array($TFather) && !empty($TFather)) {
3717  foreach ($TFather as &$fatherData) {
3718  $pFather = new Product($this->db);
3719  $pFather->id = $fatherData['id'];
3720  $qtyCoef = $fatherData['qty'];
3721 
3722  if ($fatherData['incdec']) {
3723  $pFather->load_stats_contrat($socid);
3724 
3725  $this->stats_contrat['customers'] += $pFather->stats_contrat['customers'];
3726  $this->stats_contrat['nb'] += $pFather->stats_contrat['nb'];
3727  $this->stats_contrat['rows'] += $pFather->stats_contrat['rows'];
3728  $this->stats_contrat['qty'] += $pFather->stats_contrat['qty'] * $qtyCoef;
3729  }
3730  }
3731  }
3732  }
3733 
3734  $parameters = array('socid' => $socid);
3735  $reshook = $hookmanager->executeHooks('loadStatsContract', $parameters, $this, $action);
3736  if ($reshook > 0) {
3737  $this->stats_contrat = $hookmanager->resArray['stats_contrat'];
3738  }
3739 
3740  return 1;
3741  } else {
3742  $this->error = $this->db->error().' sql='.$sql;
3743  return -1;
3744  }
3745  }
3746 
3747  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3754  public function load_stats_facture($socid = 0)
3755  {
3756  // phpcs:enable
3757  global $conf, $user, $hookmanager, $action;
3758 
3759  $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3760  $sql .= " COUNT(fd.rowid) as nb_rows, SUM(".$this->db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
3761  $sql .= " FROM ".$this->db->prefix()."facturedet as fd";
3762  $sql .= ", ".$this->db->prefix()."facture as f";
3763  $sql .= ", ".$this->db->prefix()."societe as s";
3764  if (!$user->hasRight('societe', 'client', 'voir')) {
3765  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3766  }
3767  $sql .= " WHERE f.rowid = fd.fk_facture";
3768  $sql .= " AND f.fk_soc = s.rowid";
3769  $sql .= " AND f.entity IN (".getEntity('invoice').")";
3770  $sql .= " AND fd.fk_product = ".((int) $this->id);
3771  if (!$user->hasRight('societe', 'client', 'voir')) {
3772  $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3773  }
3774  //$sql.= " AND f.fk_statut != 0";
3775  if ($socid > 0) {
3776  $sql .= " AND f.fk_soc = ".((int) $socid);
3777  }
3778 
3779  $result = $this->db->query($sql);
3780  if ($result) {
3781  $obj = $this->db->fetch_object($result);
3782  $this->stats_facture['customers'] = $obj->nb_customers;
3783  $this->stats_facture['nb'] = $obj->nb;
3784  $this->stats_facture['rows'] = $obj->nb_rows;
3785  $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
3786 
3787  // if it's a virtual product, maybe it is in invoice by extension
3788  if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3789  $TFather = $this->getFather();
3790  if (is_array($TFather) && !empty($TFather)) {
3791  foreach ($TFather as &$fatherData) {
3792  $pFather = new Product($this->db);
3793  $pFather->id = $fatherData['id'];
3794  $qtyCoef = $fatherData['qty'];
3795 
3796  if ($fatherData['incdec']) {
3797  $pFather->load_stats_facture($socid);
3798 
3799  $this->stats_facture['customers'] += $pFather->stats_facture['customers'];
3800  $this->stats_facture['nb'] += $pFather->stats_facture['nb'];
3801  $this->stats_facture['rows'] += $pFather->stats_facture['rows'];
3802  $this->stats_facture['qty'] += $pFather->stats_facture['qty'] * $qtyCoef;
3803  }
3804  }
3805  }
3806  }
3807 
3808  $parameters = array('socid' => $socid);
3809  $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoice', $parameters, $this, $action);
3810  if ($reshook > 0) {
3811  $this->stats_facture = $hookmanager->resArray['stats_facture'];
3812  }
3813 
3814  return 1;
3815  } else {
3816  $this->error = $this->db->error();
3817  return -1;
3818  }
3819  }
3820 
3821 
3822  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3829  public function load_stats_facturerec($socid = 0)
3830  {
3831  // phpcs:enable
3832  global $conf, $user, $hookmanager, $action;
3833 
3834  $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3835  $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3836  $sql .= " FROM ".MAIN_DB_PREFIX."facturedet_rec as fd";
3837  $sql .= ", ".MAIN_DB_PREFIX."facture_rec as f";
3838  $sql .= ", ".MAIN_DB_PREFIX."societe as s";
3839  if (!$user->hasRight('societe', 'client', 'voir')) {
3840  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3841  }
3842  $sql .= " WHERE f.rowid = fd.fk_facture";
3843  $sql .= " AND f.fk_soc = s.rowid";
3844  $sql .= " AND f.entity IN (".getEntity('invoice').")";
3845  $sql .= " AND fd.fk_product = ".((int) $this->id);
3846  if (!$user->hasRight('societe', 'client', 'voir')) {
3847  $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3848  }
3849  //$sql.= " AND f.fk_statut != 0";
3850  if ($socid > 0) {
3851  $sql .= " AND f.fk_soc = ".((int) $socid);
3852  }
3853 
3854  $result = $this->db->query($sql);
3855  if ($result) {
3856  $obj = $this->db->fetch_object($result);
3857  $this->stats_facturerec['customers'] = $obj->nb_customers;
3858  $this->stats_facturerec['nb'] = $obj->nb;
3859  $this->stats_facturerec['rows'] = $obj->nb_rows;
3860  $this->stats_facturerec['qty'] = $obj->qty ? $obj->qty : 0;
3861 
3862  // if it's a virtual product, maybe it is in invoice by extension
3863  if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3864  $TFather = $this->getFather();
3865  if (is_array($TFather) && !empty($TFather)) {
3866  foreach ($TFather as &$fatherData) {
3867  $pFather = new Product($this->db);
3868  $pFather->id = $fatherData['id'];
3869  $qtyCoef = $fatherData['qty'];
3870 
3871  if ($fatherData['incdec']) {
3872  $pFather->load_stats_facture($socid);
3873 
3874  $this->stats_facturerec['customers'] += $pFather->stats_facturerec['customers'];
3875  $this->stats_facturerec['nb'] += $pFather->stats_facturerec['nb'];
3876  $this->stats_facturerec['rows'] += $pFather->stats_facturerec['rows'];
3877  $this->stats_facturerec['qty'] += $pFather->stats_facturerec['qty'] * $qtyCoef;
3878  }
3879  }
3880  }
3881  }
3882 
3883  $parameters = array('socid' => $socid);
3884  $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoiceRec', $parameters, $this, $action);
3885  if ($reshook > 0) {
3886  $this->stats_facturerec = $hookmanager->resArray['stats_facturerec'];
3887  }
3888 
3889  return 1;
3890  } else {
3891  $this->error = $this->db->error();
3892  return -1;
3893  }
3894  }
3895 
3896  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3903  public function load_stats_facture_fournisseur($socid = 0)
3904  {
3905  // phpcs:enable
3906  global $conf, $user, $hookmanager, $action;
3907 
3908  $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
3909  $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3910  $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as fd";
3911  $sql .= ", ".$this->db->prefix()."facture_fourn as f";
3912  $sql .= ", ".$this->db->prefix()."societe as s";
3913  if (!$user->hasRight('societe', 'client', 'voir')) {
3914  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3915  }
3916  $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
3917  $sql .= " AND f.fk_soc = s.rowid";
3918  $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
3919  $sql .= " AND fd.fk_product = ".((int) $this->id);
3920  if (!$user->hasRight('societe', 'client', 'voir')) {
3921  $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3922  }
3923  //$sql.= " AND f.fk_statut != 0";
3924  if ($socid > 0) {
3925  $sql .= " AND f.fk_soc = ".((int) $socid);
3926  }
3927 
3928  $result = $this->db->query($sql);
3929  if ($result) {
3930  $obj = $this->db->fetch_object($result);
3931  $this->stats_facture_fournisseur['suppliers'] = $obj->nb_suppliers;
3932  $this->stats_facture_fournisseur['nb'] = $obj->nb;
3933  $this->stats_facture_fournisseur['rows'] = $obj->nb_rows;
3934  $this->stats_facture_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3935 
3936  $parameters = array('socid' => $socid);
3937  $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoice', $parameters, $this, $action);
3938  if ($reshook > 0) {
3939  $this->stats_facture_fournisseur = $hookmanager->resArray['stats_facture_fournisseur'];
3940  }
3941 
3942  return 1;
3943  } else {
3944  $this->error = $this->db->error();
3945  return -1;
3946  }
3947  }
3948 
3949  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3958  private function _get_stats($sql, $mode, $year = 0)
3959  {
3960  // phpcs:enable
3961  $tab = array();
3962 
3963  $resql = $this->db->query($sql);
3964  if ($resql) {
3965  $num = $this->db->num_rows($resql);
3966  $i = 0;
3967  while ($i < $num) {
3968  $arr = $this->db->fetch_array($resql);
3969  if (is_array($arr)) {
3970  $keyfortab = (string) $arr[1];
3971  if ($year == -1) {
3972  $keyfortab = substr($keyfortab, -2);
3973  }
3974 
3975  if ($mode == 'byunit') {
3976  $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[0]; // 1st field
3977  } elseif ($mode == 'bynumber') {
3978  $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3979  } elseif ($mode == 'byamount') {
3980  $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3981  } else {
3982  // Bad value for $mode
3983  return -1;
3984  }
3985  }
3986  $i++;
3987  }
3988  } else {
3989  $this->error = $this->db->error().' sql='.$sql;
3990  return -1;
3991  }
3992 
3993  if (empty($year)) {
3994  $year = dol_print_date(time(), '%Y');
3995  $month = dol_print_date(time(), '%m');
3996  } elseif ($year == -1) {
3997  $year = '';
3998  $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3999  } else {
4000  $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
4001  }
4002 
4003  $result = array();
4004 
4005  for ($j = 0; $j < 12; $j++) {
4006  // $ids is 'D', 'N', 'O', 'S', ... (First letter of month in user language)
4007  $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, 1970), "%b"), 1, 'right', 'UTF-8', 1));
4008 
4009  //print $idx.'-'.$year.'-'.$month.'<br>';
4010  $result[$j] = array($idx, isset($tab[$year.$month]) ? $tab[$year.$month] : 0);
4011  // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
4012 
4013  $month = "0".($month - 1);
4014  if (dol_strlen($month) == 3) {
4015  $month = substr($month, 1);
4016  }
4017  if ($month == 0) {
4018  $month = 12;
4019  $year = $year - 1;
4020  }
4021  }
4022 
4023  return array_reverse($result);
4024  }
4025 
4026 
4027  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4038  public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4039  {
4040  // phpcs:enable
4041  global $conf;
4042  global $user;
4043 
4044  $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4045  if ($mode == 'bynumber') {
4046  $sql .= ", count(DISTINCT f.rowid)";
4047  }
4048  $sql .= ", sum(d.total_ht) as total_ht";
4049  $sql .= " FROM ".$this->db->prefix()."facturedet as d, ".$this->db->prefix()."facture as f, ".$this->db->prefix()."societe as s";
4050  if ($filteronproducttype >= 0) {
4051  $sql .= ", ".$this->db->prefix()."product as p";
4052  }
4053  if (!$user->hasRight('societe', 'client', 'voir')) {
4054  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4055  }
4056  $sql .= " WHERE f.rowid = d.fk_facture";
4057  if ($this->id > 0) {
4058  $sql .= " AND d.fk_product = ".((int) $this->id);
4059  } else {
4060  $sql .= " AND d.fk_product > 0";
4061  }
4062  if ($filteronproducttype >= 0) {
4063  $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4064  }
4065  $sql .= " AND f.fk_soc = s.rowid";
4066  $sql .= " AND f.entity IN (".getEntity('invoice').")";
4067  if (!$user->hasRight('societe', 'client', 'voir')) {
4068  $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4069  }
4070  if ($socid > 0) {
4071  $sql .= " AND f.fk_soc = $socid";
4072  }
4073  $sql .= $morefilter;
4074  $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4075  $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4076 
4077  return $this->_get_stats($sql, $mode, $year);
4078  }
4079 
4080 
4081  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4092  public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4093  {
4094  // phpcs:enable
4095  global $conf;
4096  global $user;
4097 
4098  $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4099  if ($mode == 'bynumber') {
4100  $sql .= ", count(DISTINCT f.rowid)";
4101  }
4102  $sql .= ", sum(d.total_ht) as total_ht";
4103  $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as d, ".$this->db->prefix()."facture_fourn as f, ".$this->db->prefix()."societe as s";
4104  if ($filteronproducttype >= 0) {
4105  $sql .= ", ".$this->db->prefix()."product as p";
4106  }
4107  if (!$user->hasRight('societe', 'client', 'voir')) {
4108  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4109  }
4110  $sql .= " WHERE f.rowid = d.fk_facture_fourn";
4111  if ($this->id > 0) {
4112  $sql .= " AND d.fk_product = ".((int) $this->id);
4113  } else {
4114  $sql .= " AND d.fk_product > 0";
4115  }
4116  if ($filteronproducttype >= 0) {
4117  $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4118  }
4119  $sql .= " AND f.fk_soc = s.rowid";
4120  $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4121  if (!$user->hasRight('societe', 'client', 'voir')) {
4122  $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4123  }
4124  if ($socid > 0) {
4125  $sql .= " AND f.fk_soc = $socid";
4126  }
4127  $sql .= $morefilter;
4128  $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4129  $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4130 
4131  return $this->_get_stats($sql, $mode, $year);
4132  }
4133 
4134  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4145  public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4146  {
4147  // phpcs:enable
4148  global $conf, $user;
4149 
4150  $sql = "SELECT sum(d.qty) as qty, date_format(p.datep, '%Y%m')";
4151  if ($mode == 'bynumber') {
4152  $sql .= ", count(DISTINCT p.rowid)";
4153  }
4154  $sql .= ", sum(d.total_ht) as total_ht";
4155  $sql .= " FROM ".$this->db->prefix()."propaldet as d, ".$this->db->prefix()."propal as p, ".$this->db->prefix()."societe as s";
4156  if ($filteronproducttype >= 0) {
4157  $sql .= ", ".$this->db->prefix()."product as prod";
4158  }
4159  if (!$user->hasRight('societe', 'client', 'voir')) {
4160  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4161  }
4162  $sql .= " WHERE p.rowid = d.fk_propal";
4163  if ($this->id > 0) {
4164  $sql .= " AND d.fk_product = ".((int) $this->id);
4165  } else {
4166  $sql .= " AND d.fk_product > 0";
4167  }
4168  if ($filteronproducttype >= 0) {
4169  $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4170  }
4171  $sql .= " AND p.fk_soc = s.rowid";
4172  $sql .= " AND p.entity IN (".getEntity('propal').")";
4173  if (!$user->hasRight('societe', 'client', 'voir')) {
4174  $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4175  }
4176  if ($socid > 0) {
4177  $sql .= " AND p.fk_soc = ".((int) $socid);
4178  }
4179  $sql .= $morefilter;
4180  $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
4181  $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
4182 
4183  return $this->_get_stats($sql, $mode, $year);
4184  }
4185 
4186  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4197  public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4198  {
4199  // phpcs:enable
4200  global $conf;
4201  global $user;
4202 
4203  $sql = "SELECT sum(d.qty) as qty, date_format(p.date_valid, '%Y%m')";
4204  if ($mode == 'bynumber') {
4205  $sql .= ", count(DISTINCT p.rowid)";
4206  }
4207  $sql .= ", sum(d.total_ht) as total_ht";
4208  $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as d, ".$this->db->prefix()."supplier_proposal as p, ".$this->db->prefix()."societe as s";
4209  if ($filteronproducttype >= 0) {
4210  $sql .= ", ".$this->db->prefix()."product as prod";
4211  }
4212  if (!$user->hasRight('societe', 'client', 'voir')) {
4213  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4214  }
4215  $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
4216  if ($this->id > 0) {
4217  $sql .= " AND d.fk_product = ".((int) $this->id);
4218  } else {
4219  $sql .= " AND d.fk_product > 0";
4220  }
4221  if ($filteronproducttype >= 0) {
4222  $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4223  }
4224  $sql .= " AND p.fk_soc = s.rowid";
4225  $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
4226  if (!$user->hasRight('societe', 'client', 'voir')) {
4227  $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4228  }
4229  if ($socid > 0) {
4230  $sql .= " AND p.fk_soc = ".((int) $socid);
4231  }
4232  $sql .= $morefilter;
4233  $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
4234  $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
4235 
4236  return $this->_get_stats($sql, $mode, $year);
4237  }
4238 
4239  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4250  public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4251  {
4252  // phpcs:enable
4253  global $conf, $user;
4254 
4255  $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4256  if ($mode == 'bynumber') {
4257  $sql .= ", count(DISTINCT c.rowid)";
4258  }
4259  $sql .= ", sum(d.total_ht) as total_ht";
4260  $sql .= " FROM ".$this->db->prefix()."commandedet as d, ".$this->db->prefix()."commande as c, ".$this->db->prefix()."societe as s";
4261  if ($filteronproducttype >= 0) {
4262  $sql .= ", ".$this->db->prefix()."product as p";
4263  }
4264  if (!$user->hasRight('societe', 'client', 'voir')) {
4265  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4266  }
4267  $sql .= " WHERE c.rowid = d.fk_commande";
4268  if ($this->id > 0) {
4269  $sql .= " AND d.fk_product = ".((int) $this->id);
4270  } else {
4271  $sql .= " AND d.fk_product > 0";
4272  }
4273  if ($filteronproducttype >= 0) {
4274  $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4275  }
4276  $sql .= " AND c.fk_soc = s.rowid";
4277  $sql .= " AND c.entity IN (".getEntity('commande').")";
4278  if (!$user->hasRight('societe', 'client', 'voir')) {
4279  $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4280  }
4281  if ($socid > 0) {
4282  $sql .= " AND c.fk_soc = ".((int) $socid);
4283  }
4284  $sql .= $morefilter;
4285  $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4286  $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4287 
4288  return $this->_get_stats($sql, $mode, $year);
4289  }
4290 
4291  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4302  public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4303  {
4304  // phpcs:enable
4305  global $conf, $user;
4306 
4307  $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4308  if ($mode == 'bynumber') {
4309  $sql .= ", count(DISTINCT c.rowid)";
4310  }
4311  $sql .= ", sum(d.total_ht) as total_ht";
4312  $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as d, ".$this->db->prefix()."commande_fournisseur as c, ".$this->db->prefix()."societe as s";
4313  if ($filteronproducttype >= 0) {
4314  $sql .= ", ".$this->db->prefix()."product as p";
4315  }
4316  if (!$user->hasRight('societe', 'client', 'voir')) {
4317  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4318  }
4319  $sql .= " WHERE c.rowid = d.fk_commande";
4320  if ($this->id > 0) {
4321  $sql .= " AND d.fk_product = ".((int) $this->id);
4322  } else {
4323  $sql .= " AND d.fk_product > 0";
4324  }
4325  if ($filteronproducttype >= 0) {
4326  $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4327  }
4328  $sql .= " AND c.fk_soc = s.rowid";
4329  $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
4330  if (!$user->hasRight('societe', 'client', 'voir')) {
4331  $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4332  }
4333  if ($socid > 0) {
4334  $sql .= " AND c.fk_soc = ".((int) $socid);
4335  }
4336  $sql .= $morefilter;
4337  $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4338  $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4339 
4340  return $this->_get_stats($sql, $mode, $year);
4341  }
4342 
4343  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4354  public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4355  {
4356  // phpcs:enable
4357  global $conf, $user;
4358 
4359  $sql = "SELECT sum(d.qty) as qty, date_format(c.date_contrat, '%Y%m')";
4360  if ($mode == 'bynumber') {
4361  $sql .= ", count(DISTINCT c.rowid)";
4362  }
4363  $sql .= ", sum(d.total_ht) as total_ht";
4364  $sql .= " FROM ".$this->db->prefix()."contratdet as d, ".$this->db->prefix()."contrat as c, ".$this->db->prefix()."societe as s";
4365  if ($filteronproducttype >= 0) {
4366  $sql .= ", ".$this->db->prefix()."product as p";
4367  }
4368  if (!$user->hasRight('societe', 'client', 'voir')) {
4369  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4370  }
4371  $sql .= " WHERE c.entity IN (".getEntity('contract').")";
4372  $sql .= " AND c.rowid = d.fk_contrat";
4373 
4374  if ($this->id > 0) {
4375  $sql .= " AND d.fk_product = ".((int) $this->id);
4376  } else {
4377  $sql .= " AND d.fk_product > 0";
4378  }
4379  if ($filteronproducttype >= 0) {
4380  $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4381  }
4382  $sql .= " AND c.fk_soc = s.rowid";
4383 
4384  if (!$user->hasRight('societe', 'client', 'voir')) {
4385  $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4386  }
4387  if ($socid > 0) {
4388  $sql .= " AND c.fk_soc = ".((int) $socid);
4389  }
4390  $sql .= $morefilter;
4391  $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
4392  $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
4393 
4394  return $this->_get_stats($sql, $mode, $year);
4395  }
4396 
4397  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4408  public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4409  {
4410  // phpcs:enable
4411  global $conf, $user;
4412 
4413  $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
4414  if ($mode == 'bynumber') {
4415  $sql .= ", count(DISTINCT d.rowid)";
4416  }
4417  $sql .= " FROM ".$this->db->prefix()."mrp_mo as d LEFT JOIN ".$this->db->prefix()."societe as s ON d.fk_soc = s.rowid";
4418  if ($filteronproducttype >= 0) {
4419  $sql .= ", ".$this->db->prefix()."product as p";
4420  }
4421  if (!$user->hasRight('societe', 'client', 'voir')) {
4422  $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4423  }
4424 
4425  $sql .= " WHERE d.entity IN (".getEntity('mo').")";
4426  $sql .= " AND d.status > 0";
4427 
4428  if ($this->id > 0) {
4429  $sql .= " AND d.fk_product = ".((int) $this->id);
4430  } else {
4431  $sql .= " AND d.fk_product > 0";
4432  }
4433  if ($filteronproducttype >= 0) {
4434  $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4435  }
4436 
4437  if (!$user->hasRight('societe', 'client', 'voir')) {
4438  $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4439  }
4440  if ($socid > 0) {
4441  $sql .= " AND d.fk_soc = ".((int) $socid);
4442  }
4443  $sql .= $morefilter;
4444  $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
4445  $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
4446 
4447  return $this->_get_stats($sql, $mode, $year);
4448  }
4449 
4450  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4461  public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4462  {
4463  global $user;
4464 
4465  // phpcs:enable
4466  // Clean parameters
4467  if (!is_numeric($id_pere)) {
4468  $id_pere = 0;
4469  }
4470  if (!is_numeric($id_fils)) {
4471  $id_fils = 0;
4472  }
4473  if (!is_numeric($incdec)) {
4474  $incdec = 0;
4475  }
4476 
4477  $result = $this->del_sousproduit($id_pere, $id_fils);
4478  if ($result < 0) {
4479  return $result;
4480  }
4481 
4482  // Check not already father of id_pere (to avoid father -> child -> father links)
4483  $sql = "SELECT fk_product_pere from ".$this->db->prefix()."product_association";
4484  $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere);
4485  if (!$this->db->query($sql)) {
4486  dol_print_error($this->db);
4487  return -1;
4488  } else {
4489  //Selection of the highest row
4490  $sql = "SELECT MAX(rang) as max_rank FROM ".$this->db->prefix()."product_association";
4491  $sql .= " WHERE fk_product_pere = ".((int) $id_pere);
4492  $resql = $this->db->query($sql);
4493  if ($resql) {
4494  $obj = $this->db->fetch_object($resql);
4495  $rank = $obj->max_rank + 1;
4496  //Addition of a product with the highest rank +1
4497  $sql = "INSERT INTO ".$this->db->prefix()."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)";
4498  $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".price2num($qty, 'MS').", ".price2num($incdec, 'MS').", ".((int) $rank).")";
4499  if (! $this->db->query($sql)) {
4500  dol_print_error($this->db);
4501  return -1;
4502  } else {
4503  if (!$notrigger) {
4504  // Call trigger
4505  $result = $this->call_trigger('PRODUCT_SUBPRODUCT_ADD', $user);
4506  if ($result < 0) {
4507  $this->error = $this->db->lasterror();
4508  dol_syslog(get_class($this).'::addSubproduct error='.$this->error, LOG_ERR);
4509  return -1;
4510  }
4511  }
4512  // End call triggers
4513 
4514  return 1;
4515  }
4516  } else {
4517  dol_print_error($this->db);
4518  return -1;
4519  }
4520  }
4521  }
4522 
4523  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4534  public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4535  {
4536  global $user;
4537 
4538  // phpcs:enable
4539  // Clean parameters
4540  if (!is_numeric($id_pere)) {
4541  $id_pere = 0;
4542  }
4543  if (!is_numeric($id_fils)) {
4544  $id_fils = 0;
4545  }
4546  if (!is_numeric($incdec)) {
4547  $incdec = 1;
4548  }
4549  if (!is_numeric($qty)) {
4550  $qty = 1;
4551  }
4552 
4553  $sql = 'UPDATE '.$this->db->prefix().'product_association SET ';
4554  $sql .= 'qty = '.price2num($qty, 'MS');
4555  $sql .= ',incdec = '.price2num($incdec, 'MS');
4556  $sql .= ' WHERE fk_product_pere = '.((int) $id_pere).' AND fk_product_fils = '.((int) $id_fils);
4557 
4558  if (!$this->db->query($sql)) {
4559  dol_print_error($this->db);
4560  return -1;
4561  } else {
4562  if (!$notrigger) {
4563  // Call trigger
4564  $result = $this->call_trigger('PRODUCT_SUBPRODUCT_UPDATE', $user);
4565  if ($result < 0) {
4566  $this->error = $this->db->lasterror();
4567  dol_syslog(get_class($this).'::updateSubproduct error='.$this->error, LOG_ERR);
4568  return -1;
4569  }
4570  // End call triggers
4571  }
4572 
4573  return 1;
4574  }
4575  }
4576 
4577  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4586  public function del_sousproduit($fk_parent, $fk_child, $notrigger = 0)
4587  {
4588  global $user;
4589 
4590  // phpcs:enable
4591  if (!is_numeric($fk_parent)) {
4592  $fk_parent = 0;
4593  }
4594  if (!is_numeric($fk_child)) {
4595  $fk_child = 0;
4596  }
4597 
4598  $sql = "DELETE FROM ".$this->db->prefix()."product_association";
4599  $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4600  $sql .= " AND fk_product_fils = ".((int) $fk_child);
4601 
4602  dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
4603  if (!$this->db->query($sql)) {
4604  dol_print_error($this->db);
4605  return -1;
4606  }
4607 
4608  // Updated ranks so that none are missing
4609  $sqlrank = "SELECT rowid, rang FROM ".$this->db->prefix()."product_association";
4610  $sqlrank .= " WHERE fk_product_pere = ".((int) $fk_parent);
4611  $sqlrank .= " ORDER BY rang";
4612  $resqlrank = $this->db->query($sqlrank);
4613  if ($resqlrank) {
4614  $cpt = 0;
4615  while ($objrank = $this->db->fetch_object($resqlrank)) {
4616  $cpt++;
4617  $sql = "UPDATE ".$this->db->prefix()."product_association";
4618  $sql .= " SET rang = ".((int) $cpt);
4619  $sql .= " WHERE rowid = ".((int) $objrank->rowid);
4620  if (! $this->db->query($sql)) {
4621  dol_print_error($this->db);
4622  return -1;
4623  }
4624  }
4625  }
4626 
4627  if (!$notrigger) {
4628  // Call trigger
4629  $result = $this->call_trigger('PRODUCT_SUBPRODUCT_DELETE', $user);
4630  if ($result < 0) {
4631  $this->error = $this->db->lasterror();
4632  dol_syslog(get_class($this).'::delSubproduct error='.$this->error, LOG_ERR);
4633  return -1;
4634  }
4635  // End call triggers
4636  }
4637 
4638  return 1;
4639  }
4640 
4641  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4649  public function is_sousproduit($fk_parent, $fk_child)
4650  {
4651  // phpcs:enable
4652  $sql = "SELECT fk_product_pere, qty, incdec";
4653  $sql .= " FROM ".$this->db->prefix()."product_association";
4654  $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4655  $sql .= " AND fk_product_fils = ".((int) $fk_child);
4656 
4657  $result = $this->db->query($sql);
4658  if ($result) {
4659  $num = $this->db->num_rows($result);
4660 
4661  if ($num > 0) {
4662  $obj = $this->db->fetch_object($result);
4663 
4664  $this->is_sousproduit_qty = $obj->qty;
4665  $this->is_sousproduit_incdec = $obj->incdec;
4666 
4667  return 1;
4668  } else {
4669  return 0;
4670  }
4671  } else {
4672  dol_print_error($this->db);
4673  return -1;
4674  }
4675  }
4676 
4677 
4678  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4689  public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
4690  {
4691  // phpcs:enable
4692  global $conf;
4693 
4694  $now = dol_now();
4695 
4696  dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
4697 
4698  // Clean parameters
4699  $quantity = price2num($quantity, 'MS');
4700 
4701  if ($ref_fourn) {
4702  // Check if ref is not already used
4703  $sql = "SELECT rowid, fk_product";
4704  $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4705  $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4706  $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4707  $sql .= " AND fk_product <> ".((int) $this->id);
4708  $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4709 
4710  $resql = $this->db->query($sql);
4711  if ($resql) {
4712  $obj = $this->db->fetch_object($resql);
4713  if ($obj) {
4714  // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
4715  $this->product_id_already_linked = $obj->fk_product;
4716  return -3;
4717  }
4718  $this->db->free($resql);
4719  }
4720  }
4721 
4722  $sql = "SELECT rowid";
4723  $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4724  $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4725  if ($ref_fourn) {
4726  $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4727  } else {
4728  $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
4729  }
4730  $sql .= " AND quantity = ".((float) $quantity);
4731  $sql .= " AND fk_product = ".((int) $this->id);
4732  $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4733 
4734  $resql = $this->db->query($sql);
4735  if ($resql) {
4736  $obj = $this->db->fetch_object($resql);
4737 
4738  // The reference supplier does not exist, we create it for this product.
4739  if (empty($obj)) {
4740  $sql = "INSERT INTO ".$this->db->prefix()."product_fournisseur_price(";
4741  $sql .= "datec";
4742  $sql .= ", entity";
4743  $sql .= ", fk_product";
4744  $sql .= ", fk_soc";
4745  $sql .= ", ref_fourn";
4746  $sql .= ", quantity";
4747  $sql .= ", fk_user";
4748  $sql .= ", tva_tx";
4749  $sql .= ") VALUES (";
4750  $sql .= "'".$this->db->idate($now)."'";
4751  $sql .= ", ".((int) $conf->entity);
4752  $sql .= ", ".((int) $this->id);
4753  $sql .= ", ".((int) $id_fourn);
4754  $sql .= ", '".$this->db->escape($ref_fourn)."'";
4755  $sql .= ", ".((float) $quantity);
4756  $sql .= ", ".((int) $user->id);
4757  $sql .= ", 0";
4758  $sql .= ")";
4759 
4760  if ($this->db->query($sql)) {
4761  $this->product_fourn_price_id = $this->db->last_insert_id($this->db->prefix()."product_fournisseur_price");
4762  return 1;
4763  } else {
4764  $this->error = $this->db->lasterror();
4765  return -1;
4766  }
4767  } else {
4768  // If the supplier price already exists for this product and quantity
4769  $this->product_fourn_price_id = $obj->rowid;
4770  return 0;
4771  }
4772  } else {
4773  $this->error = $this->db->lasterror();
4774  return -2;
4775  }
4776  }
4777 
4778 
4779  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4785  public function list_suppliers()
4786  {
4787  // phpcs:enable
4788  global $conf;
4789 
4790  $list = array();
4791 
4792  $sql = "SELECT DISTINCT p.fk_soc";
4793  $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as p";
4794  $sql .= " WHERE p.fk_product = ".((int) $this->id);
4795  $sql .= " AND p.entity = ".((int) $conf->entity);
4796 
4797  $result = $this->db->query($sql);
4798  if ($result) {
4799  $num = $this->db->num_rows($result);
4800  $i = 0;
4801  while ($i < $num) {
4802  $obj = $this->db->fetch_object($result);
4803  $list[$i] = $obj->fk_soc;
4804  $i++;
4805  }
4806  }
4807 
4808  return $list;
4809  }
4810 
4811  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4819  public function clone_price($fromId, $toId)
4820  {
4821  global $conf, $user;
4822 
4823  $now = dol_now();
4824 
4825  $this->db->begin();
4826 
4827  // prices
4828  $sql = "INSERT INTO ".$this->db->prefix()."product_price (";
4829  $sql .= " entity";
4830  $sql .= ", fk_product";
4831  $sql .= ", date_price";
4832  $sql .= ", price_level";
4833  $sql .= ", price";
4834  $sql .= ", price_ttc";
4835  $sql .= ", price_min";
4836  $sql .= ", price_min_ttc";
4837  $sql .= ", price_base_type";
4838  $sql .= ", price_label";
4839  $sql .= ", default_vat_code";
4840  $sql .= ", tva_tx";
4841  $sql .= ", recuperableonly";
4842  $sql .= ", localtax1_tx";
4843  $sql .= ", localtax1_type";
4844  $sql .= ", localtax2_tx";
4845  $sql .= ", localtax2_type";
4846  $sql .= ", fk_user_author";
4847  $sql .= ", tosell";
4848  $sql .= ", price_by_qty";
4849  $sql .= ", fk_price_expression";
4850  $sql .= ", fk_multicurrency";
4851  $sql .= ", multicurrency_code";
4852  $sql .= ", multicurrency_tx";
4853  $sql .= ", multicurrency_price";
4854  $sql .= ", multicurrency_price_ttc";
4855  $sql .= ")";
4856  $sql .= " SELECT";
4857  $sql .= " entity";
4858  $sql .= ", ".$toId;
4859  $sql .= ", '".$this->db->idate($now)."'";
4860  $sql .= ", price_level";
4861  $sql .= ", price";
4862  $sql .= ", price_ttc";
4863  $sql .= ", price_min";
4864  $sql .= ", price_min_ttc";
4865  $sql .= ", price_base_type";
4866  $sql .= ", price_label";
4867  $sql .= ", default_vat_code";
4868  $sql .= ", tva_tx";
4869  $sql .= ", recuperableonly";
4870  $sql .= ", localtax1_tx";
4871  $sql .= ", localtax1_type";
4872  $sql .= ", localtax2_tx";
4873  $sql .= ", localtax2_type";
4874  $sql .= ", ".$user->id;
4875  $sql .= ", tosell";
4876  $sql .= ", price_by_qty";
4877  $sql .= ", fk_price_expression";
4878  $sql .= ", fk_multicurrency";
4879  $sql .= ", multicurrency_code";
4880  $sql .= ", multicurrency_tx";
4881  $sql .= ", multicurrency_price";
4882  $sql .= ", multicurrency_price_ttc";
4883  $sql .= " FROM ".$this->db->prefix()."product_price ps";
4884  $sql .= " WHERE fk_product = ".((int) $fromId);
4885  $sql .= " AND date_price IN (SELECT MAX(pd.date_price) FROM ".$this->db->prefix()."product_price pd WHERE pd.fk_product = ".((int) $fromId)." AND pd.price_level = ps.price_level)";
4886  $sql .= " ORDER BY date_price DESC";
4887 
4888  dol_syslog(__METHOD__, LOG_DEBUG);
4889  $resql = $this->db->query($sql);
4890  if (!$resql) {
4891  $this->db->rollback();
4892  return -1;
4893  }
4894 
4895  $this->db->commit();
4896  return 1;
4897  }
4898 
4899  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4907  public function clone_associations($fromId, $toId)
4908  {
4909  // phpcs:enable
4910  $this->db->begin();
4911 
4912  $sql = 'INSERT INTO '.$this->db->prefix().'product_association (fk_product_pere, fk_product_fils, qty, incdec)';
4913  $sql .= " SELECT ".$toId.", fk_product_fils, qty, incdec FROM ".$this->db->prefix()."product_association";
4914  $sql .= " WHERE fk_product_pere = ".((int) $fromId);
4915 
4916  dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
4917  if (!$this->db->query($sql)) {
4918  $this->db->rollback();
4919  return -1;
4920  }
4921 
4922  $this->db->commit();
4923  return 1;
4924  }
4925 
4926  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4934  public function clone_fournisseurs($fromId, $toId)
4935  {
4936  // phpcs:enable
4937  $this->db->begin();
4938 
4939  $now = dol_now();
4940 
4941  // les fournisseurs
4942  /*$sql = "INSERT ".$this->db->prefix()."product_fournisseur ("
4943  . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
4944  . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
4945  . " FROM ".$this->db->prefix()."product_fournisseur"
4946  . " WHERE fk_product = ".((int) $fromId);
4947 
4948  if ( ! $this->db->query($sql ) )
4949  {
4950  $this->db->rollback();
4951  return -1;
4952  }*/
4953 
4954  // les prix de fournisseurs.
4955  $sql = "INSERT ".$this->db->prefix()."product_fournisseur_price (";
4956  $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user, tva_tx)";
4957  $sql .= " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, price, quantity, fk_user, tva_tx";
4958  $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4959  $sql .= " WHERE fk_product = ".((int) $fromId);
4960 
4961  dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
4962  $resql = $this->db->query($sql);
4963  if (!$resql) {
4964  $this->db->rollback();
4965  return -1;
4966  } else {
4967  $this->db->commit();
4968  return 1;
4969  }
4970  }
4971 
4972  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4985  public function fetch_prod_arbo($prod, $compl_path = '', $multiply = 1, $level = 1, $id_parent = 0, $ignore_stock_load = 0)
4986  {
4987  // phpcs:enable
4988  global $conf, $langs;
4989 
4990  $tmpproduct = null;
4991  //var_dump($prod);
4992  foreach ($prod as $id_product => $desc_pere) { // $id_product is 0 (first call starting with root top) or an id of a sub_product
4993  if (is_array($desc_pere)) { // If desc_pere is an array, this means it's a child
4994  $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
4995  $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
4996  $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
4997  $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
4998  $incdec = (!empty($desc_pere[4]) ? $desc_pere[4] : 0);
4999 
5000  if ($multiply < 1) {
5001  $multiply = 1;
5002  }
5003 
5004  //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
5005  if (is_null($tmpproduct)) {
5006  $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
5007  }
5008  $tmpproduct->fetch($id); // Load product to get ->ref
5009 
5010  if (empty($ignore_stock_load) && ($tmpproduct->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
5011  $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
5012  }
5013 
5014  $this->res[] = array(
5015  'id' => $id, // Id product
5016  'id_parent' => $id_parent,
5017  'ref' => $tmpproduct->ref, // Ref product
5018  'nb' => $nb, // Nb of units that compose parent product
5019  'nb_total' => $nb * $multiply, // Nb of units for all nb of product
5020  'stock' => $tmpproduct->stock_reel, // Stock
5021  'stock_alert' => $tmpproduct->seuil_stock_alerte, // Stock alert
5022  'label' => $label,
5023  'fullpath' => $compl_path.$label, // Label
5024  'type' => $type, // Nb of units that compose parent product
5025  'desiredstock' => $tmpproduct->desiredstock,
5026  'level' => $level,
5027  'incdec' => $incdec,
5028  'entity' => $tmpproduct->entity
5029  );
5030 
5031  // Recursive call if there child has children of its own
5032  if (isset($desc_pere['childs']) && is_array($desc_pere['childs'])) {
5033  //print 'YYY We go down for '.$desc_pere[3]." -> \n";
5034  $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1] * $multiply, $level + 1, $id, $ignore_stock_load);
5035  }
5036  }
5037  }
5038  }
5039 
5040  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5049  public function get_arbo_each_prod($multiply = 1, $ignore_stock_load = 0)
5050  {
5051  // phpcs:enable
5052  $this->res = array();
5053  if (isset($this->sousprods) && is_array($this->sousprods)) {
5054  foreach ($this->sousprods as $prod_name => $desc_product) {
5055  if (is_array($desc_product)) {
5056  $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id, $ignore_stock_load); // This set $this->res
5057  }
5058  }
5059  }
5060  //var_dump($res);
5061  return $this->res;
5062  }
5063 
5071  public function hasFatherOrChild($mode = 0)
5072  {
5073  $nb = 0;
5074 
5075  $sql = "SELECT COUNT(pa.rowid) as nb";
5076  $sql .= " FROM ".$this->db->prefix()."product_association as pa";
5077  if ($mode == 0) {
5078  $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id)." OR pa.fk_product_pere = ".((int) $this->id);
5079  } elseif ($mode == -1) {
5080  $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)
5081  } elseif ($mode == 1) {
5082  $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)
5083  }
5084 
5085  $resql = $this->db->query($sql);
5086  if ($resql) {
5087  $obj = $this->db->fetch_object($resql);
5088  if ($obj) {
5089  $nb = $obj->nb;
5090  }
5091  } else {
5092  return -1;
5093  }
5094 
5095  return $nb;
5096  }
5097 
5103  public function hasVariants()
5104  {
5105  $nb = 0;
5106  $sql = "SELECT count(rowid) as nb FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_parent = ".((int) $this->id);
5107  $sql .= " AND entity IN (".getEntity('product').")";
5108 
5109  $resql = $this->db->query($sql);
5110  if ($resql) {
5111  $obj = $this->db->fetch_object($resql);
5112  if ($obj) {
5113  $nb = $obj->nb;
5114  }
5115  }
5116 
5117  return $nb;
5118  }
5119 
5120 
5126  public function isVariant()
5127  {
5128  global $conf;
5129  if (isModEnabled('variants')) {
5130  $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_child = ".((int) $this->id)." AND entity IN (".getEntity('product').")";
5131 
5132  $query = $this->db->query($sql);
5133 
5134  if ($query) {
5135  if (!$this->db->num_rows($query)) {
5136  return false;
5137  }
5138  return true;
5139  } else {
5140  dol_print_error($this->db);
5141  return -1;
5142  }
5143  } else {
5144  return false;
5145  }
5146  }
5147 
5154  public function getFather()
5155  {
5156  $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";
5157  $sql .= ", p.tosell as status, p.tobuy as status_buy";
5158  $sql .= " FROM ".$this->db->prefix()."product_association as pa,";
5159  $sql .= " ".$this->db->prefix()."product as p";
5160  $sql .= " WHERE p.rowid = pa.fk_product_pere";
5161  $sql .= " AND pa.fk_product_fils = ".((int) $this->id);
5162 
5163  $res = $this->db->query($sql);
5164  if ($res) {
5165  $prods = array();
5166  while ($record = $this->db->fetch_array($res)) {
5167  // $record['id'] = $record['rowid'] = id of father
5168  $prods[$record['id']]['id'] = $record['rowid'];
5169  $prods[$record['id']]['ref'] = $record['ref'];
5170  $prods[$record['id']]['label'] = $record['label'];
5171  $prods[$record['id']]['qty'] = $record['qty'];
5172  $prods[$record['id']]['incdec'] = $record['incdec'];
5173  $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
5174  $prods[$record['id']]['entity'] = $record['entity'];
5175  $prods[$record['id']]['status'] = $record['status'];
5176  $prods[$record['id']]['status_buy'] = $record['status_buy'];
5177  }
5178  return $prods;
5179  } else {
5180  dol_print_error($this->db);
5181  return -1;
5182  }
5183  }
5184 
5185 
5195  public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array())
5196  {
5197  global $alreadyfound;
5198 
5199  if (empty($id)) {
5200  return array();
5201  }
5202 
5203  $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
5204  $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
5205  $sql .= " pa.rowid as fk_association, pa.rang";
5206  $sql .= " FROM ".$this->db->prefix()."product as p,";
5207  $sql .= " ".$this->db->prefix()."product_association as pa";
5208  $sql .= " WHERE p.rowid = pa.fk_product_fils";
5209  $sql .= " AND pa.fk_product_pere = ".((int) $id);
5210  $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
5211  $sql .= " ORDER BY pa.rang";
5212 
5213  dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG);
5214 
5215  if ($level == 1) {
5216  $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 immediately
5217  }
5218  // Protection against infinite loop
5219  if ($level > 30) {
5220  return array();
5221  }
5222 
5223  $res = $this->db->query($sql);
5224  if ($res) {
5225  $prods = array();
5226  while ($rec = $this->db->fetch_array($res)) {
5227  if (!empty($alreadyfound[$rec['rowid']])) {
5228  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);
5229  if (in_array($rec['id'], $parents)) {
5230  continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5231  }
5232  }
5233  $alreadyfound[$rec['rowid']] = 1;
5234  $prods[$rec['rowid']] = array(
5235  0 => $rec['rowid'],
5236  1 => $rec['qty'],
5237  2 => $rec['fk_product_type'],
5238  3 => $this->db->escape($rec['label']),
5239  4 => $rec['incdec'],
5240  5 => $rec['ref'],
5241  6 => $rec['fk_association'],
5242  7 => $rec['rang']
5243  );
5244  //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5245  //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5246  if (empty($firstlevelonly)) {
5247  $parents[] = $rec['rowid'];
5248  $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents);
5249  foreach ($listofchilds as $keyChild => $valueChild) {
5250  $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5251  }
5252  }
5253  }
5254 
5255  return $prods;
5256  } else {
5257  dol_print_error($this->db);
5258  return -1;
5259  }
5260  }
5261 
5262  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5269  public function get_sousproduits_arbo()
5270  {
5271  // phpcs:enable
5272  $parent = array();
5273 
5274  foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5275  $parent[$this->label][$keyChild] = $valueChild;
5276  }
5277  foreach ($parent as $key => $value) { // key=label, value is array of children
5278  $this->sousprods[$key] = $value;
5279  }
5280  }
5281 
5288  public function getTooltipContentArray($params)
5289  {
5290  global $conf, $langs, $user;
5291 
5292  $langs->loadLangs(array('products', 'other'));
5293 
5294  $datas = array();
5295  $nofetch = !empty($params['nofetch']);
5296 
5297  if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5298  return ['optimize' => $langs->trans("ShowProduct")];
5299  }
5300 
5301  if (!empty($this->entity)) {
5302  $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, 1);
5303  if ($this->nbphoto > 0) {
5304  $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5305  }
5306  }
5307 
5308  if ($this->type == Product::TYPE_PRODUCT) {
5309  $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5310  } elseif ($this->type == Product::TYPE_SERVICE) {
5311  $datas['picto'] = img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5312  }
5313  if (isset($this->status) && isset($this->status_buy)) {
5314  $datas['status'] = ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5315  }
5316 
5317  if (!empty($this->ref)) {
5318  $datas['ref'] = '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5319  }
5320  if (!empty($this->label)) {
5321  $datas['label'] = '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label;
5322  }
5323  if (!empty($this->description)) {
5324  $datas['description'] = '<br><b>'.$langs->trans('ProductDescription').':</b> '.dolGetFirstLineOfText($this->description, 5);
5325  }
5326  if ($this->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
5327  if (isModEnabled('productbatch')) {
5328  $langs->load("productbatch");
5329  $datas['batchstatus'] = "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5330  }
5331  }
5332  if (isModEnabled('barcode')) {
5333  $datas['barcode'] = '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5334  }
5335 
5336  if ($this->type == Product::TYPE_PRODUCT) {
5337  if ($this->weight) {
5338  $datas['weight'] = "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5339  }
5340  $labelsize = "";
5341  if ($this->length) {
5342  $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5343  }
5344  if ($this->width) {
5345  $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5346  }
5347  if ($this->height) {
5348  $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5349  }
5350  if ($labelsize) {
5351  $datas['size'] = "<br>".$labelsize;
5352  }
5353 
5354  $labelsurfacevolume = "";
5355  if ($this->surface) {
5356  $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5357  }
5358  if ($this->volume) {
5359  $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5360  }
5361  if ($labelsurfacevolume) {
5362  $datas['surface'] = "<br>" . $labelsurfacevolume;
5363  }
5364  }
5365  if ($this->type == Product::TYPE_SERVICE && !empty($this->duration_value)) {
5366  // Duration
5367  $datas['duration'] = '<br><b>'.$langs->trans("Duration").':</b> '.$this->duration_value;
5368  if ($this->duration_value > 1) {
5369  $dur = array("i" => $langs->trans("Minutes"), "h" => $langs->trans("Hours"), "d" => $langs->trans("Days"), "w" => $langs->trans("Weeks"), "m" => $langs->trans("Months"), "y" => $langs->trans("Years"));
5370  } elseif ($this->duration_value > 0) {
5371  $dur = array("i" => $langs->trans("Minute"), "h" => $langs->trans("Hour"), "d" => $langs->trans("Day"), "w" => $langs->trans("Week"), "m" => $langs->trans("Month"), "y" => $langs->trans("Year"));
5372  }
5373  $datas['duration'] .= (!empty($this->duration_unit) && isset($dur[$this->duration_unit]) ? "&nbsp;".$langs->trans($dur[$this->duration_unit]) : '');
5374  }
5375  if (empty($user->socid)) {
5376  if (!empty($this->pmp) && $this->pmp) {
5377  $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5378  }
5379 
5380  if (isModEnabled('accounting')) {
5381  if ($this->status && isset($this->accountancy_code_sell)) {
5382  include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5383  $selllabel = '<br>';
5384  $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5385  $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5386  $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5387  $datas['accountancysell'] = $selllabel;
5388  }
5389  if ($this->status_buy && isset($this->accountancy_code_buy)) {
5390  include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5391  $buylabel = '';
5392  if (empty($this->status)) {
5393  $buylabel .= '<br>';
5394  }
5395  $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5396  $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5397  $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5398  $datas['accountancybuy'] = $buylabel;
5399  }
5400  }
5401  }
5402  // show categories for this record only in ajax to not overload lists
5403  if (isModEnabled('category') && !$nofetch) {
5404  require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5405  $form = new Form($this->db);
5406  $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5407  }
5408 
5409  return $datas;
5410  }
5411 
5425  public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5426  {
5427  global $conf, $langs, $hookmanager, $user;
5428  include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5429 
5430  $result = '';
5431 
5432  $newref = $this->ref;
5433  if ($maxlength) {
5434  $newref = dol_trunc($newref, $maxlength, 'middle');
5435  }
5436  $params = [
5437  'id' => $this->id,
5438  'objecttype' => (isset($this->type) ? ($this->type == 1 ? 'service' : 'product') : $this->element),
5439  'option' => $option,
5440  'nofetch' => 1,
5441  ];
5442  $classfortooltip = 'classfortooltip';
5443  $dataparams = '';
5444  if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5445  $classfortooltip = 'classforajaxtooltip';
5446  $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5447  $label = '';
5448  } else {
5449  $label = implode($this->getTooltipContentArray($params));
5450  }
5451 
5452  $linkclose = '';
5453  if (empty($notooltip)) {
5454  if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5455  $label = $langs->trans("ShowProduct");
5456  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5457  }
5458  $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5459  $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5460  } else {
5461  $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5462  }
5463 
5464  if ($option == 'supplier' || $option == 'category') {
5465  $url = DOL_URL_ROOT.'/product/price_suppliers.php?id='.$this->id;
5466  } elseif ($option == 'stock') {
5467  $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5468  } elseif ($option == 'composition') {
5469  $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5470  } else {
5471  $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5472  }
5473 
5474  if ($option !== 'nolink') {
5475  // Add param to save lastsearch_values or not
5476  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
5477  if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
5478  $add_save_lastsearch_values = 1;
5479  }
5480  if ($add_save_lastsearch_values) {
5481  $url .= '&save_lastsearch_values=1';
5482  }
5483  }
5484 
5485  $linkstart = '<a href="'.$url.'"';
5486  $linkstart .= $linkclose.'>';
5487  $linkend = '</a>';
5488 
5489  $result .= $linkstart;
5490  if ($withpicto) {
5491  if ($this->type == Product::TYPE_PRODUCT) {
5492  $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5493  }
5494  if ($this->type == Product::TYPE_SERVICE) {
5495  $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5496  }
5497  }
5498  $result .= '<span class="aaa">'.dol_escape_htmltag($newref).'</span>';
5499  $result .= $linkend;
5500  if ($withpicto != 2) {
5501  $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
5502  }
5503 
5504  global $action;
5505  $hookmanager->initHooks(array('productdao'));
5506  $parameters = array('id' => $this->id, 'getnomurl' => &$result, 'label' => &$label);
5507  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5508  if ($reshook > 0) {
5509  $result = $hookmanager->resPrint;
5510  } else {
5511  $result .= $hookmanager->resPrint;
5512  }
5513 
5514  return $result;
5515  }
5516 
5517 
5528  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
5529  {
5530  global $conf, $user, $langs;
5531 
5532  $langs->load("products");
5533  $outputlangs->load("products");
5534 
5535  // Positionne le modele sur le nom du modele a utiliser
5536  if (!dol_strlen($modele)) {
5537  $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
5538  }
5539 
5540  $modelpath = "core/modules/product/doc/";
5541 
5542  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
5543  }
5544 
5552  public function getLibStatut($mode = 0, $type = 0)
5553  {
5554  switch ($type) {
5555  case 0:
5556  return $this->LibStatut($this->status, $mode, $type);
5557  case 1:
5558  return $this->LibStatut($this->status_buy, $mode, $type);
5559  case 2:
5560  return $this->LibStatut($this->status_batch, $mode, $type);
5561  default:
5562  //Simulate previous behavior but should return an error string
5563  return $this->LibStatut($this->status_buy, $mode, $type);
5564  }
5565  }
5566 
5567  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5576  public function LibStatut($status, $mode = 0, $type = 0)
5577  {
5578  // phpcs:enable
5579  global $conf, $langs;
5580 
5581  $labelStatus = $labelStatusShort = '';
5582 
5583  $langs->load('products');
5584  if (isModEnabled('productbatch')) {
5585  $langs->load("productbatch");
5586  }
5587 
5588  if ($type == 2) {
5589  switch ($mode) {
5590  case 0:
5591  $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
5592  return dolGetStatus($label);
5593  case 1:
5594  $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
5595  return dolGetStatus($label);
5596  case 2:
5597  return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
5598  case 3:
5599  return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
5600  case 4:
5601  return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
5602  case 5:
5603  return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
5604  default:
5605  return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
5606  }
5607  }
5608 
5609  $statuttrans = empty($status) ? 'status5' : 'status4';
5610 
5611  if ($status == 0) {
5612  // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5613  if ($type == 0) {
5614  $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
5615  $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
5616  } elseif ($type == 1) {
5617  $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
5618  $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
5619  } elseif ($type == 2) {
5620  $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
5621  $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
5622  }
5623  } elseif ($status == 1) {
5624  // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5625  if ($type == 0) {
5626  $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
5627  $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
5628  } elseif ($type == 1) {
5629  $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
5630  $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
5631  } elseif ($type == 2) {
5632  $labelStatus = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial'));
5633  $labelStatusShort = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort'));
5634  }
5635  } elseif ($type == 2 && $status == 2) {
5636  $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
5637  $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
5638  }
5639 
5640  if ($mode > 6) {
5641  return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
5642  } else {
5643  return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
5644  }
5645  }
5646 
5647 
5653  public function getLibFinished()
5654  {
5655  global $langs;
5656  $langs->load('products');
5657  $label = '';
5658 
5659  if (isset($this->finished) && $this->finished >= 0) {
5660  $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
5661  $resql = $this->db->query($sql);
5662  if (!$resql) {
5663  $this->error = $this->db->error().' sql='.$sql;
5664  dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
5665  return -1;
5666  } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
5667  $label = $langs->trans($res['label']);
5668  }
5669  $this->db->free($resql);
5670  }
5671 
5672  return $label;
5673  }
5674 
5675 
5676  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5693  public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
5694  {
5695  // phpcs:enable
5696  if ($id_entrepot) {
5697  $this->db->begin();
5698 
5699  include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5700 
5701  if ($nbpiece < 0) {
5702  if (!$movement) {
5703  $movement = 1;
5704  }
5705  $nbpiece = abs($nbpiece);
5706  }
5707  $op = array();
5708  $op[0] = "+".trim((string) $nbpiece);
5709  $op[1] = "-".trim((string) $nbpiece);
5710 
5711  $movementstock = new MouvementStock($this->db);
5712  $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->origin_id
5713  $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
5714 
5715  if ($result >= 0) {
5716  if ($extrafields) {
5717  $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5718  $movementstock->array_options = $array_options;
5719  $movementstock->insertExtraFields();
5720  }
5721  $this->db->commit();
5722  return 1;
5723  } else {
5724  $this->error = $movementstock->error;
5725  $this->errors = $movementstock->errors;
5726 
5727  $this->db->rollback();
5728  return -1;
5729  }
5730  }
5731 
5732  return -1;
5733  }
5734 
5735  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5756  public function correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $dlc = '', $dluo = '', $lot = '', $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null, $force_update_batch = false)
5757  {
5758  // phpcs:enable
5759  if ($id_entrepot) {
5760  $this->db->begin();
5761 
5762  include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5763 
5764  if ($nbpiece < 0) {
5765  if (!$movement) {
5766  $movement = 1;
5767  }
5768  $nbpiece = abs($nbpiece);
5769  }
5770 
5771  $op = array();
5772  $op[0] = "+".trim((string) $nbpiece);
5773  $op[1] = "-".trim((string) $nbpiece);
5774 
5775  $movementstock = new MouvementStock($this->db);
5776  $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->fk_origin
5777  $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct, 0, $force_update_batch);
5778 
5779  if ($result >= 0) {
5780  if ($extrafields) {
5781  $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5782  $movementstock->array_options = $array_options;
5783  $movementstock->insertExtraFields();
5784  }
5785  $this->db->commit();
5786  return 1;
5787  } else {
5788  $this->error = $movementstock->error;
5789  $this->errors = $movementstock->errors;
5790 
5791  $this->db->rollback();
5792  return -1;
5793  }
5794  }
5795  return -1;
5796  }
5797 
5798  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5811  public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
5812  {
5813  // phpcs:enable
5814  global $conf;
5815 
5816  $this->stock_reel = 0;
5817  $this->stock_warehouse = array();
5818  $this->stock_theorique = 0;
5819 
5820  // Set filter on warehouse status
5821  $warehouseStatus = array();
5822  if (preg_match('/warehouseclosed/', $option)) {
5824  }
5825  if (preg_match('/warehouseopen/', $option)) {
5827  }
5828  if (preg_match('/warehouseinternal/', $option)) {
5829  if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
5831  } else {
5833  }
5834  }
5835 
5836  $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
5837  $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
5838  $sql .= ", ".$this->db->prefix()."entrepot as w";
5839  $sql .= " WHERE w.entity IN (".getEntity('stock').")";
5840  $sql .= " AND w.rowid = ps.fk_entrepot";
5841  $sql .= " AND ps.fk_product = ".((int) $this->id);
5842  if (count($warehouseStatus)) {
5843  $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
5844  }
5845 
5846  $sql .= " ORDER BY ps.reel ".(getDolGlobalString('DO_NOT_TRY_TO_DEFRAGMENT_STOCKS_WAREHOUSE') ? 'DESC' : 'ASC'); // Note : qty ASC is important for expedition card, to avoid stock fragmentation;
5847 
5848  dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
5849  $result = $this->db->query($sql);
5850  if ($result) {
5851  $num = $this->db->num_rows($result);
5852  $i = 0;
5853  if ($num > 0) {
5854  while ($i < $num) {
5855  $row = $this->db->fetch_object($result);
5856  $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
5857  $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
5858  $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
5859  if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
5860  $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
5861  }
5862  $this->stock_reel += $row->reel;
5863  $i++;
5864  }
5865  }
5866  $this->db->free($result);
5867 
5868  if (!preg_match('/novirtual/', $option)) {
5869  $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
5870  }
5871 
5872  return 1;
5873  } else {
5874  $this->error = $this->db->lasterror();
5875  return -1;
5876  }
5877  }
5878 
5879 
5880  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5890  public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
5891  {
5892  // phpcs:enable
5893  global $conf, $hookmanager, $action;
5894 
5895  $stock_commande_client = 0;
5896  $stock_commande_fournisseur = 0;
5897  $stock_sending_client = 0;
5898  $stock_reception_fournisseur = 0;
5899  $stock_inproduction = 0;
5900 
5901  //dol_syslog("load_virtual_stock");
5902 
5903  if (isModEnabled('order')) {
5904  $result = $this->load_stats_commande(0, '1,2', 1);
5905  if ($result < 0) {
5906  dol_print_error($this->db, $this->error);
5907  }
5908  $stock_commande_client = $this->stats_commande['qty'];
5909  }
5910  if (isModEnabled("shipping")) {
5911  require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
5912  $filterShipmentStatus = '';
5913  if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
5914  $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
5915  } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
5916  $filterShipmentStatus = Expedition::STATUS_CLOSED;
5917  }
5918  $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
5919  if ($result < 0) {
5920  dol_print_error($this->db, $this->error);
5921  }
5922  $stock_sending_client = $this->stats_expedition['qty'];
5923  }
5924  if (isModEnabled("supplier_order")) {
5925  $filterStatus = !getDolGlobalString('SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK') ? '3,4' : $conf->global->SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK;
5926  if (isset($includedraftpoforvirtual)) {
5927  $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
5928  }
5929  $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
5930  if ($result < 0) {
5931  dol_print_error($this->db, $this->error);
5932  }
5933  $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
5934  }
5935  if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && empty($conf->reception->enabled)) {
5936  // Case module reception is not used
5937  $filterStatus = '4';
5938  if (isset($includedraftpoforvirtual)) {
5939  $filterStatus = '0,'.$filterStatus;
5940  }
5941  $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
5942  if ($result < 0) {
5943  dol_print_error($this->db, $this->error);
5944  }
5945  $stock_reception_fournisseur = $this->stats_reception['qty'];
5946  }
5947  if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && isModEnabled("reception")) {
5948  // Case module reception is used
5949  $filterStatus = '4';
5950  if (isset($includedraftpoforvirtual)) {
5951  $filterStatus = '0,'.$filterStatus;
5952  }
5953  $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock); // Use same tables than when module reception is not used.
5954  if ($result < 0) {
5955  dol_print_error($this->db, $this->error);
5956  }
5957  $stock_reception_fournisseur = $this->stats_reception['qty'];
5958  }
5959  if (isModEnabled('mrp')) {
5960  $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
5961  if ($result < 0) {
5962  dol_print_error($this->db, $this->error);
5963  }
5964  $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
5965  }
5966 
5967  $this->stock_theorique = $this->stock_reel + $stock_inproduction;
5968 
5969  // Stock decrease mode
5970  if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
5971  $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
5972  } elseif (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER')) {
5973  $this->stock_theorique += 0;
5974  } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
5975  $this->stock_theorique -= $stock_commande_client;
5976  }
5977  // Stock Increase mode
5978  if (getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION') || getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
5979  $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5980  } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
5981  $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5982  } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) {
5983  $this->stock_theorique -= $stock_reception_fournisseur;
5984  } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
5985  $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5986  }
5987 
5988  $parameters = array('id' => $this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
5989  // Note that $action and $object may have been modified by some hooks
5990  $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
5991  if ($reshook > 0) {
5992  $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
5993  } elseif ($reshook == 0 && isset($hookmanager->resArray['stock_stats_hook'])) {
5994  $this->stock_theorique += $hookmanager->resArray['stock_stats_hook'];
5995  }
5996 
5997  //Virtual Stock by Warehouse
5998  if (!empty($this->stock_warehouse) && getDolGlobalString('STOCK_ALLOW_VIRTUAL_STOCK_PER_WAREHOUSE')) {
5999  foreach ($this->stock_warehouse as $warehouseid => $stockwarehouse) {
6000  if (isModEnabled('mrp')) {
6001  $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock, $warehouseid);
6002  if ($result < 0) {
6003  dol_print_error($this->db, $this->error);
6004  }
6005  }
6006 
6007  if ($this->fk_default_warehouse == $warehouseid) {
6008  $this->stock_warehouse[$warehouseid]->virtual = $this->stock_warehouse[$warehouseid]->real + $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] + $this->stats_commande_fournisseur['qty'] - ($this->stats_commande['qty'] + $this->stats_mrptoconsume['qty']);
6009  } else {
6010  $this->stock_warehouse[$warehouseid]->virtual = $this->stock_warehouse[$warehouseid]->real + $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'];
6011  }
6012  }
6013  }
6014 
6015  return 1;
6016  }
6017 
6018 
6026  public function loadBatchInfo($batch)
6027  {
6028  $result = array();
6029 
6030  $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";
6031  $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
6032  $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
6033  dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
6034  $resql = $this->db->query($sql);
6035  if ($resql) {
6036  $num = $this->db->num_rows($resql);
6037  $i = 0;
6038  while ($i < $num) {
6039  $obj = $this->db->fetch_object($resql);
6040  $result[] = array('batch' => $batch, 'eatby' => $this->db->jdate($obj->eatby), 'sellby' => $this->db->jdate($obj->sellby), 'qty' => $obj->qty);
6041  $i++;
6042  }
6043  return $result;
6044  } else {
6045  dol_print_error($this->db);
6046  $this->db->rollback();
6047  return array();
6048  }
6049  }
6050 
6051  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6059  public function add_photo($sdir, $file)
6060  {
6061  // phpcs:enable
6062  global $conf;
6063 
6064  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6065 
6066  $result = 0;
6067 
6068  $dir = $sdir;
6069  if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6070  $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
6071  } else {
6072  $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
6073  }
6074 
6075  dol_mkdir($dir);
6076 
6077  $dir_osencoded = $dir;
6078 
6079  if (is_dir($dir_osencoded)) {
6080  $originImage = $dir.'/'.$file['name'];
6081 
6082  // Cree fichier en taille origine
6083  $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
6084 
6085  if (file_exists(dol_osencode($originImage))) {
6086  // Create thumbs
6087  $this->addThumbs($originImage);
6088  }
6089  }
6090 
6091  if (is_numeric($result) && $result > 0) {
6092  return 1;
6093  } else {
6094  return -1;
6095  }
6096  }
6097 
6098  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6105  public function is_photo_available($sdir)
6106  {
6107  // phpcs:enable
6108  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6109  include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6110 
6111  global $conf;
6112 
6113  $dir = $sdir;
6114  if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6115  $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
6116  } else {
6117  $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
6118  }
6119 
6120  $nbphoto = 0;
6121 
6122  $dir_osencoded = dol_osencode($dir);
6123  if (file_exists($dir_osencoded)) {
6124  $handle = opendir($dir_osencoded);
6125  if (is_resource($handle)) {
6126  while (($file = readdir($handle)) !== false) {
6127  if (!utf8_check($file)) {
6128  $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
6129  }
6130  if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6131  return true;
6132  }
6133  }
6134  }
6135  }
6136  return false;
6137  }
6138 
6139  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6147  public function liste_photos($dir, $nbmax = 0)
6148  {
6149  // phpcs:enable
6150  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6151  include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6152 
6153  $nbphoto = 0;
6154  $tabobj = array();
6155 
6156  $dir_osencoded = dol_osencode($dir);
6157  $handle = @opendir($dir_osencoded);
6158  if (is_resource($handle)) {
6159  while (($file = readdir($handle)) !== false) {
6160  if (!utf8_check($file)) {
6161  $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // readdir returns ISO
6162  }
6163  if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6164  $nbphoto++;
6165 
6166  // We forge name of thumb.
6167  $photo = $file;
6168  $photo_vignette = '';
6169  $regs = array();
6170  if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
6171  $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
6172  }
6173 
6174  $dirthumb = $dir.'thumbs/';
6175 
6176  // Object
6177  $obj = array();
6178  $obj['photo'] = $photo;
6179  if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
6180  $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
6181  } else {
6182  $obj['photo_vignette'] = "";
6183  }
6184 
6185  $tabobj[$nbphoto - 1] = $obj;
6186 
6187  // Do we have to continue with next photo ?
6188  if ($nbmax && $nbphoto >= $nbmax) {
6189  break;
6190  }
6191  }
6192  }
6193 
6194  closedir($handle);
6195  }
6196 
6197  return $tabobj;
6198  }
6199 
6200  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6207  public function delete_photo($file)
6208  {
6209  // phpcs:enable
6210  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6211  include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6212 
6213  $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
6214  $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
6215  $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
6216 
6217  // On efface l'image d'origine
6218  dol_delete_file($file, 0, 0, 0, $this); // For triggers
6219 
6220  // Si elle existe, on efface la vignette
6221  if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
6222  $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
6223  if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6224  dol_delete_file($dirthumb.$photo_vignette);
6225  }
6226 
6227  $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
6228  if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6229  dol_delete_file($dirthumb.$photo_vignette);
6230  }
6231  }
6232  }
6233 
6234  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6241  public function get_image_size($file)
6242  {
6243  // phpcs:enable
6244  $file_osencoded = dol_osencode($file);
6245  $infoImg = getimagesize($file_osencoded); // Get information on image
6246  $this->imgWidth = $infoImg[0]; // Largeur de l'image
6247  $this->imgHeight = $infoImg[1]; // Hauteur de l'image
6248  }
6249 
6255  public function loadStateBoard()
6256  {
6257  global $hookmanager;
6258 
6259  $this->nb = array();
6260 
6261  $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6262  $sql .= " FROM ".$this->db->prefix()."product as p";
6263  $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6264  // Add where from hooks
6265  if (is_object($hookmanager)) {
6266  $parameters = array();
6267  $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6268  $sql .= $hookmanager->resPrint;
6269  }
6270  $sql .= ' GROUP BY fk_product_type';
6271 
6272  $resql = $this->db->query($sql);
6273  if ($resql) {
6274  while ($obj = $this->db->fetch_object($resql)) {
6275  if ($obj->fk_product_type == 1) {
6276  $this->nb["services"] = $obj->nb;
6277  } else {
6278  $this->nb["products"] = $obj->nb;
6279  }
6280  }
6281  $this->db->free($resql);
6282  return 1;
6283  } else {
6284  dol_print_error($this->db);
6285  $this->error = $this->db->error();
6286  return -1;
6287  }
6288  }
6289 
6295  public function isProduct()
6296  {
6297  return ($this->type == Product::TYPE_PRODUCT ? true : false);
6298  }
6299 
6305  public function isService()
6306  {
6307  return ($this->type == Product::TYPE_SERVICE ? true : false);
6308  }
6309 
6315  public function isStockManaged()
6316  {
6317  return ($this->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'));
6318  }
6319 
6325  public function isMandatoryPeriod()
6326  {
6327  return ($this->mandatory_period == 1 ? true : false);
6328  }
6329 
6335  public function hasbatch()
6336  {
6337  return ($this->status_batch > 0 ? true : false);
6338  }
6339 
6340 
6341  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6350  public function get_barcode($object, $type = '')
6351  {
6352  // phpcs:enable
6353  global $conf;
6354 
6355  $result = '';
6356  if (getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
6357  $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6358  foreach ($dirsociete as $dirroot) {
6359  $res = dol_include_once($dirroot . getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM').'.php');
6360  if ($res) {
6361  break;
6362  }
6363  }
6364  $var = getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM');
6365  $mod = new $var();
6366  '@phan-var-force ModeleNumRefBarCode $module';
6367 
6368  $result = $mod->getNextValue($object, $type);
6369 
6370  dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6371  }
6372  return $result;
6373  }
6374 
6382  public function initAsSpecimen()
6383  {
6384  $now = dol_now();
6385 
6386  // Initialize parameters
6387  $this->specimen = 1;
6388  $this->id = 0;
6389  $this->ref = 'PRODUCT_SPEC';
6390  $this->label = 'PRODUCT SPECIMEN';
6391  $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
6392  $this->specimen = 1;
6393  $this->country_id = 1;
6394  $this->status = 1;
6395  $this->status_buy = 1;
6396  $this->tobatch = 0;
6397  $this->sell_or_eat_by_mandatory = 0;
6398  $this->note_private = 'This is a comment (private)';
6399  $this->note_public = 'This is a comment (public)';
6400  $this->date_creation = $now;
6401  $this->date_modification = $now;
6402 
6403  $this->weight = 4;
6404  $this->weight_units = 3;
6405 
6406  $this->length = 5;
6407  $this->length_units = 1;
6408  $this->width = 6;
6409  $this->width_units = 0;
6410  $this->height = null;
6411  $this->height_units = null;
6412 
6413  $this->surface = 30;
6414  $this->surface_units = 0;
6415  $this->volume = 300;
6416  $this->volume_units = 0;
6417 
6418  $this->barcode = -1; // Create barcode automatically
6419 
6420  return 1;
6421  }
6422 
6429  public function getLabelOfUnit($type = 'long')
6430  {
6431  global $langs;
6432 
6433  if (!$this->fk_unit) {
6434  return '';
6435  }
6436 
6437  $langs->load('products');
6438  $label = '';
6439  $label_type = 'label';
6440  if ($type == 'short') {
6441  $label_type = 'short_label';
6442  }
6443 
6444  $sql = "SELECT ".$label_type.", code from ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
6445 
6446  $resql = $this->db->query($sql);
6447  if (!$resql) {
6448  $this->error = $this->db->error();
6449  dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
6450  return -1;
6451  } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
6452  $label = ($label_type == 'short_label' ? $res[$label_type] : 'unit'.$res['code']);
6453  }
6454  $this->db->free($resql);
6455 
6456  return $label;
6457  }
6458 
6459  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6465  public function min_recommended_price()
6466  {
6467  // phpcs:enable
6468  global $conf;
6469 
6470  $maxpricesupplier = 0;
6471 
6472  if (getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE')) {
6473  include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6474  $product_fourn = new ProductFournisseur($this->db);
6475  $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
6476 
6477  if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
6478  foreach ($product_fourn_list as $productfourn) {
6479  if ($productfourn->fourn_unitprice > $maxpricesupplier) {
6480  $maxpricesupplier = $productfourn->fourn_unitprice;
6481  }
6482  }
6483 
6484  $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
6485  }
6486  }
6487 
6488  return $maxpricesupplier;
6489  }
6490 
6491 
6502  public function setCategories($categories)
6503  {
6504  require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
6505  return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
6506  }
6507 
6516  public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
6517  {
6518  $tables = array(
6519  'product_customer_price',
6520  'product_customer_price_log'
6521  );
6522 
6523  return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
6524  }
6525 
6537  public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
6538  {
6539  global $conf;
6540 
6541  $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
6542  $query = $this->db->query($sql);
6543 
6544  $rules = array();
6545 
6546  while ($result = $this->db->fetch_object($query)) {
6547  $rules[$result->level] = $result;
6548  }
6549 
6550  //Because prices can be based on other level's prices, we temporarily store them
6551  $prices = array(
6552  1 => $baseprice
6553  );
6554 
6555  $nbofproducts = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
6556  for ($i = 1; $i <= $nbofproducts; $i++) {
6557  $price = $baseprice;
6558  $price_min = $baseprice;
6559 
6560  //We have to make sure it does exist and it is > 0
6561  //First price level only allows changing min_price
6562  if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
6563  $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
6564  }
6565 
6566  $prices[$i] = $price;
6567 
6568  //We have to make sure it does exist and it is > 0
6569  if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
6570  $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
6571  }
6572 
6573  //Little check to make sure the price is modified before triggering generation
6574  $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
6575  $check_type = ($baseprice == $this->multiprices_base_type[$i]);
6576 
6577  if ($check_amount && $check_type) {
6578  continue;
6579  }
6580 
6581  if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
6582  return -1;
6583  }
6584  }
6585 
6586  return 1;
6587  }
6588 
6594  public function getRights()
6595  {
6596  global $user;
6597 
6598  if ($this->isProduct()) {
6599  return $user->rights->produit;
6600  } else {
6601  return $user->rights->service;
6602  }
6603  }
6604 
6611  public function info($id)
6612  {
6613  $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
6614  $sql .= " p.fk_user_author, p.fk_user_modif";
6615  $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
6616  $sql .= " WHERE p.rowid = ".((int) $id);
6617 
6618  $result = $this->db->query($sql);
6619  if ($result) {
6620  if ($this->db->num_rows($result)) {
6621  $obj = $this->db->fetch_object($result);
6622 
6623  $this->id = $obj->rowid;
6624  $this->ref = $obj->ref;
6625 
6626  $this->user_creation_id = $obj->fk_user_author;
6627  $this->user_modification_id = $obj->fk_user_modif;
6628 
6629  $this->date_creation = $this->db->jdate($obj->date_creation);
6630  $this->date_modification = $this->db->jdate($obj->date_modification);
6631  }
6632 
6633  $this->db->free($result);
6634  } else {
6635  dol_print_error($this->db);
6636  }
6637  }
6638 
6639 
6645  public function getProductDurationHours()
6646  {
6647  global $langs;
6648 
6649  if (empty($this->duration_value)) {
6650  $this->errors[] = 'ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
6651  return -1;
6652  }
6653 
6654  if ($this->duration_unit == 'i') {
6655  $prodDurationHours = 1. / 60;
6656  }
6657  if ($this->duration_unit == 'h') {
6658  $prodDurationHours = 1.;
6659  }
6660  if ($this->duration_unit == 'd') {
6661  $prodDurationHours = 24.;
6662  }
6663  if ($this->duration_unit == 'w') {
6664  $prodDurationHours = 24. * 7;
6665  }
6666  if ($this->duration_unit == 'm') {
6667  $prodDurationHours = 24. * 30;
6668  }
6669  if ($this->duration_unit == 'y') {
6670  $prodDurationHours = 24. * 365;
6671  }
6672  $prodDurationHours *= $this->duration_value;
6673 
6674  return $prodDurationHours;
6675  }
6676 
6677 
6685  public function getKanbanView($option = '', $arraydata = null)
6686  {
6687  global $langs,$conf;
6688 
6689  $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6690 
6691  $return = '<div class="box-flex-item box-flex-grow-zero">';
6692  $return .= '<div class="info-box info-box-sm">';
6693  $return .= '<div class="info-box-img">';
6694  $label = '';
6695  if ($this->is_photo_available($conf->product->multidir_output[$this->entity])) {
6696  $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
6697  $return .= $label;
6698  } else {
6699  if ($this->type == Product::TYPE_PRODUCT) {
6700  $label .= img_picto('', 'product');
6701  } elseif ($this->type == Product::TYPE_SERVICE) {
6702  $label .= img_picto('', 'service');
6703  }
6704  $return .= $label;
6705  }
6706  $return .= '</div>';
6707  $return .= '<div class="info-box-content">';
6708  $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
6709  if ($selected >= 0) {
6710  $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
6711  }
6712  if (property_exists($this, 'label')) {
6713  $return .= '<br><span class="info-box-label opacitymedium inline-block tdoverflowmax150 valignmiddle" title="'.dol_escape_htmltag($this->label).'">'.dol_escape_htmltag($this->label).'</span>';
6714  }
6715  if (property_exists($this, 'price') && property_exists($this, 'price_ttc')) {
6716  if ($this->price_base_type == 'TTC') {
6717  $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
6718  } else {
6719  if ($this->status) {
6720  $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
6721  }
6722  }
6723  }
6724  $br = 1;
6725  if (property_exists($this, 'stock_reel') && $this->isProduct()) {
6726  $return .= '<br><div class="info-box-status opacitymedium inline-block valignmiddle">'.img_picto($langs->trans('PhysicalStock'), 'stock').'</div><div class="inline-block valignmiddle paddingleft" title="'.$langs->trans('PhysicalStock').'">'.$this->stock_reel.'</div>';
6727  $br = 0;
6728  }
6729  if (method_exists($this, 'getLibStatut')) {
6730  if ($br) {
6731  $return .= '<br><div class="info-box-status inline-block valignmiddle">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6732  } else {
6733  $return .= '<div class="info-box-status inline-block valignmiddle marginleftonly paddingleft">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6734  }
6735  }
6736  $return .= '</div>';
6737  $return .= '</div>';
6738  $return .= '</div>';
6739  return $return;
6740  }
6741 }
6742 
6748 {
6749  public $picto = 'service';
6750 }
if($user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition: card.php:58
length_accountg($account)
Return General accounting account with defined length (used for product and miscellaneous)
print $langs trans("AuditedSecurityEvents").'</strong >< span class="opacitymedium"></span >< br > status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition: security.php:607
$object ref
Definition: info.php:79
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
deleteEcmFiles($mode=0)
Delete related files of object in database.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
isObjectUsed($id=0, $entity=0)
Function to check if an object is used by others (by children).
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.
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.
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 -> parcel was received by customer / end of process prev status : validated or shipment...
const STATUS_VALIDATED
Validated status -> parcel is ready to be sent prev status : draft next status : closed or shipment_i...
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 the relation between a product and one of its variants.
File of class to manage predefined price products or services by customer.
Class to manage predefined suppliers products.
Class to manage products or services.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
get_nb_achat($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or supplier invoices in which product is included.
getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp=0)
Return price of sell of a product for a seller/buyer/product.
__construct($db)
Constructor.
$price_by_qty
Price by quantity arrays.
is_sousproduit($fk_parent, $fk_child)
Check if it is a sub-product into a kit.
const SELL_OR_EAT_BY_MANDATORY_ID_NONE
Const sell or eat by mandatory id.
isStockManaged()
Return if object need to have its stock managed.
$duration
Service expiration label (value + unit)
setPriceExpression($expression_id)
Sets the supplier price expression.
getArrayForPriceCompare($level=0)
used to check if price have really change to avoid log pollution
check_barcode($valuetotest, $typefortest)
Check barcode.
list_suppliers()
Return list of suppliers providing the product or service.
load_stats_mo($socid=0)
Charge tableau des stats OF pour le produit/service.
isVariant()
Return if loaded product is a variant.
updatePrice($newprice, $newpricebase, $user, $newvat=null, $newminprice=0, $level=0, $newnpr=0, $newpbq=0, $ignore_autogen=0, $localtaxes_array=array(), $newdefaultvatcode='', $price_label='', $notrigger=0)
Modify customer price of a product/Service for a given level.
hasVariants()
Return if a product has variants or not.
delMultiLangs($langtodelete, $user)
Delete a language for this product.
getLabelOfUnit($type='long')
Returns the text label from units dictionary.
load_stats_proposal_supplier($socid=0)
Charge tableau des stats propale pour le produit/service.
getLibFinished()
Retour label of nature of product.
add_sousproduit($id_pere, $id_fils, $qty, $incdec=1, $notrigger=0)
Link a product/service to a parent product/service.
add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
Add a supplier price for the product.
hasFatherOrChild($mode=0)
Count all parent and children products for current product (first level only)
load_stats_facturerec($socid=0)
Charge tableau des stats facture recurrentes pour le produit/service.
$product_id_already_linked
Product ID already linked to a reference supplier.
get_nb_propalsupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in proposals in which product is included.
get_nb_contract($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
load_stats_facture_fournisseur($socid=0)
Charge tableau des stats facture pour le produit/service.
get_nb_ordersupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
getMultiLangs()
Load array this->multilangs.
get_nb_mos($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
clone_associations($fromId, $toId)
Clone links between products.
create($user, $notrigger=0)
Insert product into database.
load_stats_contrat($socid=0)
Charge tableau des stats contrat pour le produit/service.
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...
get_arbo_each_prod($multiply=1, $ignore_stock_load=0)
Build the tree of subproducts and return it.
getProductDurationHours()
Return the duration of a service in hours (for a service based on duration fields)
$default_vat_code
Default VAT code for product (link to code into llx_c_tva but without foreign keys)
$duration_unit
Service 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.
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.
info($id)
Load information for tab info.
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.
static getSellOrEatByMandatoryList()
Get sell or eat by mandatory list.
$multiprices
Arrays for multiprices.
$localtax1_tx
Other local taxes.
getChildsArbo($id, $firstlevelonly=0, $level=1, $parents=array())
Return children 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.
update($id, $user, $notrigger=0, $action='update', $updatetype=false)
Update a record into database.
setMultiLangs($user)
Update or add a translation for a product.
correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $dlc='', $dluo='', $lot='', $inventorycode='', $origin_element='', $origin_id=null, $disablestockchangeforsubproduct=0, $extrafields=null, $force_update_batch=false)
Adjust stock in a warehouse for product with batch number.
$tva_npr
int French VAT NPR is used (0 or 1)
$tva_tx
Default VAT rate of product.
load_stats_bom($socid=0)
Charge tableau des stats OF pour le produit/service.
hasbatch()
Return if object has a sell-by date or eat-by date.
$weight
Metric of products.
del_sousproduit($fk_parent, $fk_child, $notrigger=0)
Remove a link between a subproduct and a parent product/service.
fetch($id=0, $ref='', $ref_ext='', $barcode='', $ignore_expression=0, $ignore_price_load=0, $ignore_lang_load=0)
Load a product in memory from database.
update_sousproduit($id_pere, $id_fils, $qty, $incdec=1, $notrigger=0)
Modify composed product.
$table_ref_field
{}
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.
load_stats_inproduction($socid=0, $filtrestatut='', $forVirtualStock=0, $dateofvirtualstock=null, $warehouseid=0)
Charge tableau des stats production pour le produit/service.
$duration_value
Service expiration.
check()
Check that ref and label are ok.
initAsSpecimen()
Initialise an instance with random values.
liste_photos($dir, $nbmax=0)
Return an array with all photos of product found on disk.
loadStateBoard()
Load indicators this->nb for the dashboard.
getFather()
Return all parent products for current product (first level only)
getNomUrl($withpicto=0, $option='', $maxlength=0, $save_lastsearch_value=-1, $notooltip=0, $morecss='', $add_label=0, $sep=' - ')
Return clickable link of object (with eventually picto)
$product_fourn_id
Id du fournisseur.
getSellOrEatByMandatoryLabel()
Get sell or eat by mandatory label.
$desiredstock
Ask for replenishment when $desiredstock < $stock_reel.
verify()
Check properties of product are ok (like name, barcode, ...).
get_sousproduits_arbo()
get_nb_order($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
min_recommended_price()
Return minimum product recommended price.
_log_price($user, $level=0)
Insert a track that we changed a customer price.
_get_stats($sql, $mode, $year=0)
Return an array formatted for showing graphs.
$multilangs
Array for multilangs.
load_stats_commande_fournisseur($socid=0, $filtrestatut='', $forVirtualStock=0, $dateofvirtualstock=null)
Charge tableau des stats commande fournisseur pour le produit/service.
isMandatoryPeriod()
Return if object have a constraint on mandatory_period.
isProduct()
Return if object is a product.
generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
Generates prices for a product based on product multiprice generation rules.
LibStatut($status, $mode=0, $type=0)
Return label of a given status.
const TYPE_SERVICE
Service.
is_photo_available($sdir)
Return if at least one photo is available.
get_image_size($file)
Load size of image file.
get_nb_vente($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or customers invoices in which product is included.
getTooltipContentArray($params)
getTooltipContentArray
Class to manage products or services.
Manage record for batch number management.
static findAll($dbs, $fk_product_stock, $with_qty=0, $fk_product=0)
Return all batch detail records for a given product and warehouse.
Class with list of lots and properties.
Class to manage Dolibarr users.
Definition: user.class.php:50
hasRight($module, $permlevel1, $permlevel2='')
Return if a user has a permission.
Definition: user.class.php:772
getCountry($searchkey, $withcode='', $dbtouse=null, $outputlangs=null, $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
if(isModEnabled('invoice') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD') && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') && $user->hasRight('don', 'lire')) if(isModEnabled('tax') && $user->hasRight('tax', 'charges', 'lire')) if(isModEnabled('invoice') &&isModEnabled('order') && $user->hasRight("commande", "lire") &&!getDolGlobalString('WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER')) $sql
Social contributions to pay.
Definition: index.php:745
print *****$script_file(".$version.") pid 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)
Definition: files.lib.php:1609
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.
Definition: files.lib.php:1458
dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disablevirusscan=0, $uploaderrorcode=0, $nohook=0, $varfiles='addedfile', $upload_dir='')
Check validity of a file upload from an GUI page, and move it to its final destination.
Definition: files.lib.php:1320
dol_is_file($pathoffile)
Return if path is a file.
Definition: files.lib.php:519
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
dolGetFirstLineOfText($text, $nboflines=1, $charset='UTF-8')
Return first line of text.
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_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.
if(!function_exists('dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
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.
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
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.
isModEnabled($module)
Is Dolibarr module enabled.
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...
get_localtax($vatrate, $local, $thirdparty_buyer=null, $thirdparty_seller=null, $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
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.
Definition: images.lib.php:84
div float
Buy price without taxes.
Definition: style.css.php:960
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:123