dolibarr  13.0.2
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-2020 Alexandre Spangaro <aspangaro@open-dsi.fr>
12  * Copyright (C) 2014 Henry Florian <florian.henry@open-concept.pro>
13  * Copyright (C) 2014-2016 Philippe Grand <philippe.grand@atoo-net.com>
14  * Copyright (C) 2014 Ion agorria <ion@agorria.com>
15  * Copyright (C) 2016-2018 Ferran Marcet <fmarcet@2byte.es>
16  * Copyright (C) 2017 Gustavo Novaro
17  * Copyright (C) 2019-2020 Frédéric France <frederic.france@netlogic.fr>
18  *
19  * This program is free software; you can redistribute it and/or modify
20  * it under the terms of the GNU General Public License as published by
21  * the Free Software Foundation; either version 3 of the License, or
22  * (at your option) any later version.
23  *
24  * This program is distributed in the hope that it will be useful,
25  * but WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27  * GNU General Public License for more details.
28  *
29  * You should have received a copy of the GNU General Public License
30  * along with this program. If not, see <https://www.gnu.org/licenses/>.
31  */
32 
38 require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
39 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
40 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
41 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
42 
46 class Product extends CommonObject
47 {
51  public $element = 'product';
52 
56  public $table_element = 'product';
57 
61  public $fk_element = 'fk_product';
62 
66  protected $childtables = array(
67  'supplier_proposaldet',
68  'propaldet',
69  'commandedet',
70  'facturedet',
71  'contratdet',
72  'facture_fourn_det',
73  'commande_fournisseurdet'
74  );
75 
81  public $ismultientitymanaged = 1;
82 
86  public $picto = 'product';
87 
91  protected $table_ref_field = 'ref';
92 
93  public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.xpm|\.xbm'; // See also into images.lib.php
94 
95  /*
96  * @deprecated
97  * @see label
98  */
99  public $libelle;
100 
106  public $label;
107 
113  public $description;
114 
120  public $other;
121 
127  public $type = self::TYPE_PRODUCT;
128 
134  public $price; // Price net
135 
141  public $price_ttc;
142 
148  public $price_min;
149 
155  public $price_min_ttc;
156 
161  public $price_base_type;
162 
164  public $multiprices = array();
165  public $multiprices_ttc = array();
166  public $multiprices_base_type = array();
167  public $multiprices_min = array();
168  public $multiprices_min_ttc = array();
169  public $multiprices_tva_tx = array();
170  public $multiprices_recuperableonly = array();
171 
174  public $prices_by_qty = array();
175  public $prices_by_qty_id = array();
176  public $prices_by_qty_list = array();
177 
180 
182  public $tva_tx;
183 
185  public $tva_npr = 0;
186 
189  public $localtax2_tx;
190  public $localtax1_type;
191  public $localtax2_type;
192 
198  public $stock_reel = 0;
199 
205  public $stock_theorique;
206 
212  public $cost_price;
213 
215  public $pmp;
216 
222  public $seuil_stock_alerte = 0;
223 
227  public $desiredstock = 0;
228 
229  /*
230  * Service expiration
231  */
232  public $duration_value;
233 
238 
244  public $status = 0;
245 
251  public $status_buy = 0;
252 
258  public $finished;
259 
265  public $status_batch = 0;
266 
272  public $customcode;
273 
279  public $url;
280 
282  public $weight;
283  public $weight_units; // scale -3, 0, 3, 6
284  public $length;
285  public $length_units; // scale -3, 0, 3, 6
286  public $width;
287  public $width_units; // scale -3, 0, 3, 6
288  public $height;
289  public $height_units; // scale -3, 0, 3, 6
290  public $surface;
291  public $surface_units; // scale -3, 0, 3, 6
292  public $volume;
293  public $volume_units; // scale -3, 0, 3, 6
294 
295  public $net_measure;
296  public $net_measure_units; // scale -3, 0, 3, 6
297 
298  public $accountancy_code_sell;
299  public $accountancy_code_sell_intra;
300  public $accountancy_code_sell_export;
301  public $accountancy_code_buy;
302  public $accountancy_code_buy_intra;
303  public $accountancy_code_buy_export;
304 
310  public $barcode;
311 
317  public $barcode_type;
318 
324  public $barcode_type_code;
325 
331  public $barcodes_extra = array();
332 
333  public $stats_propale = array();
334  public $stats_commande = array();
335  public $stats_contrat = array();
336  public $stats_facture = array();
337  public $stats_commande_fournisseur = array();
338  public $stats_reception = array();
339  public $stats_mrptoconsume = array();
340  public $stats_mrptoproduce = array();
341 
342  public $multilangs = array();
343 
345  public $imgWidth;
346  public $imgHeight;
347 
351  public $date_creation;
352 
356  public $date_modification;
357 
360 
363 
364  public $nbphoto = 0;
365 
367  public $stock_warehouse = array();
368 
369  public $oldcopy;
370 
371  public $fk_default_warehouse;
375  public $fk_price_expression;
376 
377  /* To store supplier price found */
378  public $fourn_pu;
379  public $fourn_price_base_type;
380  public $fourn_socid;
381 
386  public $ref_fourn;
387  public $ref_supplier;
388 
394  public $fk_unit;
395 
401  public $price_autogen = 0;
402 
408  public $supplierprices;
409 
413  public $fields = array(
414  'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'index'=>1, 'position'=>1, 'comment'=>'Id'),
415  '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'),
416  'barcode' =>array('type'=>'varchar(255)', 'label'=>'Barcode', 'enabled'=>'!empty($conf->barcode->enabled)', 'visible'=>-1, 'showoncombobox'=>1),
417  'entity' =>array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'default'=>1, 'notnull'=>1, 'index'=>1, 'position'=>20),
418  'label' =>array('type'=>'varchar(255)', 'label'=>'Label', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>1),
419  'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>61),
420  'note' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>62),
421  'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>500),
422  'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>501),
423  //'date_valid' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>502),
424  'fk_user_author'=>array('type'=>'integer', 'label'=>'UserAuthor', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>510, 'foreignkey'=>'llx_user.rowid'),
425  'fk_user_modif' =>array('type'=>'integer', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>511),
426  //'fk_user_valid' =>array('type'=>'integer', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>512),
427  'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'index'=>0, 'position'=>1000),
428  //'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')),
429  //'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')),
430  );
431 
435  const TYPE_PRODUCT = 0;
439  const TYPE_SERVICE = 1;
443  const TYPE_ASSEMBLYKIT = 2;
447  const TYPE_STOCKKIT = 3;
448 
449 
455  public function __construct($db)
456  {
457  $this->db = $db;
458  $this->canvas = '';
459  }
460 
466  public function check()
467  {
468  $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
469 
470  $err = 0;
471  if (dol_strlen(trim($this->ref)) == 0) {
472  $err++;
473  }
474 
475  if (dol_strlen(trim($this->label)) == 0) {
476  $err++;
477  }
478 
479  if ($err > 0) {
480  return 0;
481  } else {
482  return 1;
483  }
484  }
485 
493  public function create($user, $notrigger = 0)
494  {
495  global $conf, $langs;
496 
497  $error = 0;
498 
499  // Clean parameters
500  $this->ref = dol_sanitizeFileName(dol_string_nospecial(trim($this->ref)));
501  $this->label = trim($this->label);
502  $this->price_ttc = price2num($this->price_ttc);
503  $this->price = price2num($this->price);
504  $this->price_min_ttc = price2num($this->price_min_ttc);
505  $this->price_min = price2num($this->price_min);
506  if (empty($this->tva_tx)) {
507  $this->tva_tx = 0;
508  }
509  if (empty($this->tva_npr)) {
510  $this->tva_npr = 0;
511  }
512  //Local taxes
513  if (empty($this->localtax1_tx)) {
514  $this->localtax1_tx = 0;
515  }
516  if (empty($this->localtax2_tx)) {
517  $this->localtax2_tx = 0;
518  }
519  if (empty($this->localtax1_type)) {
520  $this->localtax1_type = '0';
521  }
522  if (empty($this->localtax2_type)) {
523  $this->localtax2_type = '0';
524  }
525  if (empty($this->price)) {
526  $this->price = 0;
527  }
528  if (empty($this->price_min)) {
529  $this->price_min = 0;
530  }
531  // Price by quantity
532  if (empty($this->price_by_qty)) {
533  $this->price_by_qty = 0;
534  }
535 
536  if (empty($this->status)) {
537  $this->status = 0;
538  }
539  if (empty($this->status_buy)) {
540  $this->status_buy = 0;
541  }
542 
543  $price_ht = 0;
544  $price_ttc = 0;
545  $price_min_ht = 0;
546  $price_min_ttc = 0;
547 
548  //
549  if ($this->price_base_type == 'TTC' && $this->price_ttc > 0) {
550  $price_ttc = price2num($this->price_ttc, 'MU');
551  $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)), 'MU');
552  }
553 
554  //
555  if ($this->price_base_type != 'TTC' && $this->price > 0) {
556  $price_ht = price2num($this->price, 'MU');
557  $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)), 'MU');
558  }
559 
560  //
561  if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC')) {
562  $price_min_ttc = price2num($this->price_min_ttc, 'MU');
563  $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)), 'MU');
564  }
565 
566  //
567  if (($this->price_min > 0) && ($this->price_base_type != 'TTC')) {
568  $price_min_ht = price2num($this->price_min, 'MU');
569  $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)), 'MU');
570  }
571 
572  $this->accountancy_code_buy = trim($this->accountancy_code_buy);
573  $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
574  $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
575  $this->accountancy_code_sell = trim($this->accountancy_code_sell);
576  $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
577  $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
578 
579  // Barcode value
580  $this->barcode = trim($this->barcode);
581 
582  // Check parameters
583  if (empty($this->label)) {
584  $this->error = 'ErrorMandatoryParametersNotProvided';
585  return -1;
586  }
587 
588  if (empty($this->ref) || $this->ref == 'auto') {
589  // Load object modCodeProduct
590  $module = (!empty($conf->global->PRODUCT_CODEPRODUCT_ADDON) ? $conf->global->PRODUCT_CODEPRODUCT_ADDON : 'mod_codeproduct_leopard');
591  if ($module != 'mod_codeproduct_leopard') // Do not load module file for leopard
592  {
593  if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
594  $module = substr($module, 0, dol_strlen($module) - 4);
595  }
596  dol_include_once('/core/modules/product/'.$module.'.php');
597  $modCodeProduct = new $module;
598  if (!empty($modCodeProduct->code_auto)) {
599  $this->ref = $modCodeProduct->getNextValue($this, $this->type);
600  }
601  unset($modCodeProduct);
602  }
603 
604  if (empty($this->ref)) {
605  $this->error = 'ProductModuleNotSetupForAutoRef';
606  return -2;
607  }
608  }
609 
610  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);
611 
612  $now = dol_now();
613 
614  $this->db->begin();
615 
616  // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
617  if ($this->barcode == -1) {
618  $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
619  }
620 
621  // Check more parameters
622  // If error, this->errors[] is filled
623  $result = $this->verify();
624 
625  if ($result >= 0) {
626  $sql = "SELECT count(*) as nb";
627  $sql .= " FROM ".MAIN_DB_PREFIX."product";
628  $sql .= " WHERE entity IN (".getEntity('product').")";
629  $sql .= " AND ref = '".$this->db->escape($this->ref)."'";
630 
631  $result = $this->db->query($sql);
632  if ($result) {
633  $obj = $this->db->fetch_object($result);
634  if ($obj->nb == 0) {
635  // Produit non deja existant
636  $sql = "INSERT INTO ".MAIN_DB_PREFIX."product (";
637  $sql .= "datec";
638  $sql .= ", entity";
639  $sql .= ", ref";
640  $sql .= ", ref_ext";
641  $sql .= ", price_min";
642  $sql .= ", price_min_ttc";
643  $sql .= ", label";
644  $sql .= ", fk_user_author";
645  $sql .= ", fk_product_type";
646  $sql .= ", price";
647  $sql .= ", price_ttc";
648  $sql .= ", price_base_type";
649  $sql .= ", tobuy";
650  $sql .= ", tosell";
651  $sql .= ", accountancy_code_buy";
652  $sql .= ", accountancy_code_buy_intra";
653  $sql .= ", accountancy_code_buy_export";
654  $sql .= ", accountancy_code_sell";
655  $sql .= ", accountancy_code_sell_intra";
656  $sql .= ", accountancy_code_sell_export";
657  $sql .= ", canvas";
658  $sql .= ", finished";
659  $sql .= ", tobatch";
660  $sql .= ", fk_unit";
661  $sql .= ") VALUES (";
662  $sql .= "'".$this->db->idate($now)."'";
663  $sql .= ", ".$conf->entity;
664  $sql .= ", '".$this->db->escape($this->ref)."'";
665  $sql .= ", ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
666  $sql .= ", ".price2num($price_min_ht);
667  $sql .= ", ".price2num($price_min_ttc);
668  $sql .= ", ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
669  $sql .= ", ".$user->id;
670  $sql .= ", ".$this->type;
671  $sql .= ", ".price2num($price_ht);
672  $sql .= ", ".price2num($price_ttc);
673  $sql .= ", '".$this->db->escape($this->price_base_type)."'";
674  $sql .= ", ".$this->status;
675  $sql .= ", ".$this->status_buy;
676  $sql .= ", '".$this->db->escape($this->accountancy_code_buy)."'";
677  $sql .= ", '".$this->db->escape($this->accountancy_code_buy_intra)."'";
678  $sql .= ", '".$this->db->escape($this->accountancy_code_buy_export)."'";
679  $sql .= ", '".$this->db->escape($this->accountancy_code_sell)."'";
680  $sql .= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
681  $sql .= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
682  $sql .= ", '".$this->db->escape($this->canvas)."'";
683  $sql .= ", ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'null' : (int) $this->finished);
684  $sql .= ", ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : $this->status_batch);
685  $sql .= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
686  $sql .= ")";
687 
688  dol_syslog(get_class($this)."::Create", LOG_DEBUG);
689  $result = $this->db->query($sql);
690  if ($result) {
691  $id = $this->db->last_insert_id(MAIN_DB_PREFIX."product");
692 
693  if ($id > 0) {
694  $this->id = $id;
695  $this->price = $price_ht;
696  $this->price_ttc = $price_ttc;
697  $this->price_min = $price_min_ht;
698  $this->price_min_ttc = $price_min_ttc;
699 
700  $result = $this->_log_price($user);
701  if ($result > 0) {
702  if ($this->update($id, $user, true, 'add') <= 0) {
703  $error++;
704  }
705  } else {
706  $error++;
707  $this->error = $this->db->lasterror();
708  }
709  } else {
710  $error++;
711  $this->error = 'ErrorFailedToGetInsertedId';
712  }
713  } else {
714  $error++;
715  $this->error = $this->db->lasterror();
716  }
717  } else {
718  // Product already exists with this ref
719  $langs->load("products");
720  $error++;
721  $this->error = "ErrorProductAlreadyExists";
722  }
723  } else {
724  $error++;
725  $this->error = $this->db->lasterror();
726  }
727 
728  if (!$error && !$notrigger) {
729  // Call trigger
730  $result = $this->call_trigger('PRODUCT_CREATE', $user);
731  if ($result < 0) { $error++;
732  }
733  // End call triggers
734  }
735 
736  if (!$error) {
737  $this->db->commit();
738  return $this->id;
739  } else {
740  $this->db->rollback();
741  return -$error;
742  }
743  } else {
744  $this->db->rollback();
745  dol_syslog(get_class($this)."::Create fails verify ".join(',', $this->errors), LOG_WARNING);
746  return -3;
747  }
748  }
749 
750 
757  public function verify()
758  {
759  $this->errors = array();
760 
761  $result = 0;
762  $this->ref = trim($this->ref);
763 
764  if (!$this->ref) {
765  $this->errors[] = 'ErrorBadRef';
766  $result = -2;
767  }
768 
769  $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
770  if ($rescode) {
771  if ($rescode == -1) {
772  $this->errors[] = 'ErrorBadBarCodeSyntax';
773  } elseif ($rescode == -2) {
774  $this->errors[] = 'ErrorBarCodeRequired';
775  } elseif ($rescode == -3) {
776  // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
777  $this->errors[] = 'ErrorBarCodeAlreadyUsed';
778  }
779 
780  $result = -3;
781  }
782 
783  return $result;
784  }
785 
786  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
797  public function check_barcode($valuetotest, $typefortest)
798  {
799  // phpcs:enable
800  global $conf;
801  if (!empty($conf->barcode->enabled) && !empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
802  $module = strtolower($conf->global->BARCODE_PRODUCT_ADDON_NUM);
803 
804  $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
805  foreach ($dirsociete as $dirroot)
806  {
807  $res = dol_include_once($dirroot.$module.'.php');
808  if ($res) { break;
809  }
810  }
811 
812  $mod = new $module();
813 
814  dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
815  $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
816  return $result;
817  } else {
818  return 0;
819  }
820  }
821 
833  public function update($id, $user, $notrigger = false, $action = 'update', $updatetype = false)
834  {
835  global $langs, $conf, $hookmanager;
836 
837  $error = 0;
838 
839  // Check parameters
840  if (!$this->label) {
841  $this->label = 'MISSING LABEL';
842  }
843 
844  // Clean parameters
845  $this->ref = dol_string_nospecial(trim($this->ref));
846  $this->label = trim($this->label);
847  $this->description = trim($this->description);
848  $this->note = (isset($this->note) ? trim($this->note) : null);
849  $this->net_measure = price2num($this->net_measure);
850  $this->net_measure_units = trim($this->net_measure_units);
851  $this->weight = price2num($this->weight);
852  $this->weight_units = trim($this->weight_units);
853  $this->length = price2num($this->length);
854  $this->length_units = trim($this->length_units);
855  $this->width = price2num($this->width);
856  $this->width_units = trim($this->width_units);
857  $this->height = price2num($this->height);
858  $this->height_units = trim($this->height_units);
859  $this->surface = price2num($this->surface);
860  $this->surface_units = trim($this->surface_units);
861  $this->volume = price2num($this->volume);
862  $this->volume_units = trim($this->volume_units);
863 
864  // set unit not defined
865  if (is_numeric($this->length_units)) {
866  $this->width_units = $this->length_units; // Not used yet
867  }
868  if (is_numeric($this->length_units)) {
869  $this->height_units = $this->length_units; // Not used yet
870  }
871 
872  // Automated compute surface and volume if not filled
873  if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
874  $this->surface = $this->length * $this->width;
875  $this->surface_units = measuring_units_squared($this->length_units);
876  }
877  if (empty($this->volume) && !empty($this->surface) && !empty($this->height) && $this->length_units == $this->height_units) {
878  $this->volume = $this->surface * $this->height;
879  $this->volume_units = measuring_units_cubed($this->height_units);
880  }
881 
882  if (empty($this->tva_tx)) {
883  $this->tva_tx = 0;
884  }
885  if (empty($this->tva_npr)) {
886  $this->tva_npr = 0;
887  }
888  if (empty($this->localtax1_tx)) {
889  $this->localtax1_tx = 0;
890  }
891  if (empty($this->localtax2_tx)) {
892  $this->localtax2_tx = 0;
893  }
894  if (empty($this->localtax1_type)) {
895  $this->localtax1_type = '0';
896  }
897  if (empty($this->localtax2_type)) {
898  $this->localtax2_type = '0';
899  }
900  if (empty($this->status)) {
901  $this->status = 0;
902  }
903  if (empty($this->status_buy)) {
904  $this->status_buy = 0;
905  }
906 
907  if (empty($this->country_id)) {
908  $this->country_id = 0;
909  }
910 
911  if (empty($this->state_id)) {
912  $this->state_id = 0;
913  }
914 
915  // Barcode value
916  $this->barcode = trim($this->barcode);
917 
918  $this->accountancy_code_buy = trim($this->accountancy_code_buy);
919  $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
920  $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
921  $this->accountancy_code_sell = trim($this->accountancy_code_sell);
922  $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
923  $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
924 
925 
926 
927  $this->db->begin();
928 
929  $result = 0;
930  // Check name is required and codes are ok or unique. If error, this->errors[] is filled
931  if ($action != 'add') {
932  $result = $this->verify(); // We don't check when update called during a create because verify was already done
933  } else {
934  // we can continue
935  $result = 0;
936  }
937 
938  if ($result >= 0) {
939  if (empty($this->oldcopy)) {
940  $org = new self($this->db);
941  $org->fetch($this->id);
942  $this->oldcopy = $org;
943  }
944 
945  // Test if batch management is activated on existing product
946  // If yes, we create missing entries into product_batch
947  if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
948  //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
949  $valueforundefinedlot = '000000';
950  if (!empty($conf->global->STOCK_DEFAULT_BATCH)) $valueforundefinedlot = $conf->global->STOCK_DEFAULT_BATCH;
951 
952  dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
953 
954  $this->load_stock();
955  foreach ($this->stock_warehouse as $idW => $ObjW) // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
956  {
957  $qty_batch = 0;
958  foreach ($ObjW->detail_batch as $detail) // Each lines of detail in product_batch of the current $ObjW = product_stock
959  {
960  if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
961  // We discard this line, we will create it later
962  $sqlclean = "DELETE FROM ".MAIN_DB_PREFIX."product_batch WHERE batch in('Undefined', '".$this->db->escape($valueforundefinedlot)."') AND fk_product_stock = ".$ObjW->id;
963  $result = $this->db->query($sqlclean);
964  if (!$result) {
965  dol_print_error($this->db);
966  exit;
967  }
968  continue;
969  }
970 
971  $qty_batch += $detail->qty;
972  }
973  // Quantities in batch details are not same as stock quantity,
974  // so we add a default batch record to complete and get same qty in parent and child table
975  if ($ObjW->real <> $qty_batch) {
976  $ObjBatch = new Productbatch($this->db);
977  $ObjBatch->batch = $valueforundefinedlot;
978  $ObjBatch->qty = ($ObjW->real - $qty_batch);
979  $ObjBatch->fk_product_stock = $ObjW->id;
980 
981  if ($ObjBatch->create($user, 1) < 0) {
982  $error++;
983  $this->errors = $ObjBatch->errors;
984  }
985  }
986  }
987  }
988 
989  // For automatic creation
990  if ($this->barcode == -1) { $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
991  }
992 
993  $sql = "UPDATE ".MAIN_DB_PREFIX."product";
994  $sql .= " SET label = '".$this->db->escape($this->label)."'";
995 
996  if ($updatetype && ($this->isProduct() || $this->isService())) {
997  $sql .= ", fk_product_type = ".$this->type;
998  }
999 
1000  $sql .= ", ref = '".$this->db->escape($this->ref)."'";
1001  $sql .= ", ref_ext = ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1002  $sql .= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
1003  $sql .= ", tva_tx = ".$this->tva_tx;
1004  $sql .= ", recuperableonly = ".$this->tva_npr;
1005  $sql .= ", localtax1_tx = ".$this->localtax1_tx;
1006  $sql .= ", localtax2_tx = ".$this->localtax2_tx;
1007  $sql .= ", localtax1_type = ".($this->localtax1_type != '' ? "'".$this->db->escape($this->localtax1_type)."'" : "'0'");
1008  $sql .= ", localtax2_type = ".($this->localtax2_type != '' ? "'".$this->db->escape($this->localtax2_type)."'" : "'0'");
1009 
1010  $sql .= ", barcode = ".(empty($this->barcode) ? "null" : "'".$this->db->escape($this->barcode)."'");
1011  $sql .= ", fk_barcode_type = ".(empty($this->barcode_type) ? "null" : $this->db->escape($this->barcode_type));
1012 
1013  $sql .= ", tosell = ".(int) $this->status;
1014  $sql .= ", tobuy = ".(int) $this->status_buy;
1015  $sql .= ", tobatch = ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
1016  $sql .= ", finished = ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? "null" : (int) $this->finished);
1017  $sql .= ", net_measure = ".($this->net_measure != '' ? "'".$this->db->escape($this->net_measure)."'" : 'null');
1018  $sql .= ", net_measure_units = ".($this->net_measure_units != '' ? "'".$this->db->escape($this->net_measure_units)."'" : 'null');
1019  $sql .= ", weight = ".($this->weight != '' ? "'".$this->db->escape($this->weight)."'" : 'null');
1020  $sql .= ", weight_units = ".($this->weight_units != '' ? "'".$this->db->escape($this->weight_units)."'" : 'null');
1021  $sql .= ", length = ".($this->length != '' ? "'".$this->db->escape($this->length)."'" : 'null');
1022  $sql .= ", length_units = ".($this->length_units != '' ? "'".$this->db->escape($this->length_units)."'" : 'null');
1023  $sql .= ", width= ".($this->width != '' ? "'".$this->db->escape($this->width)."'" : 'null');
1024  $sql .= ", width_units = ".($this->width_units != '' ? "'".$this->db->escape($this->width_units)."'" : 'null');
1025  $sql .= ", height = ".($this->height != '' ? "'".$this->db->escape($this->height)."'" : 'null');
1026  $sql .= ", height_units = ".($this->height_units != '' ? "'".$this->db->escape($this->height_units)."'" : 'null');
1027  $sql .= ", surface = ".($this->surface != '' ? "'".$this->db->escape($this->surface)."'" : 'null');
1028  $sql .= ", surface_units = ".($this->surface_units != '' ? "'".$this->db->escape($this->surface_units)."'" : 'null');
1029  $sql .= ", volume = ".($this->volume != '' ? "'".$this->db->escape($this->volume)."'" : 'null');
1030  $sql .= ", volume_units = ".($this->volume_units != '' ? "'".$this->db->escape($this->volume_units)."'" : 'null');
1031  $sql .= ", fk_default_warehouse = ".($this->fk_default_warehouse > 0 ? $this->db->escape($this->fk_default_warehouse) : 'null');
1032  $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (float) $this->seuil_stock_alerte : 'null');
1033  $sql .= ", description = '".$this->db->escape($this->description)."'";
1034  $sql .= ", url = ".($this->url ? "'".$this->db->escape($this->url)."'" : 'null');
1035  $sql .= ", customcode = '".$this->db->escape($this->customcode)."'";
1036  $sql .= ", fk_country = ".($this->country_id > 0 ? (int) $this->country_id : 'null');
1037  $sql .= ", fk_state = ".($this->state_id > 0 ? (int) $this->state_id : 'null');
1038  $sql .= ", note = ".(isset($this->note) ? "'".$this->db->escape($this->note)."'" : 'null');
1039  $sql .= ", duration = '".$this->db->escape($this->duration_value.$this->duration_unit)."'";
1040  $sql .= ", accountancy_code_buy = '".$this->db->escape($this->accountancy_code_buy)."'";
1041  $sql .= ", accountancy_code_buy_intra = '".$this->db->escape($this->accountancy_code_buy_intra)."'";
1042  $sql .= ", accountancy_code_buy_export = '".$this->db->escape($this->accountancy_code_buy_export)."'";
1043  $sql .= ", accountancy_code_sell= '".$this->db->escape($this->accountancy_code_sell)."'";
1044  $sql .= ", accountancy_code_sell_intra= '".$this->db->escape($this->accountancy_code_sell_intra)."'";
1045  $sql .= ", accountancy_code_sell_export= '".$this->db->escape($this->accountancy_code_sell_export)."'";
1046  $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (float) $this->desiredstock : "null");
1047  $sql .= ", cost_price = ".($this->cost_price != '' ? $this->db->escape($this->cost_price) : 'null');
1048  $sql .= ", fk_unit= ".(!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
1049  $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1);
1050  $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
1051  $sql .= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL');
1052 
1053  // stock field is not here because it is a denormalized value from product_stock.
1054  $sql .= " WHERE rowid = ".$id;
1055 
1056  dol_syslog(get_class($this)."::update", LOG_DEBUG);
1057 
1058  $resql = $this->db->query($sql);
1059  if ($resql) {
1060  $this->id = $id;
1061 
1062  // Multilangs
1063  if (!empty($conf->global->MAIN_MULTILANGS)) {
1064  if ($this->setMultiLangs($user) < 0) {
1065  $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1066  return -2;
1067  }
1068  }
1069 
1070  $action = 'update';
1071 
1072  // Actions on extra fields
1073  if (!$error) {
1074  $result = $this->insertExtraFields();
1075  if ($result < 0) {
1076  $error++;
1077  }
1078  }
1079 
1080  if (!$error && !$notrigger) {
1081  // Call trigger
1082  $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1083  if ($result < 0) {
1084  $error++;
1085  }
1086  // End call triggers
1087  }
1088 
1089  if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
1090  // We remove directory
1091  if ($conf->product->dir_output) {
1092  $olddir = $conf->product->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
1093  $newdir = $conf->product->dir_output."/".dol_sanitizeFileName($this->ref);
1094  if (file_exists($olddir)) {
1095  //include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1096  //$res = dol_move($olddir, $newdir);
1097  // do not use dol_move with directory
1098  $res = @rename($olddir, $newdir);
1099  if (!$res) {
1100  $langs->load("errors");
1101  $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
1102  $error++;
1103  }
1104  }
1105  }
1106  }
1107 
1108  if (!$error) {
1109  if (!empty($conf->variants->enabled)) {
1110  include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1111 
1112  $comb = new ProductCombination($this->db);
1113 
1114  foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1115  $currcomb->updateProperties($this, $user);
1116  }
1117  }
1118 
1119  $this->db->commit();
1120  return 1;
1121  } else {
1122  $this->db->rollback();
1123  return -$error;
1124  }
1125  } else {
1126  if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1127  $langs->load("errors");
1128  if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1129  $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1130  } else {
1131  $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1132  }
1133  $this->errors[] = $this->error;
1134  $this->db->rollback();
1135  return -1;
1136  } else {
1137  $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1138  $this->errors[] = $this->error;
1139  $this->db->rollback();
1140  return -2;
1141  }
1142  }
1143  } else {
1144  $this->db->rollback();
1145  dol_syslog(get_class($this)."::Update fails verify ".join(',', $this->errors), LOG_WARNING);
1146  return -3;
1147  }
1148  }
1149 
1157  public function delete(User $user, $notrigger = 0)
1158  {
1159  global $conf, $langs;
1160  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1161 
1162  $error = 0;
1163 
1164  // Check parameters
1165  if (empty($this->id)) {
1166  $this->error = "Object must be fetched before calling delete";
1167  return -1;
1168  }
1169  if (($this->type == Product::TYPE_PRODUCT && empty($user->rights->produit->supprimer)) || ($this->type == Product::TYPE_SERVICE && empty($user->rights->service->supprimer))) {
1170  $this->error = "ErrorForbidden";
1171  return 0;
1172  }
1173 
1174  $objectisused = $this->isObjectUsed($this->id);
1175  if (empty($objectisused)) {
1176  $this->db->begin();
1177 
1178  if (!$error && empty($notrigger)) {
1179  // Call trigger
1180  $result = $this->call_trigger('PRODUCT_DELETE', $user);
1181  if ($result < 0) {
1182  $error++;
1183  }
1184  // End call triggers
1185  }
1186 
1187  // Delete from product_batch on product delete
1188  if (!$error) {
1189  $sql = "DELETE FROM ".MAIN_DB_PREFIX.'product_batch';
1190  $sql .= " WHERE fk_product_stock IN (";
1191  $sql .= "SELECT rowid FROM ".MAIN_DB_PREFIX.'product_stock';
1192  $sql .= " WHERE fk_product = ".(int) $this->id.")";
1193 
1194  $result = $this->db->query($sql);
1195  if (!$result) {
1196  $error++;
1197  $this->errors[] = $this->db->lasterror();
1198  }
1199  }
1200 
1201  // Delete all child tables
1202  if (!$error) {
1203  $elements = array('product_fournisseur_price', 'product_price', 'product_lang', 'categorie_product', 'product_stock', 'product_customer_price', 'product_lot'); // product_batch is done before
1204  foreach ($elements as $table)
1205  {
1206  if (!$error) {
1207  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$table;
1208  $sql .= " WHERE fk_product = ".(int) $this->id;
1209 
1210  $result = $this->db->query($sql);
1211  if (!$result) {
1212  $error++;
1213  $this->errors[] = $this->db->lasterror();
1214  }
1215  }
1216  }
1217  }
1218 
1219  if (!$error) {
1220  include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1221  include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1222 
1223  //If it is a parent product, then we remove the association with child products
1224  $prodcomb = new ProductCombination($this->db);
1225 
1226  if ($prodcomb->deleteByFkProductParent($user, $this->id) < 0) {
1227  $error++;
1228  $this->errors[] = 'Error deleting combinations';
1229  }
1230 
1231  //We also check if it is a child product
1232  if (!$error && ($prodcomb->fetchByFkProductChild($this->id) > 0) && ($prodcomb->delete($user) < 0)) {
1233  $error++;
1234  $this->errors[] = 'Error deleting child combination';
1235  }
1236  }
1237 
1238  // Delete from product_association
1239  if (!$error) {
1240  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_association";
1241  $sql .= " WHERE fk_product_pere = ".(int) $this->id." OR fk_product_fils = ".(int) $this->id;
1242 
1243  $result = $this->db->query($sql);
1244  if (!$result) {
1245  $error++;
1246  $this->errors[] = $this->db->lasterror();
1247  }
1248  }
1249 
1250  // Remove extrafields
1251  if (!$error) {
1252  $result = $this->deleteExtraFields();
1253  if ($result < 0) {
1254  $error++;
1255  dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1256  }
1257  }
1258 
1259  // Delete product
1260  if (!$error) {
1261  $sqlz = "DELETE FROM ".MAIN_DB_PREFIX."product";
1262  $sqlz .= " WHERE rowid = ".(int) $this->id;
1263 
1264  $resultz = $this->db->query($sqlz);
1265  if (!$resultz) {
1266  $error++;
1267  $this->errors[] = $this->db->lasterror();
1268  }
1269  }
1270 
1271  if (!$error) {
1272  // We remove directory
1273  $ref = dol_sanitizeFileName($this->ref);
1274  if ($conf->product->dir_output) {
1275  $dir = $conf->product->dir_output."/".$ref;
1276  if (file_exists($dir)) {
1277  $res = @dol_delete_dir_recursive($dir);
1278  if (!$res) {
1279  $this->errors[] = 'ErrorFailToDeleteDir';
1280  $error++;
1281  }
1282  }
1283  }
1284  }
1285 
1286  if (!$error) {
1287  $this->db->commit();
1288  return 1;
1289  } else {
1290  foreach ($this->errors as $errmsg)
1291  {
1292  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1293  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1294  }
1295  $this->db->rollback();
1296  return -$error;
1297  }
1298  } else {
1299  $this->error = "ErrorRecordIsUsedCantDelete";
1300  return 0;
1301  }
1302  }
1303 
1310  public function setMultiLangs($user)
1311  {
1312  global $conf, $langs;
1313 
1314  $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1315  $current_lang = $langs->getDefaultLang();
1316 
1317  foreach ($langs_available as $key => $value)
1318  {
1319  if ($key == $current_lang) {
1320  $sql = "SELECT rowid";
1321  $sql .= " FROM ".MAIN_DB_PREFIX."product_lang";
1322  $sql .= " WHERE fk_product=".$this->id;
1323  $sql .= " AND lang='".$this->db->escape($key)."'";
1324 
1325  $result = $this->db->query($sql);
1326 
1327  if ($this->db->num_rows($result)) // if there is already a description line for this language
1328  {
1329  $sql2 = "UPDATE ".MAIN_DB_PREFIX."product_lang";
1330  $sql2 .= " SET ";
1331  $sql2 .= " label='".$this->db->escape($this->label)."',";
1332  $sql2 .= " description='".$this->db->escape($this->description)."'";
1333  if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2 .= ", note='".$this->db->escape($this->other)."'";
1334  }
1335  $sql2 .= " WHERE fk_product=".$this->id." AND lang='".$this->db->escape($key)."'";
1336  } else {
1337  $sql2 = "INSERT INTO ".MAIN_DB_PREFIX."product_lang (fk_product, lang, label, description";
1338  if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2 .= ", note";
1339  }
1340  $sql2 .= ")";
1341  $sql2 .= " VALUES(".$this->id.",'".$this->db->escape($key)."','".$this->db->escape($this->label)."',";
1342  $sql2 .= " '".$this->db->escape($this->description)."'";
1343  if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1344  $sql2 .= ", '".$this->db->escape($this->other)."'";
1345  }
1346  $sql2 .= ")";
1347  }
1348  dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
1349  if (!$this->db->query($sql2)) {
1350  $this->error = $this->db->lasterror();
1351  return -1;
1352  }
1353  } elseif (isset($this->multilangs[$key])) {
1354  $sql = "SELECT rowid";
1355  $sql .= " FROM ".MAIN_DB_PREFIX."product_lang";
1356  $sql .= " WHERE fk_product=".$this->id;
1357  $sql .= " AND lang='".$this->db->escape($key)."'";
1358 
1359  $result = $this->db->query($sql);
1360 
1361  if ($this->db->num_rows($result)) // if there is already a description line for this language
1362  {
1363  $sql2 = "UPDATE ".MAIN_DB_PREFIX."product_lang";
1364  $sql2 .= " SET ";
1365  $sql2 .= " label='".$this->db->escape($this->multilangs["$key"]["label"])."',";
1366  $sql2 .= " description='".$this->db->escape($this->multilangs["$key"]["description"])."'";
1367  if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1368  $sql2 .= ", note='".$this->db->escape($this->multilangs["$key"]["other"])."'";
1369  }
1370  $sql2 .= " WHERE fk_product=".$this->id." AND lang='".$this->db->escape($key)."'";
1371  } else {
1372  $sql2 = "INSERT INTO ".MAIN_DB_PREFIX."product_lang (fk_product, lang, label, description";
1373  if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2 .= ", note";
1374  }
1375  $sql2 .= ")";
1376  $sql2 .= " VALUES(".$this->id.",'".$this->db->escape($key)."','".$this->db->escape($this->multilangs["$key"]["label"])."',";
1377  $sql2 .= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1378  if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1379  $sql2 .= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1380  }
1381  $sql2 .= ")";
1382  }
1383 
1384  // We do not save if main fields are empty
1385  if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
1386  if (!$this->db->query($sql2)) {
1387  $this->error = $this->db->lasterror();
1388  return -1;
1389  }
1390  }
1391  } else {
1392  // language is not current language and we didn't provide a multilang description for this language
1393  }
1394  }
1395 
1396  // Call trigger
1397  $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
1398  if ($result < 0) {
1399  $this->error = $this->db->lasterror();
1400  return -1;
1401  }
1402  // End call triggers
1403 
1404  return 1;
1405  }
1406 
1415  public function delMultiLangs($langtodelete, $user)
1416  {
1417  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_lang";
1418  $sql .= " WHERE fk_product=".$this->id." AND lang='".$this->db->escape($langtodelete)."'";
1419 
1420  dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
1421  $result = $this->db->query($sql);
1422  if ($result) {
1423  // Call trigger
1424  $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
1425  if ($result < 0) {
1426  $this->error = $this->db->lasterror();
1427  dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1428  return -1;
1429  }
1430  // End call triggers
1431  return 1;
1432  } else {
1433  $this->error = $this->db->lasterror();
1434  dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1435  return -1;
1436  }
1437  }
1438 
1447  public function setAccountancyCode($type, $value)
1448  {
1449  global $user, $langs, $conf;
1450 
1451  $error = 0;
1452 
1453  $this->db->begin();
1454 
1455  if ($type == 'buy') {
1456  $field = 'accountancy_code_buy';
1457  } elseif ($type == 'buy_intra') {
1458  $field = 'accountancy_code_buy_intra';
1459  } elseif ($type == 'buy_export') {
1460  $field = 'accountancy_code_buy_export';
1461  } elseif ($type == 'sell') {
1462  $field = 'accountancy_code_sell';
1463  } elseif ($type == 'sell_intra') {
1464  $field = 'accountancy_code_sell_intra';
1465  } elseif ($type == 'sell_export') {
1466  $field = 'accountancy_code_sell_export';
1467  } else {
1468  return -1;
1469  }
1470 
1471  $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET ";
1472  $sql .= "$field = '".$this->db->escape($value)."'";
1473  $sql .= " WHERE rowid = ".$this->id;
1474 
1475  dol_syslog(__METHOD__." sql=".$sql, LOG_DEBUG);
1476  $resql = $this->db->query($sql);
1477 
1478  if ($resql) {
1479  // Call trigger
1480  $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1481  if ($result < 0) $error++;
1482  // End call triggers
1483 
1484  if ($error) {
1485  $this->db->rollback();
1486  return -1;
1487  }
1488 
1489  $this->$field = $value;
1490 
1491  $this->db->commit();
1492  return 1;
1493  } else {
1494  $this->error = $this->db->lasterror();
1495  $this->db->rollback();
1496  return -1;
1497  }
1498  }
1499 
1505  public function getMultiLangs()
1506  {
1507  global $langs;
1508 
1509  $current_lang = $langs->getDefaultLang();
1510 
1511  $sql = "SELECT lang, label, description, note as other";
1512  $sql .= " FROM ".MAIN_DB_PREFIX."product_lang";
1513  $sql .= " WHERE fk_product=".$this->id;
1514 
1515  $result = $this->db->query($sql);
1516  if ($result) {
1517  while ($obj = $this->db->fetch_object($result))
1518  {
1519  //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
1520  if ($obj->lang == $current_lang) // si on a les traduct. dans la langue courante on les charge en infos principales.
1521  {
1522  $this->label = $obj->label;
1523  $this->description = $obj->description;
1524  $this->other = $obj->other;
1525  }
1526  $this->multilangs["$obj->lang"]["label"] = $obj->label;
1527  $this->multilangs["$obj->lang"]["description"] = $obj->description;
1528  $this->multilangs["$obj->lang"]["other"] = $obj->other;
1529  }
1530  return 1;
1531  } else {
1532  $this->error = "Error: ".$this->db->lasterror()." - ".$sql;
1533  return -1;
1534  }
1535  }
1536 
1537 
1538 
1539  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1547  private function _log_price($user, $level = 0)
1548  {
1549  // phpcs:enable
1550  global $conf;
1551 
1552  $now = dol_now();
1553 
1554  // Clean parameters
1555  if (empty($this->price_by_qty)) {
1556  $this->price_by_qty = 0;
1557  }
1558 
1559  // Add new price
1560  $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_price(price_level,date_price, fk_product, fk_user_author, price, price_ttc, price_base_type,tosell, tva_tx, default_vat_code, recuperableonly,";
1561  $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
1562  $sql .= " VALUES(".($level ? $level : 1).", '".$this->db->idate($now)."',".$this->id.",".$user->id.",".$this->price.",".$this->price_ttc.",'".$this->db->escape($this->price_base_type)."',".$this->status.",".$this->tva_tx.", ".($this->default_vat_code ? ("'".$this->db->escape($this->default_vat_code)."'") : "null").",".$this->tva_npr.",";
1563  $sql .= " ".$this->localtax1_tx.", ".$this->localtax2_tx.", '".$this->db->escape($this->localtax1_type)."', '".$this->db->escape($this->localtax2_type)."', ".$this->price_min.",".$this->price_min_ttc.",".$this->price_by_qty.",".$conf->entity.",".($this->fk_price_expression > 0 ? $this->fk_price_expression : 'null');
1564  $sql .= ")";
1565 
1566  dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
1567  $resql = $this->db->query($sql);
1568  if (!$resql) {
1569  $this->error = $this->db->lasterror();
1570  dol_print_error($this->db);
1571  return -1;
1572  } else {
1573  return 1;
1574  }
1575  }
1576 
1577 
1578  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1586  public function log_price_delete($user, $rowid)
1587  {
1588  // phpcs:enable
1589  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_price_by_qty";
1590  $sql .= " WHERE fk_product_price=".$rowid;
1591  $resql = $this->db->query($sql);
1592 
1593  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_price";
1594  $sql .= " WHERE rowid=".$rowid;
1595  $resql = $this->db->query($sql);
1596  if ($resql) {
1597  return 1;
1598  } else {
1599  $this->error = $this->db->lasterror();
1600  return -1;
1601  }
1602  }
1603 
1604 
1614  public function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp = 0)
1615  {
1616  global $conf, $db;
1617 
1618  // Update if prices fields are defined
1619  $tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id);
1620  $tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id);
1621  if (empty($tva_tx)) $tva_npr = 0;
1622 
1623  $pu_ht = $this->price;
1624  $pu_ttc = $this->price_ttc;
1625  $price_min = $this->price_min;
1626  $price_base_type = $this->price_base_type;
1627 
1628  // If price per segment
1629  if (!empty($conf->global->PRODUIT_MULTIPRICES) && !empty($thirdparty_buyer->price_level)) {
1630  $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
1631  $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
1632  $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
1633  $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
1634  if (!empty($conf->global->PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL)) // using this option is a bug. kept for backward compatibility
1635  {
1636  if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
1637  if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
1638  if (empty($tva_tx)) $tva_npr = 0;
1639  }
1640  } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
1641  // If price per customer
1642  require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
1643 
1644  $prodcustprice = new Productcustomerprice($this->db);
1645 
1646  $filter = array('t.fk_product' => $this->id, 't.fk_soc' => $thirdparty_buyer->id);
1647 
1648  $result = $prodcustprice->fetch_all('', '', 0, 0, $filter);
1649  if ($result) {
1650  if (count($prodcustprice->lines) > 0) {
1651  $pu_ht = price($prodcustprice->lines[0]->price);
1652  $price_min = price($prodcustprice->lines[0]->price_min);
1653  $pu_ttc = price($prodcustprice->lines[0]->price_ttc);
1654  $price_base_type = $prodcustprice->lines[0]->price_base_type;
1655  $tva_tx = $prodcustprice->lines[0]->tva_tx;
1656  if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\(.*\)/', $tva_tx)) $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
1657  $tva_npr = $prodcustprice->lines[0]->recuperableonly;
1658  if (empty($tva_tx)) $tva_npr = 0;
1659  }
1660  }
1661  } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY)) {
1662  // If price per quantity
1663  if ($this->prices_by_qty[0]) {
1664  // yes, this product has some prices per quantity
1665  // Search price into product_price_by_qty from $this->id
1666  foreach ($this->prices_by_qty_list[0] as $priceforthequantityarray) {
1667  if ($priceforthequantityarray['rowid'] != $pqp) continue;
1668  // We found the price
1669  if ($priceforthequantityarray['price_base_type'] == 'HT')
1670  {
1671  $pu_ht = $priceforthequantityarray['unitprice'];
1672  } else {
1673  $pu_ttc = $priceforthequantityarray['unitprice'];
1674  }
1675  break;
1676  }
1677  }
1678  } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) {
1679  // If price per quantity and customer
1680  if ($this->prices_by_qty[$thirdparty_buyer->price_level]) {
1681  // yes, this product has some prices per quantity
1682  // Search price into product_price_by_qty from $this->id
1683  foreach ($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray)
1684  {
1685  if ($priceforthequantityarray['rowid'] != $pqp) continue;
1686  // We found the price
1687  if ($priceforthequantityarray['price_base_type'] == 'HT')
1688  {
1689  $pu_ht = $priceforthequantityarray['unitprice'];
1690  } else {
1691  $pu_ttc = $priceforthequantityarray['unitprice'];
1692  }
1693  break;
1694  }
1695  }
1696  }
1697 
1698  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);
1699  }
1700 
1701  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1715  public function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
1716  {
1717  // phpcs:enable
1718  global $conf;
1719  $result = 0;
1720 
1721  // We do a first seach with a select by searching with couple prodfournprice and qty only (later we will search on triplet qty/product_id/fourn_ref)
1722  $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent,";
1723  $sql .= " pfp.fk_product, pfp.ref_fourn, pfp.desc_fourn, pfp.fk_soc, pfp.tva_tx, pfp.fk_supplier_price_expression,";
1724  $sql .= " pfp.default_vat_code,";
1725  $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code";
1726  if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) $sql .= ", pfp.packaging";
1727  $sql .= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
1728  $sql .= " WHERE pfp.rowid = ".$prodfournprice;
1729  if ($qty > 0) { $sql .= " AND pfp.quantity <= ".$qty;
1730  }
1731  $sql .= " ORDER BY pfp.quantity DESC";
1732 
1733  dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
1734  $resql = $this->db->query($sql);
1735  if ($resql) {
1736  $obj = $this->db->fetch_object($resql);
1737  if ($obj && $obj->quantity > 0) // If we found a supplier prices from the id of supplier price
1738  {
1739  if (!empty($conf->dynamicprices->enabled) && !empty($obj->fk_supplier_price_expression)) {
1740  include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
1741  $prod_supplier = new ProductFournisseur($this->db);
1742  $prod_supplier->product_fourn_price_id = $obj->rowid;
1743  $prod_supplier->id = $obj->fk_product;
1744  $prod_supplier->fourn_qty = $obj->quantity;
1745  $prod_supplier->fourn_tva_tx = $obj->tva_tx;
1746  $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
1747  $priceparser = new PriceParser($this->db);
1748  $price_result = $priceparser->parseProductSupplier($prod_supplier);
1749  if ($price_result >= 0) {
1750  $obj->price = $price_result;
1751  }
1752  }
1753  $this->product_fourn_price_id = $obj->rowid;
1754  $this->buyprice = $obj->price; // deprecated
1755  $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
1756  $this->fourn_price_base_type = 'HT'; // Price base type
1757  $this->fourn_socid = $obj->fk_soc; // Company that offer this price
1758  $this->ref_fourn = $obj->ref_fourn; // deprecated
1759  $this->ref_supplier = $obj->ref_fourn; // Ref supplier
1760  $this->desc_supplier = $obj->desc_fourn; // desc supplier
1761  $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
1762  $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
1763  $this->default_vat_code = $obj->default_vat_code; // Vat code supplier
1764  $this->fourn_multicurrency_price = $obj->multicurrency_price;
1765  $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
1766  $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
1767  $this->fourn_multicurrency_id = $obj->fk_multicurrency;
1768  $this->fourn_multicurrency_code = $obj->multicurrency_code;
1769  if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) $this->packaging = $obj->packaging;
1770  $result = $obj->fk_product;
1771  return $result;
1772  } else { // If not found
1773  // 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.
1774  $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
1775  $sql .= " pfp.fk_product, pfp.ref_fourn as ref_supplier, pfp.desc_fourn as desc_supplier, pfp.tva_tx, pfp.fk_supplier_price_expression,";
1776  $sql .= " pfp.default_vat_code,";
1777  $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
1778  $sql .= " pfp.packaging";
1779  $sql .= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
1780  $sql .= " WHERE pfp.fk_product = ".$product_id;
1781  if ($fourn_ref != 'none') { $sql .= " AND pfp.ref_fourn = '".$this->db->escape($fourn_ref)."'";
1782  }
1783  if ($fk_soc > 0) { $sql .= " AND pfp.fk_soc = ".$fk_soc;
1784  }
1785  if ($qty > 0) { $sql .= " AND pfp.quantity <= ".$qty;
1786  }
1787  $sql .= " ORDER BY pfp.quantity DESC";
1788  $sql .= " LIMIT 1";
1789 
1790  dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
1791  $resql = $this->db->query($sql);
1792  if ($resql) {
1793  $obj = $this->db->fetch_object($resql);
1794  if ($obj && $obj->quantity > 0) // If found
1795  {
1796  if (!empty($conf->dynamicprices->enabled) && !empty($obj->fk_supplier_price_expression)) {
1797  include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
1798  $prod_supplier = new ProductFournisseur($this->db);
1799  $prod_supplier->product_fourn_price_id = $obj->rowid;
1800  $prod_supplier->id = $obj->fk_product;
1801  $prod_supplier->fourn_qty = $obj->quantity;
1802  $prod_supplier->fourn_tva_tx = $obj->tva_tx;
1803  $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
1804  $priceparser = new PriceParser($this->db);
1805  $price_result = $priceparser->parseProductSupplier($prod_supplier);
1806  if ($result >= 0) {
1807  $obj->price = $price_result;
1808  }
1809  }
1810  $this->product_fourn_price_id = $obj->rowid;
1811  $this->buyprice = $obj->price; // deprecated
1812  $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
1813  $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
1814  $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
1815  $this->fourn_socid = $obj->fk_soc; // Company that offer this price
1816  $this->ref_fourn = $obj->ref_supplier; // deprecated
1817  $this->ref_supplier = $obj->ref_supplier; // Ref supplier
1818  $this->desc_supplier = $obj->desc_supplier; // desc supplier
1819  $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
1820  $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
1821  $this->default_vat_code = $obj->default_vat_code; // Vat code supplier
1822  $this->fourn_multicurrency_price = $obj->multicurrency_price;
1823  $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
1824  $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
1825  $this->fourn_multicurrency_id = $obj->fk_multicurrency;
1826  $this->fourn_multicurrency_code = $obj->multicurrency_code;
1827  if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) $this->packaging = $obj->packaging;
1828  $result = $obj->fk_product;
1829  return $result;
1830  } else {
1831  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é.
1832  }
1833  } else {
1834  $this->error = $this->db->lasterror();
1835  return -3;
1836  }
1837  }
1838  } else {
1839  $this->error = $this->db->lasterror();
1840  return -2;
1841  }
1842  }
1843 
1844 
1861  public function updatePrice($newprice, $newpricebase, $user, $newvat = '', $newminprice = 0, $level = 0, $newnpr = 0, $newpbq = 0, $ignore_autogen = 0, $localtaxes_array = array(), $newdefaultvatcode = '')
1862  {
1863  global $conf, $langs;
1864 
1865  $id = $this->id;
1866 
1867  dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
1868 
1869  // Clean parameters
1870  if (empty($this->tva_tx)) {
1871  $this->tva_tx = 0;
1872  }
1873  if (empty($newnpr)) {
1874  $newnpr = 0;
1875  }
1876  if (empty($newminprice)) {
1877  $newminprice = 0;
1878  }
1879  if (empty($newminprice)) {
1880  $newminprice = 0;
1881  }
1882 
1883  // Check parameters
1884  if ($newvat == '') {
1885  $newvat = $this->tva_tx;
1886  }
1887 
1888  // If multiprices are enabled, then we check if the current product is subject to price autogeneration
1889  // Price will be modified ONLY when the first one is the one that is being modified
1890  if ((!empty($conf->global->PRODUIT_MULTIPRICES) || !empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
1891  return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
1892  }
1893 
1894  if (!empty($newminprice) && ($newminprice > $newprice)) {
1895  $this->error = 'ErrorPriceCantBeLowerThanMinPrice';
1896  return -1;
1897  }
1898 
1899  if ($newprice !== '' || $newprice === 0) {
1900  if ($newpricebase == 'TTC') {
1901  $price_ttc = price2num($newprice, 'MU');
1902  $price = price2num($newprice) / (1 + ($newvat / 100));
1903  $price = price2num($price, 'MU');
1904 
1905  if ($newminprice != '' || $newminprice == 0) {
1906  $price_min_ttc = price2num($newminprice, 'MU');
1907  $price_min = price2num($newminprice) / (1 + ($newvat / 100));
1908  $price_min = price2num($price_min, 'MU');
1909  } else {
1910  $price_min = 0;
1911  $price_min_ttc = 0;
1912  }
1913  } else {
1914  $price = price2num($newprice, 'MU');
1915  $price_ttc = ($newnpr != 1) ? price2num($newprice) * (1 + ($newvat / 100)) : $price;
1916  $price_ttc = price2num($price_ttc, 'MU');
1917 
1918  if ($newminprice !== '' || $newminprice === 0) {
1919  $price_min = price2num($newminprice, 'MU');
1920  $price_min_ttc = price2num($newminprice) * (1 + ($newvat / 100));
1921  $price_min_ttc = price2num($price_min_ttc, 'MU');
1922  //print 'X'.$newminprice.'-'.$price_min;
1923  } else {
1924  $price_min = 0;
1925  $price_min_ttc = 0;
1926  }
1927  }
1928  //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
1929 
1930  if (count($localtaxes_array) > 0) {
1931  $localtaxtype1 = $localtaxes_array['0'];
1932  $localtax1 = $localtaxes_array['1'];
1933  $localtaxtype2 = $localtaxes_array['2'];
1934  $localtax2 = $localtaxes_array['3'];
1935  } else // old method. deprecated because ot can't retrieve type
1936  {
1937  $localtaxtype1 = '0';
1938  $localtax1 = get_localtax($newvat, 1);
1939  $localtaxtype2 = '0';
1940  $localtax2 = get_localtax($newvat, 2);
1941  }
1942  if (empty($localtax1)) {
1943  $localtax1 = 0; // If = '' then = 0
1944  }
1945  if (empty($localtax2)) {
1946  $localtax2 = 0; // If = '' then = 0
1947  }
1948 
1949  $this->db->begin();
1950 
1951  // Ne pas mettre de quote sur les numeriques decimaux.
1952  // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
1953  $sql = "UPDATE ".MAIN_DB_PREFIX."product SET";
1954  $sql .= " price_base_type='".$this->db->escape($newpricebase)."',";
1955  $sql .= " price=".$price.",";
1956  $sql .= " price_ttc=".$price_ttc.",";
1957  $sql .= " price_min=".$price_min.",";
1958  $sql .= " price_min_ttc=".$price_min_ttc.",";
1959  $sql .= " localtax1_tx=".($localtax1 >= 0 ? $localtax1 : 'NULL').",";
1960  $sql .= " localtax2_tx=".($localtax2 >= 0 ? $localtax2 : 'NULL').",";
1961  $sql .= " localtax1_type=".($localtaxtype1 != '' ? "'".$this->db->escape($localtaxtype1)."'" : "'0'").",";
1962  $sql .= " localtax2_type=".($localtaxtype2 != '' ? "'".$this->db->escape($localtaxtype2)."'" : "'0'").",";
1963  $sql .= " default_vat_code=".($newdefaultvatcode ? "'".$this->db->escape($newdefaultvatcode)."'" : "null").",";
1964  $sql .= " tva_tx='".price2num($newvat)."',";
1965  $sql .= " recuperableonly='".$this->db->escape($newnpr)."'";
1966  $sql .= " WHERE rowid = ".$id;
1967 
1968  dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
1969  $resql = $this->db->query($sql);
1970  if ($resql) {
1971  $this->multiprices[$level] = $price;
1972  $this->multiprices_ttc[$level] = $price_ttc;
1973  $this->multiprices_min[$level] = $price_min;
1974  $this->multiprices_min_ttc[$level] = $price_min_ttc;
1975  $this->multiprices_base_type[$level] = $newpricebase;
1976  $this->multiprices_default_vat_code[$level] = $newdefaultvatcode;
1977  $this->multiprices_tva_tx[$level] = $newvat;
1978  $this->multiprices_recuperableonly[$level] = $newnpr;
1979 
1980  $this->price = $price;
1981  $this->price_ttc = $price_ttc;
1982  $this->price_min = $price_min;
1983  $this->price_min_ttc = $price_min_ttc;
1984  $this->price_base_type = $newpricebase;
1985  $this->default_vat_code = $newdefaultvatcode;
1986  $this->tva_tx = $newvat;
1987  $this->tva_npr = $newnpr;
1988  //Local taxes
1989  $this->localtax1_tx = $localtax1;
1990  $this->localtax2_tx = $localtax2;
1991  $this->localtax1_type = $localtaxtype1;
1992  $this->localtax2_type = $localtaxtype2;
1993 
1994  // Price by quantity
1995  $this->price_by_qty = $newpbq;
1996 
1997  $this->_log_price($user, $level); // Save price for level into table product_price
1998 
1999  $this->level = $level; // Store level of price edited for trigger
2000 
2001  // Call trigger
2002  $result = $this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
2003  if ($result < 0) {
2004  $this->db->rollback();
2005  return -1;
2006  }
2007  // End call triggers
2008 
2009  $this->db->commit();
2010  } else {
2011  $this->db->rollback();
2012  dol_print_error($this->db);
2013  }
2014  }
2015 
2016  return 1;
2017  }
2018 
2026  public function setPriceExpression($expression_id)
2027  {
2028  global $user;
2029 
2030  $this->fk_price_expression = $expression_id;
2031 
2032  return $this->update($this->id, $user);
2033  }
2034 
2047  public function fetch($id = '', $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0, $ignore_price_load = 0, $ignore_lang_load = 0)
2048  {
2049  include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2050 
2051  global $langs, $conf;
2052 
2053  dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
2054 
2055  // Check parameters
2056  if (!$id && !$ref && !$ref_ext && !$barcode) {
2057  $this->error = 'ErrorWrongParameters';
2058  dol_syslog(get_class($this)."::fetch ".$this->error);
2059  return -1;
2060  }
2061 
2062  $sql = "SELECT rowid, ref, ref_ext, label, description, url, note_public, note as note_private, customcode, fk_country, fk_state, price, price_ttc,";
2063  $sql .= " price_min, price_min_ttc, price_base_type, cost_price, default_vat_code, tva_tx, recuperableonly as tva_npr, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, tosell,";
2064  $sql .= " tobuy, fk_product_type, duration, fk_default_warehouse, seuil_stock_alerte, canvas, net_measure, net_measure_units, weight, weight_units,";
2065  $sql .= " length, length_units, width, width_units, height, height_units,";
2066  $sql .= " surface, surface_units, volume, volume_units, barcode, fk_barcode_type, finished,";
2067  $sql .= " accountancy_code_buy, accountancy_code_buy_intra, accountancy_code_buy_export,";
2068  $sql .= " accountancy_code_sell, accountancy_code_sell_intra, accountancy_code_sell_export, stock, pmp,";
2069  $sql .= " datec, tms, import_key, entity, desiredstock, tobatch, fk_unit,";
2070  $sql .= " fk_price_expression, price_autogen, model_pdf";
2071  $sql .= " FROM ".MAIN_DB_PREFIX."product";
2072  if ($id) {
2073  $sql .= " WHERE rowid = ".(int) $id;
2074  } else {
2075  $sql .= " WHERE entity IN (".getEntity($this->element).")";
2076  if ($ref) {
2077  $sql .= " AND ref = '".$this->db->escape($ref)."'";
2078  } elseif ($ref_ext) {
2079  $sql .= " AND ref_ext = '".$this->db->escape($ref_ext)."'";
2080  } elseif ($barcode) {
2081  $sql .= " AND barcode = '".$this->db->escape($barcode)."'";
2082  }
2083  }
2084 
2085  $resql = $this->db->query($sql);
2086  if ($resql) {
2087  unset($this->oldcopy);
2088 
2089  if ($this->db->num_rows($resql) > 0) {
2090  $obj = $this->db->fetch_object($resql);
2091 
2092  $this->id = $obj->rowid;
2093  $this->ref = $obj->ref;
2094  $this->ref_ext = $obj->ref_ext;
2095  $this->label = $obj->label;
2096  $this->description = $obj->description;
2097  $this->url = $obj->url;
2098  $this->note_public = $obj->note_public;
2099  $this->note_private = $obj->note_private;
2100  $this->note = $obj->note_private; // deprecated
2101 
2102  $this->type = $obj->fk_product_type;
2103  $this->status = $obj->tosell;
2104  $this->status_buy = $obj->tobuy;
2105  $this->status_batch = $obj->tobatch;
2106 
2107  $this->customcode = $obj->customcode;
2108  $this->country_id = $obj->fk_country;
2109  $this->country_code = getCountry($this->country_id, 2, $this->db);
2110  $this->state_id = $obj->fk_state;
2111  $this->price = $obj->price;
2112  $this->price_ttc = $obj->price_ttc;
2113  $this->price_min = $obj->price_min;
2114  $this->price_min_ttc = $obj->price_min_ttc;
2115  $this->price_base_type = $obj->price_base_type;
2116  $this->cost_price = $obj->cost_price;
2117  $this->default_vat_code = $obj->default_vat_code;
2118  $this->tva_tx = $obj->tva_tx;
2120  $this->tva_npr = $obj->tva_npr;
2121  $this->recuperableonly = $obj->tva_npr; // For backward compatibility
2123  $this->localtax1_tx = $obj->localtax1_tx;
2124  $this->localtax2_tx = $obj->localtax2_tx;
2125  $this->localtax1_type = $obj->localtax1_type;
2126  $this->localtax2_type = $obj->localtax2_type;
2127 
2128  $this->finished = $obj->finished;
2129  $this->duration = $obj->duration;
2130  $this->duration_value = substr($obj->duration, 0, dol_strlen($obj->duration) - 1);
2131  $this->duration_unit = substr($obj->duration, -1);
2132  $this->canvas = $obj->canvas;
2133  $this->net_measure = $obj->net_measure;
2134  $this->net_measure_units = $obj->net_measure_units;
2135  $this->weight = $obj->weight;
2136  $this->weight_units = $obj->weight_units;
2137  $this->length = $obj->length;
2138  $this->length_units = $obj->length_units;
2139  $this->width = $obj->width;
2140  $this->width_units = $obj->width_units;
2141  $this->height = $obj->height;
2142  $this->height_units = $obj->height_units;
2143 
2144  $this->surface = $obj->surface;
2145  $this->surface_units = $obj->surface_units;
2146  $this->volume = $obj->volume;
2147  $this->volume_units = $obj->volume_units;
2148  $this->barcode = $obj->barcode;
2149  $this->barcode_type = $obj->fk_barcode_type;
2150 
2151  $this->accountancy_code_buy = $obj->accountancy_code_buy;
2152  $this->accountancy_code_buy_intra = $obj->accountancy_code_buy_intra;
2153  $this->accountancy_code_buy_export = $obj->accountancy_code_buy_export;
2154  $this->accountancy_code_sell = $obj->accountancy_code_sell;
2155  $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
2156  $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
2157 
2158  $this->fk_default_warehouse = $obj->fk_default_warehouse;
2159  $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
2160  $this->desiredstock = $obj->desiredstock;
2161  $this->stock_reel = $obj->stock;
2162  $this->pmp = $obj->pmp;
2163 
2164  $this->date_creation = $obj->datec;
2165  $this->date_modification = $obj->tms;
2166  $this->import_key = $obj->import_key;
2167  $this->entity = $obj->entity;
2168 
2169  $this->ref_ext = $obj->ref_ext;
2170  $this->fk_price_expression = $obj->fk_price_expression;
2171  $this->fk_unit = $obj->fk_unit;
2172  $this->price_autogen = $obj->price_autogen;
2173  $this->model_pdf = $obj->model_pdf;
2174 
2175  $this->db->free($resql);
2176 
2177  // Retrieve all extrafield
2178  // fetch optionals attributes and labels
2179  $this->fetch_optionals();
2180 
2181  // multilangs
2182  if (!empty($conf->global->MAIN_MULTILANGS) && empty($ignore_lang_load)) {
2183  $this->getMultiLangs();
2184  }
2185 
2186  // Load multiprices array
2187  if (!empty($conf->global->PRODUIT_MULTIPRICES) && empty($ignore_price_load)) // prices per segment
2188  {
2189  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++)
2190  {
2191  $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2192  $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2193  $sql .= " FROM ".MAIN_DB_PREFIX."product_price";
2194  $sql .= " WHERE entity IN (".getEntity('productprice').")";
2195  $sql .= " AND price_level=".$i;
2196  $sql .= " AND fk_product = ".$this->id;
2197  $sql .= " ORDER BY date_price DESC, rowid DESC";
2198  $sql .= " LIMIT 1";
2199  $resql = $this->db->query($sql);
2200  if ($resql) {
2201  $result = $this->db->fetch_array($resql);
2202 
2203  $this->multiprices[$i] = $result ? $result["price"] : null;
2204  $this->multiprices_ttc[$i] = $result ? $result["price_ttc"] : null;
2205  $this->multiprices_min[$i] = $result ? $result["price_min"] : null;
2206  $this->multiprices_min_ttc[$i] = $result ? $result["price_min_ttc"] : null;
2207  $this->multiprices_base_type[$i] = $result ? $result["price_base_type"] : null;
2208  // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2209  $this->multiprices_tva_tx[$i] = $result ? $result["tva_tx"].($result ? ' ('.$result['default_vat_code'].')' : '') : null;
2210  $this->multiprices_recuperableonly[$i] = $result ? $result["recuperableonly"] : null;
2211 
2212  // Price by quantity
2213  /*
2214  $this->prices_by_qty[$i]=$result["price_by_qty"];
2215  $this->prices_by_qty_id[$i]=$result["rowid"];
2216  // Récuperation de la liste des prix selon qty si flag positionné
2217  if ($this->prices_by_qty[$i] == 1)
2218  {
2219  $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2220  $sql.= " FROM ".MAIN_DB_PREFIX."product_price_by_qty";
2221  $sql.= " WHERE fk_product_price = ".$this->prices_by_qty_id[$i];
2222  $sql.= " ORDER BY quantity ASC";
2223  $resultat=array();
2224  $resql = $this->db->query($sql);
2225  if ($resql)
2226  {
2227  $ii=0;
2228  while ($result= $this->db->fetch_array($resql)) {
2229  $resultat[$ii]=array();
2230  $resultat[$ii]["rowid"]=$result["rowid"];
2231  $resultat[$ii]["price"]= $result["price"];
2232  $resultat[$ii]["unitprice"]= $result["unitprice"];
2233  $resultat[$ii]["quantity"]= $result["quantity"];
2234  $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2235  $resultat[$ii]["remise"]= $result["remise"]; // deprecated
2236  $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2237  $ii++;
2238  }
2239  $this->prices_by_qty_list[$i]=$resultat;
2240  }
2241  else
2242  {
2243  dol_print_error($this->db);
2244  return -1;
2245  }
2246  }*/
2247  } else {
2248  $this->error = $this->db->lasterror;
2249  return -1;
2250  }
2251  }
2252  } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES) && empty($ignore_price_load)) // prices per customers
2253  {
2254  // Nothing loaded by default. List may be very long.
2255  } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY) && empty($ignore_price_load)) // prices per quantity
2256  {
2257  $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2258  $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
2259  $sql .= " FROM ".MAIN_DB_PREFIX."product_price";
2260  $sql .= " WHERE fk_product = ".$this->id;
2261  $sql .= " ORDER BY date_price DESC, rowid DESC";
2262  $sql .= " LIMIT 1";
2263  $resql = $this->db->query($sql);
2264  if ($resql) {
2265  $result = $this->db->fetch_array($resql);
2266 
2267  // Price by quantity
2268  $this->prices_by_qty[0] = $result["price_by_qty"];
2269  $this->prices_by_qty_id[0] = $result["rowid"];
2270  // Récuperation de la liste des prix selon qty si flag positionné
2271  if ($this->prices_by_qty[0] == 1) {
2272  $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
2273  $sql .= " FROM ".MAIN_DB_PREFIX."product_price_by_qty";
2274  $sql .= " WHERE fk_product_price = ".$this->prices_by_qty_id[0];
2275  $sql .= " ORDER BY quantity ASC";
2276  $resultat = array();
2277  $resql = $this->db->query($sql);
2278  if ($resql) {
2279  $ii = 0;
2280  while ($result = $this->db->fetch_array($resql)) {
2281  $resultat[$ii] = array();
2282  $resultat[$ii]["rowid"] = $result["rowid"];
2283  $resultat[$ii]["price"] = $result["price"];
2284  $resultat[$ii]["unitprice"] = $result["unitprice"];
2285  $resultat[$ii]["quantity"] = $result["quantity"];
2286  $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2287  //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
2288  $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2289  $ii++;
2290  }
2291  $this->prices_by_qty_list[0] = $resultat;
2292  } else {
2293  $this->error = $this->db->lasterror;
2294  return -1;
2295  }
2296  }
2297  } else {
2298  $this->error = $this->db->lasterror;
2299  return -1;
2300  }
2301  } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES) && empty($ignore_price_load)) // prices per customer and quantity
2302  {
2303  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++)
2304  {
2305  $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2306  $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2307  $sql .= " FROM ".MAIN_DB_PREFIX."product_price";
2308  $sql .= " WHERE entity IN (".getEntity('productprice').")";
2309  $sql .= " AND price_level=".$i;
2310  $sql .= " AND fk_product = ".$this->id;
2311  $sql .= " ORDER BY date_price DESC, rowid DESC";
2312  $sql .= " LIMIT 1";
2313  $resql = $this->db->query($sql);
2314  if ($resql) {
2315  $result = $this->db->fetch_array($resql);
2316 
2317  $this->multiprices[$i] = $result["price"];
2318  $this->multiprices_ttc[$i] = $result["price_ttc"];
2319  $this->multiprices_min[$i] = $result["price_min"];
2320  $this->multiprices_min_ttc[$i] = $result["price_min_ttc"];
2321  $this->multiprices_base_type[$i] = $result["price_base_type"];
2322  // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2323  $this->multiprices_tva_tx[$i] = $result["tva_tx"]; // TODO Add ' ('.$result['default_vat_code'].')'
2324  $this->multiprices_recuperableonly[$i] = $result["recuperableonly"];
2325 
2326  // Price by quantity
2327  $this->prices_by_qty[$i] = $result["price_by_qty"];
2328  $this->prices_by_qty_id[$i] = $result["rowid"];
2329  // Récuperation de la liste des prix selon qty si flag positionné
2330  if ($this->prices_by_qty[$i] == 1) {
2331  $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2332  $sql .= " FROM ".MAIN_DB_PREFIX."product_price_by_qty";
2333  $sql .= " WHERE fk_product_price = ".$this->prices_by_qty_id[$i];
2334  $sql .= " ORDER BY quantity ASC";
2335  $resultat = array();
2336  $resql = $this->db->query($sql);
2337  if ($resql) {
2338  $ii = 0;
2339  while ($result = $this->db->fetch_array($resql)) {
2340  $resultat[$ii] = array();
2341  $resultat[$ii]["rowid"] = $result["rowid"];
2342  $resultat[$ii]["price"] = $result["price"];
2343  $resultat[$ii]["unitprice"] = $result["unitprice"];
2344  $resultat[$ii]["quantity"] = $result["quantity"];
2345  $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2346  $resultat[$ii]["remise"] = $result["remise"]; // deprecated
2347  $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2348  $ii++;
2349  }
2350  $this->prices_by_qty_list[$i] = $resultat;
2351  } else {
2352  $this->error = $this->db->lasterror;
2353  return -1;
2354  }
2355  }
2356  } else {
2357  $this->error = $this->db->lasterror;
2358  return -1;
2359  }
2360  }
2361  }
2362 
2363  if (!empty($conf->dynamicprices->enabled) && !empty($this->fk_price_expression) && empty($ignore_expression)) {
2364  include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2365  $priceparser = new PriceParser($this->db);
2366  $price_result = $priceparser->parseProduct($this);
2367  if ($price_result >= 0) {
2368  $this->price = $price_result;
2369  // Calculate the VAT
2370  $this->price_ttc = price2num($this->price) * (1 + ($this->tva_tx / 100));
2371  $this->price_ttc = price2num($this->price_ttc, 'MU');
2372  }
2373  }
2374 
2375  // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
2376  // Instead we just init the stock_warehouse array
2377  $this->stock_warehouse = array();
2378 
2379  return 1;
2380  } else {
2381  return 0;
2382  }
2383  } else {
2384  $this->error = $this->db->lasterror;
2385  return -1;
2386  }
2387  }
2388 
2389  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2396  public function load_stats_mo($socid = 0)
2397  {
2398  // phpcs:enable
2399  global $user, $hookmanager, $action;
2400 
2401  $error = 0;
2402 
2403  foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
2404  $this->stats_mo['customers_'.$role] = 0;
2405  $this->stats_mo['nb_'.$role] = 0;
2406  $this->stats_mo['qty_'.$role] = 0;
2407 
2408  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2409  $sql .= " SUM(mp.qty) as qty";
2410  $sql .= " FROM ".MAIN_DB_PREFIX."mrp_mo as c";
2411  $sql .= " INNER JOIN ".MAIN_DB_PREFIX."mrp_production as mp ON mp.fk_mo=c.rowid";
2412  if (empty($user->rights->societe->client->voir) && !$socid) {
2413  $sql .= "INNER JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc=c.fk_soc AND sc.fk_user = ".$user->id;
2414  }
2415  $sql .= " WHERE ";
2416  $sql .= " c.entity IN (".getEntity('mo').")";
2417 
2418  $sql .= " AND mp.fk_product =".$this->id;
2419  $sql .= " AND mp.role ='".$this->db->escape($role)."'";
2420  if ($socid > 0) {
2421  $sql .= " AND c.fk_soc = ".$socid;
2422  }
2423 
2424  $result = $this->db->query($sql);
2425  if ($result) {
2426  $obj = $this->db->fetch_object($result);
2427  $this->stats_mo['customers_'.$role] = $obj->nb_customers ? $obj->nb_customers : 0;
2428  $this->stats_mo['nb_'.$role] = $obj->nb ? $obj->nb : 0;
2429  $this->stats_mo['qty_'.$role] = $obj->qty ? $obj->qty : 0;
2430  } else {
2431  $this->error = $this->db->error();
2432  $error++;
2433  }
2434  }
2435 
2436  if (!empty($error)) {
2437  return -1;
2438  }
2439 
2440  $parameters = array('socid' => $socid);
2441  $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
2442  if ($reshook > 0) $this->stats_mo = $hookmanager->resArray['stats_mo'];
2443 
2444  return 1;
2445  }
2446 
2447  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2454  public function load_stats_bom($socid = 0)
2455  {
2456  // phpcs:enable
2457  global $user, $hookmanager;
2458 
2459  $error = 0;
2460 
2461  $this->stats_bom['nb_toproduce'] = 0;
2462  $this->stats_bom['nb_toconsume'] = 0;
2463  $this->stats_bom['qty_toproduce'] = 0;
2464  $this->stats_bom['qty_toconsume'] = 0;
2465 
2466  $sql = "SELECT COUNT(DISTINCT b.rowid) as nb_toproduce,";
2467  $sql .= " SUM(b.qty) as qty_toproduce";
2468  $sql .= " FROM ".MAIN_DB_PREFIX."bom_bom as b";
2469  $sql .= " INNER JOIN ".MAIN_DB_PREFIX."bom_bomline as bl ON bl.fk_bom=b.rowid";
2470  $sql .= " WHERE ";
2471  $sql .= " b.entity IN (".getEntity('bom').")";
2472  $sql .= " AND b.fk_product =".$this->id;
2473  $sql .= " GROUP BY b.rowid";
2474 
2475  $result = $this->db->query($sql);
2476  if ($result) {
2477  $obj = $this->db->fetch_object($result);
2478  $this->stats_bom['nb_toproduce'] = $obj->nb_toproduce ? $obj->nb_toproduce : 0;
2479  $this->stats_bom['qty_toproduce'] = $obj->qty_toproduce ? price2num($obj->qty_toproduce) : 0;
2480  } else {
2481  $this->error = $this->db->error();
2482  $error++;
2483  }
2484 
2485  $sql = "SELECT COUNT(DISTINCT bl.rowid) as nb_toconsume,";
2486  $sql .= " SUM(bl.qty) as qty_toconsume";
2487  $sql .= " FROM ".MAIN_DB_PREFIX."bom_bom as b";
2488  $sql .= " INNER JOIN ".MAIN_DB_PREFIX."bom_bomline as bl ON bl.fk_bom=b.rowid";
2489  $sql .= " WHERE ";
2490  $sql .= " b.entity IN (".getEntity('bom').")";
2491  $sql .= " AND bl.fk_product =".$this->id;
2492 
2493  $result = $this->db->query($sql);
2494  if ($result) {
2495  $obj = $this->db->fetch_object($result);
2496  $this->stats_bom['nb_toconsume'] = $obj->nb_toconsume ? $obj->nb_toconsume : 0;
2497  $this->stats_bom['qty_toconsume'] = $obj->qty_toconsume ? price2num($obj->qty_toconsume) : 0;
2498  } else {
2499  $this->error = $this->db->error();
2500  $error++;
2501  }
2502 
2503  if (!empty($error)) {
2504  return -1;
2505  }
2506 
2507  $parameters = array('socid' => $socid);
2508  $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
2509  if ($reshook > 0) $this->stats_bom = $hookmanager->resArray['stats_bom'];
2510 
2511  return 1;
2512  }
2513 
2514  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2521  public function load_stats_propale($socid = 0)
2522  {
2523  // phpcs:enable
2524  global $conf, $user, $hookmanager;
2525 
2526  $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
2527  $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
2528  $sql .= " FROM ".MAIN_DB_PREFIX."propaldet as pd";
2529  $sql .= ", ".MAIN_DB_PREFIX."propal as p";
2530  $sql .= ", ".MAIN_DB_PREFIX."societe as s";
2531  if (empty($user->rights->societe->client->voir) && !$socid) {
2532  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2533  }
2534  $sql .= " WHERE p.rowid = pd.fk_propal";
2535  $sql .= " AND p.fk_soc = s.rowid";
2536  $sql .= " AND p.entity IN (".getEntity('propal').")";
2537  $sql .= " AND pd.fk_product = ".$this->id;
2538  if (empty($user->rights->societe->client->voir) && !$socid) {
2539  $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
2540  }
2541  //$sql.= " AND pr.fk_statut != 0";
2542  if ($socid > 0) {
2543  $sql .= " AND p.fk_soc = ".$socid;
2544  }
2545 
2546  $result = $this->db->query($sql);
2547  if ($result) {
2548  $obj = $this->db->fetch_object($result);
2549  $this->stats_propale['customers'] = $obj->nb_customers;
2550  $this->stats_propale['nb'] = $obj->nb;
2551  $this->stats_propale['rows'] = $obj->nb_rows;
2552  $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
2553 
2554  // if it's a virtual product, maybe it is in proposal by extension
2555  if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
2556  $TFather = $this->getFather();
2557  if (is_array($TFather) && !empty($TFather)) {
2558  foreach ($TFather as &$fatherData) {
2559  $pFather = new Product($this->db);
2560  $pFather->id = $fatherData['id'];
2561  $qtyCoef = $fatherData['qty'];
2562 
2563  if ($fatherData['incdec']) {
2564  $pFather->load_stats_propale($socid);
2565 
2566  $this->stats_propale['customers'] += $pFather->stats_propale['customers'];
2567  $this->stats_propale['nb'] += $pFather->stats_propale['nb'];
2568  $this->stats_propale['rows'] += $pFather->stats_propale['rows'];
2569  $this->stats_propale['qty'] += $pFather->stats_propale['qty'] * $qtyCoef;
2570  }
2571  }
2572  }
2573  }
2574 
2575  $parameters = array('socid' => $socid);
2576  $reshook = $hookmanager->executeHooks('loadStatsCustomerProposal', $parameters, $this, $action);
2577  if ($reshook > 0) $this->stats_propale = $hookmanager->resArray['stats_propale'];
2578 
2579  return 1;
2580  } else {
2581  $this->error = $this->db->error();
2582  return -1;
2583  }
2584  }
2585 
2586 
2587  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2594  public function load_stats_proposal_supplier($socid = 0)
2595  {
2596  // phpcs:enable
2597  global $conf, $user, $hookmanager, $action;
2598 
2599  $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
2600  $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
2601  $sql .= " FROM ".MAIN_DB_PREFIX."supplier_proposaldet as pd";
2602  $sql .= ", ".MAIN_DB_PREFIX."supplier_proposal as p";
2603  $sql .= ", ".MAIN_DB_PREFIX."societe as s";
2604  if (empty($user->rights->societe->client->voir) && !$socid) {
2605  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2606  }
2607  $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
2608  $sql .= " AND p.fk_soc = s.rowid";
2609  $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
2610  $sql .= " AND pd.fk_product = ".$this->id;
2611  if (empty($user->rights->societe->client->voir) && !$socid) {
2612  $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
2613  }
2614  //$sql.= " AND pr.fk_statut != 0";
2615  if ($socid > 0) {
2616  $sql .= " AND p.fk_soc = ".$socid;
2617  }
2618 
2619  $result = $this->db->query($sql);
2620  if ($result) {
2621  $obj = $this->db->fetch_object($result);
2622  $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
2623  $this->stats_proposal_supplier['nb'] = $obj->nb;
2624  $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
2625  $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
2626 
2627  $parameters = array('socid' => $socid);
2628  $reshook = $hookmanager->executeHooks('loadStatsSupplierProposal', $parameters, $this, $action);
2629  if ($reshook > 0) $this->stats_proposal_supplier = $hookmanager->resArray['stats_proposal_supplier'];
2630 
2631  return 1;
2632  } else {
2633  $this->error = $this->db->error();
2634  return -1;
2635  }
2636  }
2637 
2638 
2639  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2648  public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
2649  {
2650  // phpcs:enable
2651  global $conf, $user, $hookmanager;
2652 
2653  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2654  $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2655  $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
2656  $sql .= ", ".MAIN_DB_PREFIX."commande as c";
2657  $sql .= ", ".MAIN_DB_PREFIX."societe as s";
2658  if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
2659  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2660  }
2661  $sql .= " WHERE c.rowid = cd.fk_commande";
2662  $sql .= " AND c.fk_soc = s.rowid";
2663  $sql .= " AND c.entity IN (".getEntity($forVirtualStock && !empty($conf->global->STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE) ? 'stock' : 'commande').")";
2664  $sql .= " AND cd.fk_product = ".$this->id;
2665  if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
2666  $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
2667  }
2668  if ($socid > 0) {
2669  $sql .= " AND c.fk_soc = ".$socid;
2670  }
2671  if ($filtrestatut <> '') {
2672  $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")";
2673  }
2674 
2675  $result = $this->db->query($sql);
2676  if ($result) {
2677  $obj = $this->db->fetch_object($result);
2678  $this->stats_commande['customers'] = $obj->nb_customers;
2679  $this->stats_commande['nb'] = $obj->nb;
2680  $this->stats_commande['rows'] = $obj->nb_rows;
2681  $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
2682 
2683  // if it's a virtual product, maybe it is in order by extension
2684  if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
2685  $TFather = $this->getFather();
2686  if (is_array($TFather) && !empty($TFather)) {
2687  foreach ($TFather as &$fatherData) {
2688  $pFather = new Product($this->db);
2689  $pFather->id = $fatherData['id'];
2690  $qtyCoef = $fatherData['qty'];
2691 
2692  if ($fatherData['incdec']) {
2693  $pFather->load_stats_commande($socid, $filtrestatut);
2694 
2695  $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
2696  $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
2697  $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
2698  $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
2699  }
2700  }
2701  }
2702  }
2703 
2704  // If stock decrease is on invoice validation, the theorical stock continue to
2705  // count the orders to ship in theorical stock when some are already removed b invoice validation.
2706  // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation.
2707  if (!empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
2708  if (!empty($conf->global->DECREASE_ONLY_UNINVOICEDPRODUCTS)) {
2709  $adeduire = 0;
2710  $sql = "SELECT sum(fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet fd ";
2711  $sql .= " JOIN ".MAIN_DB_PREFIX."facture f ON fd.fk_facture = f.rowid ";
2712  $sql .= " JOIN ".MAIN_DB_PREFIX."element_element el ON el.fk_target = f.rowid and el.targettype = 'facture' and sourcetype = 'commande'";
2713  $sql .= " JOIN ".MAIN_DB_PREFIX."commande c ON el.fk_source = c.rowid ";
2714  $sql .= " WHERE c.fk_statut IN (".$filtrestatut.") AND c.facture = 0 AND fd.fk_product = ".$this->id;
2715  dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
2716 
2717  $resql = $this->db->query($sql);
2718  if ($resql) {
2719  if ($this->db->num_rows($resql) > 0) {
2720  $obj = $this->db->fetch_object($resql);
2721  $adeduire += $obj->count;
2722  }
2723  }
2724 
2725  $this->stats_commande['qty'] -= $adeduire;
2726  }
2727  }
2728 
2729  $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
2730  $reshook = $hookmanager->executeHooks('loadStatsCustomerOrder', $parameters, $this, $action);
2731  if ($reshook > 0) $this->stats_commande = $hookmanager->resArray['stats_commande'];
2732  return 1;
2733  } else {
2734  $this->error = $this->db->error();
2735  return -1;
2736  }
2737  }
2738 
2739  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2748  public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
2749  {
2750  // phpcs:enable
2751  global $conf, $user, $hookmanager, $action;
2752 
2753  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
2754  $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2755  $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd";
2756  $sql .= ", ".MAIN_DB_PREFIX."commande_fournisseur as c";
2757  $sql .= ", ".MAIN_DB_PREFIX."societe as s";
2758  if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
2759  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2760  }
2761  $sql .= " WHERE c.rowid = cd.fk_commande";
2762  $sql .= " AND c.fk_soc = s.rowid";
2763  $sql .= " AND c.entity IN (".getEntity($forVirtualStock && !empty($conf->global->STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE) ? 'stock' : 'supplier_order').")";
2764  $sql .= " AND cd.fk_product = ".$this->id;
2765  if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
2766  $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
2767  }
2768  if ($socid > 0) {
2769  $sql .= " AND c.fk_soc = ".$socid;
2770  }
2771  if ($filtrestatut != '') {
2772  $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
2773  }
2774 
2775  $result = $this->db->query($sql);
2776  if ($result) {
2777  $obj = $this->db->fetch_object($result);
2778  $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
2779  $this->stats_commande_fournisseur['nb'] = $obj->nb;
2780  $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
2781  $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
2782 
2783  $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
2784  $reshook = $hookmanager->executeHooks('loadStatsSupplierOrder', $parameters, $this, $action);
2785  if ($reshook > 0) $this->stats_commande_fournisseur = $hookmanager->resArray['stats_commande_fournisseur'];
2786 
2787  return 1;
2788  } else {
2789  $this->error = $this->db->error().' sql='.$sql;
2790  return -1;
2791  }
2792  }
2793 
2794  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2804  public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $filterShipmentStatus = '')
2805  {
2806  // phpcs:enable
2807  global $conf, $user, $hookmanager;
2808 
2809  $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
2810  $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
2811  $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed";
2812  $sql .= ", ".MAIN_DB_PREFIX."commandedet as cd";
2813  $sql .= ", ".MAIN_DB_PREFIX."commande as c";
2814  $sql .= ", ".MAIN_DB_PREFIX."expedition as e";
2815  $sql .= ", ".MAIN_DB_PREFIX."societe as s";
2816  if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
2817  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2818  }
2819  $sql .= " WHERE e.rowid = ed.fk_expedition";
2820  $sql .= " AND c.rowid = cd.fk_commande";
2821  $sql .= " AND e.fk_soc = s.rowid";
2822  $sql .= " AND e.entity IN (".getEntity($forVirtualStock && !empty($conf->global->STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE) ? 'stock' : 'expedition').")";
2823  $sql .= " AND ed.fk_origin_line = cd.rowid";
2824  $sql .= " AND cd.fk_product = ".$this->id;
2825  if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
2826  $sql .= " AND e.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
2827  }
2828  if ($socid > 0) {
2829  $sql .= " AND e.fk_soc = ".$socid;
2830  }
2831  if ($filtrestatut <> '') {
2832  $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
2833  }
2834  if (!empty($filterShipmentStatus)) $sql .= " AND e.fk_statut IN (".$this->db->sanitize($filterShipmentStatus).")";
2835 
2836  $result = $this->db->query($sql);
2837  if ($result) {
2838  $obj = $this->db->fetch_object($result);
2839  $this->stats_expedition['customers'] = $obj->nb_customers;
2840  $this->stats_expedition['nb'] = $obj->nb;
2841  $this->stats_expedition['rows'] = $obj->nb_rows;
2842  $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
2843 
2844  // if it's a virtual product, maybe it is in sending by extension
2845  if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
2846  $TFather = $this->getFather();
2847  if (is_array($TFather) && !empty($TFather)) {
2848  foreach ($TFather as &$fatherData) {
2849  $pFather = new Product($this->db);
2850  $pFather->id = $fatherData['id'];
2851  $qtyCoef = $fatherData['qty'];
2852 
2853  if ($fatherData['incdec']) {
2854  $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
2855 
2856  $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
2857  $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
2858  $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
2859  $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
2860  }
2861  }
2862  }
2863  }
2864 
2865  $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock, 'filterShipmentStatus' => $filterShipmentStatus);
2866  $reshook = $hookmanager->executeHooks('loadStatsSending', $parameters, $this, $action);
2867  if ($reshook > 0) $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
2868 
2869  return 1;
2870  } else {
2871  $this->error = $this->db->error();
2872  return -1;
2873  }
2874  }
2875 
2876  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2885  public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
2886  {
2887  // phpcs:enable
2888  global $conf, $user, $hookmanager, $action;
2889 
2890  $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_suppliers, COUNT(DISTINCT cf.rowid) as nb,";
2891  $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
2892  $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseur_dispatch as fd";
2893  $sql .= ", ".MAIN_DB_PREFIX."commande_fournisseur as cf";
2894  $sql .= ", ".MAIN_DB_PREFIX."societe as s";
2895  if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
2896  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2897  }
2898  $sql .= " WHERE cf.rowid = fd.fk_commande";
2899  $sql .= " AND cf.fk_soc = s.rowid";
2900  $sql .= " AND cf.entity IN (".getEntity($forVirtualStock && !empty($conf->global->STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE) ? 'stock' : 'supplier_order').")";
2901  $sql .= " AND fd.fk_product = ".$this->id;
2902  if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
2903  $sql .= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
2904  }
2905  if ($socid > 0) {
2906  $sql .= " AND cf.fk_soc = ".$socid;
2907  }
2908  if ($filtrestatut <> '') {
2909  $sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
2910  }
2911 
2912  $result = $this->db->query($sql);
2913  if ($result) {
2914  $obj = $this->db->fetch_object($result);
2915  $this->stats_reception['suppliers'] = $obj->nb_suppliers;
2916  $this->stats_reception['nb'] = $obj->nb;
2917  $this->stats_reception['rows'] = $obj->nb_rows;
2918  $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
2919 
2920  $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
2921  $reshook = $hookmanager->executeHooks('loadStatsReception', $parameters, $this, $action);
2922  if ($reshook > 0) $this->stats_reception = $hookmanager->resArray['stats_reception'];
2923 
2924  return 1;
2925  } else {
2926  $this->error = $this->db->error();
2927  return -1;
2928  }
2929  }
2930 
2931  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2940  public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
2941  {
2942  // phpcs:enable
2943  global $conf, $user, $hookmanager;
2944 
2945  $sql = "SELECT COUNT(DISTINCT m.fk_soc) as nb_customers, COUNT(DISTINCT m.rowid) as nb,";
2946  $sql .= " COUNT(mp.rowid) as nb_rows, SUM(mp.qty) as qty, role";
2947  $sql .= " FROM ".MAIN_DB_PREFIX."mrp_production as mp";
2948  $sql .= ", ".MAIN_DB_PREFIX."mrp_mo as m";
2949  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = m.fk_soc";
2950  if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
2951  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2952  }
2953  $sql .= " WHERE m.rowid = mp.fk_mo";
2954  $sql .= " AND m.entity IN (".getEntity($forVirtualStock && !empty($conf->global->STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE) ? 'stock' : 'mrp').")";
2955  $sql .= " AND mp.fk_product = ".$this->id;
2956  if (empty($user->rights->societe->client->voir) && !$socid && !$forVirtualStock) {
2957  $sql .= " AND m.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
2958  }
2959  if ($socid > 0) {
2960  $sql .= " AND m.fk_soc = ".$socid;
2961  }
2962  if ($filtrestatut <> '') {
2963  $sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
2964  }
2965  $sql .= " GROUP BY role";
2966 
2967  $this->stats_mrptoconsume['customers'] = 0;
2968  $this->stats_mrptoconsume['nb'] = 0;
2969  $this->stats_mrptoconsume['rows'] = 0;
2970  $this->stats_mrptoconsume['qty'] = 0;
2971  $this->stats_mrptoproduce['customers'] = 0;
2972  $this->stats_mrptoproduce['nb'] = 0;
2973  $this->stats_mrptoproduce['rows'] = 0;
2974  $this->stats_mrptoproduce['qty'] = 0;
2975 
2976  $result = $this->db->query($sql);
2977  if ($result) {
2978  while ($obj = $this->db->fetch_object($result)) {
2979  if ($obj->role == 'toconsume') {
2980  $this->stats_mrptoconsume['customers'] += $obj->nb_customers;
2981  $this->stats_mrptoconsume['nb'] += $obj->nb;
2982  $this->stats_mrptoconsume['rows'] += $obj->nb_rows;
2983  $this->stats_mrptoconsume['qty'] += ($obj->qty ? $obj->qty : 0);
2984  }
2985  if ($obj->role == 'consumed') {
2986  //$this->stats_mrptoconsume['customers'] += $obj->nb_customers;
2987  //$this->stats_mrptoconsume['nb'] += $obj->nb;
2988  //$this->stats_mrptoconsume['rows'] += $obj->nb_rows;
2989  $this->stats_mrptoconsume['qty'] -= ($obj->qty ? $obj->qty : 0);
2990  }
2991  if ($obj->role == 'toproduce') {
2992  $this->stats_mrptoproduce['customers'] += $obj->nb_customers;
2993  $this->stats_mrptoproduce['nb'] += $obj->nb;
2994  $this->stats_mrptoproduce['rows'] += $obj->nb_rows;
2995  $this->stats_mrptoproduce['qty'] += ($obj->qty ? $obj->qty : 0);
2996  }
2997  if ($obj->role == 'produced') {
2998  //$this->stats_mrptoproduce['customers'] += $obj->nb_customers;
2999  //$this->stats_mrptoproduce['nb'] += $obj->nb;
3000  //$this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3001  $this->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3002  }
3003  }
3004 
3005  // Clean data
3006  if ($this->stats_mrptoconsume['qty'] < 0) $this->stats_mrptoconsume['qty'] = 0;
3007  if ($this->stats_mrptoproduce['qty'] < 0) $this->stats_mrptoproduce['qty'] = 0;
3008 
3009  $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3010  $reshook = $hookmanager->executeHooks('loadStatsInProduction', $parameters, $this, $action);
3011  if ($reshook > 0) $this->stats_mrptoproduce = $hookmanager->resArray['stats_mrptoproduce'];
3012 
3013  return 1;
3014  } else {
3015  $this->error = $this->db->error();
3016  return -1;
3017  }
3018  }
3019 
3020  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3027  public function load_stats_contrat($socid = 0)
3028  {
3029  // phpcs:enable
3030  global $conf, $user, $hookmanager;
3031 
3032  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3033  $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3034  $sql .= " FROM ".MAIN_DB_PREFIX."contratdet as cd";
3035  $sql .= ", ".MAIN_DB_PREFIX."contrat as c";
3036  $sql .= ", ".MAIN_DB_PREFIX."societe as s";
3037  if (empty($user->rights->societe->client->voir) && !$socid) {
3038  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3039  }
3040  $sql .= " WHERE c.rowid = cd.fk_contrat";
3041  $sql .= " AND c.fk_soc = s.rowid";
3042  $sql .= " AND c.entity IN (".getEntity('contract').")";
3043  $sql .= " AND cd.fk_product = ".$this->id;
3044  if (empty($user->rights->societe->client->voir) && !$socid) {
3045  $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
3046  }
3047  //$sql.= " AND c.statut != 0";
3048  if ($socid > 0) {
3049  $sql .= " AND c.fk_soc = ".$socid;
3050  }
3051 
3052  $result = $this->db->query($sql);
3053  if ($result) {
3054  $obj = $this->db->fetch_object($result);
3055  $this->stats_contrat['customers'] = $obj->nb_customers;
3056  $this->stats_contrat['nb'] = $obj->nb;
3057  $this->stats_contrat['rows'] = $obj->nb_rows;
3058  $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
3059 
3060  // if it's a virtual product, maybe it is in contract by extension
3061  if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
3062  $TFather = $this->getFather();
3063  if (is_array($TFather) && !empty($TFather)) {
3064  foreach ($TFather as &$fatherData) {
3065  $pFather = new Product($this->db);
3066  $pFather->id = $fatherData['id'];
3067  $qtyCoef = $fatherData['qty'];
3068 
3069  if ($fatherData['incdec']) {
3070  $pFather->load_stats_contrat($socid);
3071 
3072  $this->stats_contrat['customers'] += $pFather->stats_contrat['customers'];
3073  $this->stats_contrat['nb'] += $pFather->stats_contrat['nb'];
3074  $this->stats_contrat['rows'] += $pFather->stats_contrat['rows'];
3075  $this->stats_contrat['qty'] += $pFather->stats_contrat['qty'] * $qtyCoef;
3076  }
3077  }
3078  }
3079  }
3080 
3081  $parameters = array('socid' => $socid);
3082  $reshook = $hookmanager->executeHooks('loadStatsContract', $parameters, $this, $action);
3083  if ($reshook > 0) $this->stats_contrat = $hookmanager->resArray['stats_contrat'];
3084 
3085  return 1;
3086  } else {
3087  $this->error = $this->db->error().' sql='.$sql;
3088  return -1;
3089  }
3090  }
3091 
3092  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3099  public function load_stats_facture($socid = 0)
3100  {
3101  // phpcs:enable
3102  global $db, $conf, $user, $hookmanager;
3103 
3104  $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3105  $sql .= " COUNT(fd.rowid) as nb_rows, SUM(".$this->db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
3106  $sql .= " FROM ".MAIN_DB_PREFIX."facturedet as fd";
3107  $sql .= ", ".MAIN_DB_PREFIX."facture as f";
3108  $sql .= ", ".MAIN_DB_PREFIX."societe as s";
3109  if (empty($user->rights->societe->client->voir) && !$socid) {
3110  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3111  }
3112  $sql .= " WHERE f.rowid = fd.fk_facture";
3113  $sql .= " AND f.fk_soc = s.rowid";
3114  $sql .= " AND f.entity IN (".getEntity('invoice').")";
3115  $sql .= " AND fd.fk_product = ".$this->id;
3116  if (empty($user->rights->societe->client->voir) && !$socid) {
3117  $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
3118  }
3119  //$sql.= " AND f.fk_statut != 0";
3120  if ($socid > 0) {
3121  $sql .= " AND f.fk_soc = ".$socid;
3122  }
3123 
3124  $result = $this->db->query($sql);
3125  if ($result) {
3126  $obj = $this->db->fetch_object($result);
3127  $this->stats_facture['customers'] = $obj->nb_customers;
3128  $this->stats_facture['nb'] = $obj->nb;
3129  $this->stats_facture['rows'] = $obj->nb_rows;
3130  $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
3131 
3132  // if it's a virtual product, maybe it is in invoice by extension
3133  if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
3134  $TFather = $this->getFather();
3135  if (is_array($TFather) && !empty($TFather)) {
3136  foreach ($TFather as &$fatherData) {
3137  $pFather = new Product($this->db);
3138  $pFather->id = $fatherData['id'];
3139  $qtyCoef = $fatherData['qty'];
3140 
3141  if ($fatherData['incdec']) {
3142  $pFather->load_stats_facture($socid);
3143 
3144  $this->stats_facture['customers'] += $pFather->stats_facture['customers'];
3145  $this->stats_facture['nb'] += $pFather->stats_facture['nb'];
3146  $this->stats_facture['rows'] += $pFather->stats_facture['rows'];
3147  $this->stats_facture['qty'] += $pFather->stats_facture['qty'] * $qtyCoef;
3148  }
3149  }
3150  }
3151  }
3152 
3153  $parameters = array('socid' => $socid);
3154  $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoice', $parameters, $this, $action);
3155  if ($reshook > 0) $this->stats_facture = $hookmanager->resArray['stats_facture'];
3156 
3157  return 1;
3158  } else {
3159  $this->error = $this->db->error();
3160  return -1;
3161  }
3162  }
3163 
3164  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3171  public function load_stats_facture_fournisseur($socid = 0)
3172  {
3173  // phpcs:enable
3174  global $conf, $user, $hookmanager, $action;
3175 
3176  $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
3177  $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3178  $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn_det as fd";
3179  $sql .= ", ".MAIN_DB_PREFIX."facture_fourn as f";
3180  $sql .= ", ".MAIN_DB_PREFIX."societe as s";
3181  if (empty($user->rights->societe->client->voir) && !$socid) {
3182  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3183  }
3184  $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
3185  $sql .= " AND f.fk_soc = s.rowid";
3186  $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
3187  $sql .= " AND fd.fk_product = ".$this->id;
3188  if (empty($user->rights->societe->client->voir) && !$socid) {
3189  $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
3190  }
3191  //$sql.= " AND f.fk_statut != 0";
3192  if ($socid > 0) {
3193  $sql .= " AND f.fk_soc = ".$socid;
3194  }
3195 
3196  $result = $this->db->query($sql);
3197  if ($result) {
3198  $obj = $this->db->fetch_object($result);
3199  $this->stats_facture_fournisseur['suppliers'] = $obj->nb_suppliers;
3200  $this->stats_facture_fournisseur['nb'] = $obj->nb;
3201  $this->stats_facture_fournisseur['rows'] = $obj->nb_rows;
3202  $this->stats_facture_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3203 
3204  $parameters = array('socid' => $socid);
3205  $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoice', $parameters, $this, $action);
3206  if ($reshook > 0) $this->stats_facture_fournisseur = $hookmanager->resArray['stats_facture_fournisseur'];
3207 
3208  return 1;
3209  } else {
3210  $this->error = $this->db->error();
3211  return -1;
3212  }
3213  }
3214 
3215  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3224  private function _get_stats($sql, $mode, $year = 0)
3225  {
3226  // phpcs:enable
3227  $tab = array();
3228 
3229  $resql = $this->db->query($sql);
3230  if ($resql) {
3231  $num = $this->db->num_rows($resql);
3232  $i = 0;
3233  while ($i < $num)
3234  {
3235  $arr = $this->db->fetch_array($resql);
3236  $keyfortab = (string) $arr[1];
3237  if ($year == -1) {
3238  $keyfortab = substr($keyfortab, -2);
3239  }
3240 
3241  if ($mode == 'byunit') {
3242  $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[0]; // 1st field
3243  } elseif ($mode == 'bynumber') {
3244  $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3245  }
3246  $i++;
3247  }
3248  } else {
3249  $this->error = $this->db->error().' sql='.$sql;
3250  return -1;
3251  }
3252 
3253  if (empty($year)) {
3254  $year = strftime('%Y', time());
3255  $month = strftime('%m', time());
3256  } elseif ($year == -1) {
3257  $year = '';
3258  $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3259  } else {
3260  $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3261  }
3262 
3263  $result = array();
3264 
3265  for ($j = 0; $j < 12; $j++)
3266  {
3267  // $ids is 'D', 'N', 'O', 'S', ... (First letter of month in user language)
3268  $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, 1970), "%b"), 1, 'right', 'UTF-8', 1));
3269 
3270  //print $idx.'-'.$year.'-'.$month.'<br>';
3271  $result[$j] = array($idx, isset($tab[$year.$month]) ? $tab[$year.$month] : 0);
3272  // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
3273 
3274  $month = "0".($month - 1);
3275  if (dol_strlen($month) == 3) {
3276  $month = substr($month, 1);
3277  }
3278  if ($month == 0) {
3279  $month = 12;
3280  $year = $year - 1;
3281  }
3282  }
3283 
3284  return array_reverse($result);
3285  }
3286 
3287 
3288  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3299  public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3300  {
3301  // phpcs:enable
3302  global $conf;
3303  global $user;
3304 
3305  $sql = "SELECT sum(d.qty), date_format(f.datef, '%Y%m')";
3306  if ($mode == 'bynumber') {
3307  $sql .= ", count(DISTINCT f.rowid)";
3308  }
3309  $sql .= " FROM ".MAIN_DB_PREFIX."facturedet as d, ".MAIN_DB_PREFIX."facture as f, ".MAIN_DB_PREFIX."societe as s";
3310  if ($filteronproducttype >= 0) {
3311  $sql .= ", ".MAIN_DB_PREFIX."product as p";
3312  }
3313  if (empty($user->rights->societe->client->voir) && !$socid) {
3314  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3315  }
3316  $sql .= " WHERE f.rowid = d.fk_facture";
3317  if ($this->id > 0) {
3318  $sql .= " AND d.fk_product =".$this->id;
3319  } else {
3320  $sql .= " AND d.fk_product > 0";
3321  }
3322  if ($filteronproducttype >= 0) {
3323  $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
3324  }
3325  $sql .= " AND f.fk_soc = s.rowid";
3326  $sql .= " AND f.entity IN (".getEntity('invoice').")";
3327  if (empty($user->rights->societe->client->voir) && !$socid) {
3328  $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
3329  }
3330  if ($socid > 0) {
3331  $sql .= " AND f.fk_soc = $socid";
3332  }
3333  $sql .= $morefilter;
3334  $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
3335  $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
3336 
3337  return $this->_get_stats($sql, $mode, $year);
3338  }
3339 
3340 
3341  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3352  public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3353  {
3354  // phpcs:enable
3355  global $conf;
3356  global $user;
3357 
3358  $sql = "SELECT sum(d.qty), date_format(f.datef, '%Y%m')";
3359  if ($mode == 'bynumber') {
3360  $sql .= ", count(DISTINCT f.rowid)";
3361  }
3362  $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn_det as d, ".MAIN_DB_PREFIX."facture_fourn as f, ".MAIN_DB_PREFIX."societe as s";
3363  if ($filteronproducttype >= 0) {
3364  $sql .= ", ".MAIN_DB_PREFIX."product as p";
3365  }
3366  if (empty($user->rights->societe->client->voir) && !$socid) {
3367  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3368  }
3369  $sql .= " WHERE f.rowid = d.fk_facture_fourn";
3370  if ($this->id > 0) {
3371  $sql .= " AND d.fk_product =".$this->id;
3372  } else {
3373  $sql .= " AND d.fk_product > 0";
3374  }
3375  if ($filteronproducttype >= 0) {
3376  $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
3377  }
3378  $sql .= " AND f.fk_soc = s.rowid";
3379  $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
3380  if (empty($user->rights->societe->client->voir) && !$socid) {
3381  $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
3382  }
3383  if ($socid > 0) {
3384  $sql .= " AND f.fk_soc = $socid";
3385  }
3386  $sql .= $morefilter;
3387  $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
3388  $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
3389 
3390  return $this->_get_stats($sql, $mode, $year);
3391  }
3392 
3393  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3404  public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3405  {
3406  // phpcs:enable
3407  global $conf, $user;
3408 
3409  $sql = "SELECT sum(d.qty), date_format(p.datep, '%Y%m')";
3410  if ($mode == 'bynumber') {
3411  $sql .= ", count(DISTINCT p.rowid)";
3412  }
3413  $sql .= " FROM ".MAIN_DB_PREFIX."propaldet as d, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."societe as s";
3414  if ($filteronproducttype >= 0) {
3415  $sql .= ", ".MAIN_DB_PREFIX."product as prod";
3416  }
3417  if (empty($user->rights->societe->client->voir) && !$socid) {
3418  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3419  }
3420  $sql .= " WHERE p.rowid = d.fk_propal";
3421  if ($this->id > 0) {
3422  $sql .= " AND d.fk_product =".$this->id;
3423  } else {
3424  $sql .= " AND d.fk_product > 0";
3425  }
3426  if ($filteronproducttype >= 0) {
3427  $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
3428  }
3429  $sql .= " AND p.fk_soc = s.rowid";
3430  $sql .= " AND p.entity IN (".getEntity('propal').")";
3431  if (empty($user->rights->societe->client->voir) && !$socid) {
3432  $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
3433  }
3434  if ($socid > 0) {
3435  $sql .= " AND p.fk_soc = ".$socid;
3436  }
3437  $sql .= $morefilter;
3438  $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
3439  $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
3440 
3441  return $this->_get_stats($sql, $mode, $year);
3442  }
3443 
3444  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3455  public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3456  {
3457  // phpcs:enable
3458  global $conf;
3459  global $user;
3460 
3461  $sql = "SELECT sum(d.qty), date_format(p.date_valid, '%Y%m')";
3462  if ($mode == 'bynumber') {
3463  $sql .= ", count(DISTINCT p.rowid)";
3464  }
3465  $sql .= " FROM ".MAIN_DB_PREFIX."supplier_proposaldet as d, ".MAIN_DB_PREFIX."supplier_proposal as p, ".MAIN_DB_PREFIX."societe as s";
3466  if ($filteronproducttype >= 0) {
3467  $sql .= ", ".MAIN_DB_PREFIX."product as prod";
3468  }
3469  if (empty($user->rights->societe->client->voir) && !$socid) {
3470  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3471  }
3472  $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
3473  if ($this->id > 0) {
3474  $sql .= " AND d.fk_product =".$this->id;
3475  } else {
3476  $sql .= " AND d.fk_product > 0";
3477  }
3478  if ($filteronproducttype >= 0) {
3479  $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
3480  }
3481  $sql .= " AND p.fk_soc = s.rowid";
3482  $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
3483  if (empty($user->rights->societe->client->voir) && !$socid) {
3484  $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
3485  }
3486  if ($socid > 0) {
3487  $sql .= " AND p.fk_soc = ".$socid;
3488  }
3489  $sql .= $morefilter;
3490  $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
3491  $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
3492 
3493  return $this->_get_stats($sql, $mode, $year);
3494  }
3495 
3496  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3507  public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3508  {
3509  // phpcs:enable
3510  global $conf, $user;
3511 
3512  $sql = "SELECT sum(d.qty), date_format(c.date_commande, '%Y%m')";
3513  if ($mode == 'bynumber') {
3514  $sql .= ", count(DISTINCT c.rowid)";
3515  }
3516  $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as d, ".MAIN_DB_PREFIX."commande as c, ".MAIN_DB_PREFIX."societe as s";
3517  if ($filteronproducttype >= 0) {
3518  $sql .= ", ".MAIN_DB_PREFIX."product as p";
3519  }
3520  if (empty($user->rights->societe->client->voir) && !$socid) {
3521  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3522  }
3523  $sql .= " WHERE c.rowid = d.fk_commande";
3524  if ($this->id > 0) {
3525  $sql .= " AND d.fk_product =".$this->id;
3526  } else {
3527  $sql .= " AND d.fk_product > 0";
3528  }
3529  if ($filteronproducttype >= 0) {
3530  $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
3531  }
3532  $sql .= " AND c.fk_soc = s.rowid";
3533  $sql .= " AND c.entity IN (".getEntity('commande').")";
3534  if (empty($user->rights->societe->client->voir) && !$socid) {
3535  $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
3536  }
3537  if ($socid > 0) {
3538  $sql .= " AND c.fk_soc = ".$socid;
3539  }
3540  $sql .= $morefilter;
3541  $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
3542  $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
3543 
3544  return $this->_get_stats($sql, $mode, $year);
3545  }
3546 
3547  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3558  public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3559  {
3560  // phpcs:enable
3561  global $conf, $user;
3562 
3563  $sql = "SELECT sum(d.qty), date_format(c.date_commande, '%Y%m')";
3564  if ($mode == 'bynumber') {
3565  $sql .= ", count(DISTINCT c.rowid)";
3566  }
3567  $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as d, ".MAIN_DB_PREFIX."commande_fournisseur as c, ".MAIN_DB_PREFIX."societe as s";
3568  if ($filteronproducttype >= 0) {
3569  $sql .= ", ".MAIN_DB_PREFIX."product as p";
3570  }
3571  if (empty($user->rights->societe->client->voir) && !$socid) {
3572  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3573  }
3574  $sql .= " WHERE c.rowid = d.fk_commande";
3575  if ($this->id > 0) {
3576  $sql .= " AND d.fk_product =".$this->id;
3577  } else {
3578  $sql .= " AND d.fk_product > 0";
3579  }
3580  if ($filteronproducttype >= 0) {
3581  $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
3582  }
3583  $sql .= " AND c.fk_soc = s.rowid";
3584  $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
3585  if (empty($user->rights->societe->client->voir) && !$socid) {
3586  $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
3587  }
3588  if ($socid > 0) {
3589  $sql .= " AND c.fk_soc = ".$socid;
3590  }
3591  $sql .= $morefilter;
3592  $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
3593  $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
3594 
3595  return $this->_get_stats($sql, $mode, $year);
3596  }
3597 
3598  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3609  public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3610  {
3611  // phpcs:enable
3612  global $conf, $user;
3613 
3614  $sql = "SELECT sum(d.qty), date_format(c.date_contrat, '%Y%m')";
3615  if ($mode == 'bynumber') {
3616  $sql .= ", count(DISTINCT c.rowid)";
3617  }
3618  $sql .= " FROM ".MAIN_DB_PREFIX."contratdet as d, ".MAIN_DB_PREFIX."contrat as c, ".MAIN_DB_PREFIX."societe as s";
3619  if ($filteronproducttype >= 0) {
3620  $sql .= ", ".MAIN_DB_PREFIX."product as p";
3621  }
3622  if (empty($user->rights->societe->client->voir) && !$socid) {
3623  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3624  }
3625 
3626  $sql .= " WHERE c.entity IN (".getEntity('contract').")";
3627  $sql .= " AND c.rowid = d.fk_contrat";
3628 
3629  if ($this->id > 0) {
3630  $sql .= " AND d.fk_product =".$this->id;
3631  } else {
3632  $sql .= " AND d.fk_product > 0";
3633  }
3634  if ($filteronproducttype >= 0) {
3635  $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
3636  }
3637  $sql .= " AND c.fk_soc = s.rowid";
3638 
3639  if (empty($user->rights->societe->client->voir) && !$socid) {
3640  $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
3641  }
3642  if ($socid > 0) {
3643  $sql .= " AND c.fk_soc = ".$socid;
3644  }
3645  $sql .= $morefilter;
3646  $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
3647  $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
3648 
3649  return $this->_get_stats($sql, $mode, $year);
3650  }
3651 
3652  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3663  public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3664  {
3665  // phpcs:enable
3666  global $conf, $user;
3667 
3668  $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
3669  if ($mode == 'bynumber') {
3670  $sql .= ", count(DISTINCT d.rowid)";
3671  }
3672  $sql .= " FROM ".MAIN_DB_PREFIX."mrp_mo as d LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON d.fk_soc = s.rowid";
3673  if ($filteronproducttype >= 0) {
3674  $sql .= ", ".MAIN_DB_PREFIX."product as p";
3675  }
3676  if (empty($user->rights->societe->client->voir) && !$socid) {
3677  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3678  }
3679 
3680  $sql .= " WHERE d.entity IN (".getEntity('mo').")";
3681  $sql .= " AND d.status > 0";
3682 
3683  if ($this->id > 0) {
3684  $sql .= " AND d.fk_product =".$this->id;
3685  } else {
3686  $sql .= " AND d.fk_product > 0";
3687  }
3688  if ($filteronproducttype >= 0) {
3689  $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
3690  }
3691 
3692  if (empty($user->rights->societe->client->voir) && !$socid) {
3693  $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".$user->id;
3694  }
3695  if ($socid > 0) {
3696  $sql .= " AND d.fk_soc = ".$socid;
3697  }
3698  $sql .= $morefilter;
3699  $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
3700  $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
3701 
3702  return $this->_get_stats($sql, $mode, $year);
3703  }
3704 
3705  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3715  public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1)
3716  {
3717  // phpcs:enable
3718  // Clean parameters
3719  if (!is_numeric($id_pere)) {
3720  $id_pere = 0;
3721  }
3722  if (!is_numeric($id_fils)) {
3723  $id_fils = 0;
3724  }
3725  if (!is_numeric($incdec)) {
3726  $incdec = 0;
3727  }
3728 
3729  $result = $this->del_sousproduit($id_pere, $id_fils);
3730  if ($result < 0) {
3731  return $result;
3732  }
3733 
3734  // Check not already father of id_pere (to avoid father -> child -> father links)
3735  $sql = 'SELECT fk_product_pere from '.MAIN_DB_PREFIX.'product_association';
3736  $sql .= ' WHERE fk_product_pere = '.$id_fils.' AND fk_product_fils = '.$id_pere;
3737  if (!$this->db->query($sql)) {
3738  dol_print_error($this->db);
3739  return -1;
3740  } else {
3741  $result = $this->db->query($sql);
3742  if ($result) {
3743  $num = $this->db->num_rows($result);
3744  if ($num > 0) {
3745  $this->error = "isFatherOfThis";
3746  return -1;
3747  } else {
3748  $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'product_association(fk_product_pere,fk_product_fils,qty,incdec)';
3749  $sql .= ' VALUES ('.$id_pere.', '.$id_fils.', '.$qty.', '.$incdec.')';
3750  if (!$this->db->query($sql)) {
3751  dol_print_error($this->db);
3752  return -1;
3753  } else {
3754  return 1;
3755  }
3756  }
3757  }
3758  }
3759  }
3760 
3761  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3771  public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1)
3772  {
3773  // phpcs:enable
3774  // Clean parameters
3775  if (!is_numeric($id_pere)) {
3776  $id_pere = 0;
3777  }
3778  if (!is_numeric($id_fils)) {
3779  $id_fils = 0;
3780  }
3781  if (!is_numeric($incdec)) {
3782  $incdec = 1;
3783  }
3784  if (!is_numeric($qty)) {
3785  $qty = 1;
3786  }
3787 
3788  $sql = 'UPDATE '.MAIN_DB_PREFIX.'product_association SET ';
3789  $sql .= 'qty='.$qty;
3790  $sql .= ',incdec='.$incdec;
3791  $sql .= ' WHERE fk_product_pere='.$id_pere.' AND fk_product_fils='.$id_fils;
3792 
3793  if (!$this->db->query($sql)) {
3794  dol_print_error($this->db);
3795  return -1;
3796  } else {
3797  return 1;
3798  }
3799  }
3800 
3801  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3809  public function del_sousproduit($fk_parent, $fk_child)
3810  {
3811  // phpcs:enable
3812  if (!is_numeric($fk_parent)) {
3813  $fk_parent = 0;
3814  }
3815  if (!is_numeric($fk_child)) {
3816  $fk_child = 0;
3817  }
3818 
3819  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_association";
3820  $sql .= " WHERE fk_product_pere = ".$fk_parent;
3821  $sql .= " AND fk_product_fils = ".$fk_child;
3822 
3823  dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
3824  if (!$this->db->query($sql)) {
3825  dol_print_error($this->db);
3826  return -1;
3827  }
3828 
3829  return 1;
3830  }
3831 
3832  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3840  public function is_sousproduit($fk_parent, $fk_child)
3841  {
3842  // phpcs:enable
3843  $sql = "SELECT fk_product_pere, qty, incdec";
3844  $sql .= " FROM ".MAIN_DB_PREFIX."product_association";
3845  $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
3846  $sql .= " AND fk_product_fils = ".((int) $fk_child);
3847 
3848  $result = $this->db->query($sql);
3849  if ($result) {
3850  $num = $this->db->num_rows($result);
3851 
3852  if ($num > 0) {
3853  $obj = $this->db->fetch_object($result);
3854 
3855  $this->is_sousproduit_qty = $obj->qty;
3856  $this->is_sousproduit_incdec = $obj->incdec;
3857 
3858  return true;
3859  } else {
3860  return false;
3861  }
3862  } else {
3863  dol_print_error($this->db);
3864  return -1;
3865  }
3866  }
3867 
3868 
3869  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3880  public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
3881  {
3882  // phpcs:enable
3883  global $conf;
3884 
3885  $now = dol_now();
3886 
3887  dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
3888 
3889  // Clean parameters
3890  $quantity = price2num($quantity, 'MS');
3891 
3892  if ($ref_fourn) {
3893  $sql = "SELECT rowid, fk_product";
3894  $sql .= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
3895  $sql .= " WHERE fk_soc = ".((int) $id_fourn);
3896  $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
3897  $sql .= " AND fk_product <> ".((int) $this->id);
3898  $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
3899 
3900  $resql = $this->db->query($sql);
3901  if ($resql) {
3902  $obj = $this->db->fetch_object($resql);
3903  if ($obj) {
3904  // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
3905  $this->product_id_already_linked = $obj->fk_product;
3906  return -3;
3907  }
3908  $this->db->free($resql);
3909  }
3910  }
3911 
3912  $sql = "SELECT rowid";
3913  $sql .= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
3914  $sql .= " WHERE fk_soc = ".$id_fourn;
3915  if ($ref_fourn) { $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
3916  } else { $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
3917  }
3918  $sql .= " AND quantity = ".$quantity;
3919  $sql .= " AND fk_product = ".$this->id;
3920  $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
3921 
3922  $resql = $this->db->query($sql);
3923  if ($resql) {
3924  $obj = $this->db->fetch_object($resql);
3925 
3926  // The reference supplier does not exist, we create it for this product.
3927  if (empty($obj)) {
3928  $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_fournisseur_price(";
3929  $sql .= "datec";
3930  $sql .= ", entity";
3931  $sql .= ", fk_product";
3932  $sql .= ", fk_soc";
3933  $sql .= ", ref_fourn";
3934  $sql .= ", quantity";
3935  $sql .= ", fk_user";
3936  $sql .= ", tva_tx";
3937  $sql .= ") VALUES (";
3938  $sql .= "'".$this->db->idate($now)."'";
3939  $sql .= ", ".$conf->entity;
3940  $sql .= ", ".$this->id;
3941  $sql .= ", ".$id_fourn;
3942  $sql .= ", '".$this->db->escape($ref_fourn)."'";
3943  $sql .= ", ".$quantity;
3944  $sql .= ", ".$user->id;
3945  $sql .= ", 0";
3946  $sql .= ")";
3947 
3948  if ($this->db->query($sql)) {
3949  $this->product_fourn_price_id = $this->db->last_insert_id(MAIN_DB_PREFIX."product_fournisseur_price");
3950  return 1;
3951  } else {
3952  $this->error = $this->db->lasterror();
3953  return -1;
3954  }
3955  } else {
3956  // If the supplier price already exists for this product and quantity
3957  $this->product_fourn_price_id = $obj->rowid;
3958  return 0;
3959  }
3960  } else {
3961  $this->error = $this->db->lasterror();
3962  return -2;
3963  }
3964  }
3965 
3966 
3967  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3973  public function list_suppliers()
3974  {
3975  // phpcs:enable
3976  global $conf;
3977 
3978  $list = array();
3979 
3980  $sql = "SELECT DISTINCT p.fk_soc";
3981  $sql .= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as p";
3982  $sql .= " WHERE p.fk_product = ".$this->id;
3983  $sql .= " AND p.entity = ".$conf->entity;
3984 
3985  $result = $this->db->query($sql);
3986  if ($result) {
3987  $num = $this->db->num_rows($result);
3988  $i = 0;
3989  while ($i < $num)
3990  {
3991  $obj = $this->db->fetch_object($result);
3992  $list[$i] = $obj->fk_soc;
3993  $i++;
3994  }
3995  }
3996 
3997  return $list;
3998  }
3999 
4000  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4008  public function clone_price($fromId, $toId)
4009  {
4010  global $conf, $user;
4011 
4012  $now = dol_now();
4013 
4014  $this->db->begin();
4015 
4016  // prices
4017  $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_price (";
4018  $sql .= " entity";
4019  $sql .= ", fk_product";
4020  $sql .= ", date_price";
4021  $sql .= ", price_level";
4022  $sql .= ", price";
4023  $sql .= ", price_ttc";
4024  $sql .= ", price_min";
4025  $sql .= ", price_min_ttc";
4026  $sql .= ", price_base_type";
4027  $sql .= ", default_vat_code";
4028  $sql .= ", tva_tx";
4029  $sql .= ", recuperableonly";
4030  $sql .= ", localtax1_tx";
4031  $sql .= ", localtax1_type";
4032  $sql .= ", localtax2_tx";
4033  $sql .= ", localtax2_type";
4034  $sql .= ", fk_user_author";
4035  $sql .= ", tosell";
4036  $sql .= ", price_by_qty";
4037  $sql .= ", fk_price_expression";
4038  $sql .= ", fk_multicurrency";
4039  $sql .= ", multicurrency_code";
4040  $sql .= ", multicurrency_tx";
4041  $sql .= ", multicurrency_price";
4042  $sql .= ", multicurrency_price_ttc";
4043  $sql .= ")";
4044  $sql .= " SELECT";
4045  $sql .= " entity";
4046  $sql .= ", ".$toId;
4047  $sql .= ", '".$this->db->idate($now)."'";
4048  $sql .= ", price_level";
4049  $sql .= ", price";
4050  $sql .= ", price_ttc";
4051  $sql .= ", price_min";
4052  $sql .= ", price_min_ttc";
4053  $sql .= ", price_base_type";
4054  $sql .= ", default_vat_code";
4055  $sql .= ", tva_tx";
4056  $sql .= ", recuperableonly";
4057  $sql .= ", localtax1_tx";
4058  $sql .= ", localtax1_type";
4059  $sql .= ", localtax2_tx";
4060  $sql .= ", localtax2_type";
4061  $sql .= ", ".$user->id;
4062  $sql .= ", tosell";
4063  $sql .= ", price_by_qty";
4064  $sql .= ", fk_price_expression";
4065  $sql .= ", fk_multicurrency";
4066  $sql .= ", multicurrency_code";
4067  $sql .= ", multicurrency_tx";
4068  $sql .= ", multicurrency_price";
4069  $sql .= ", multicurrency_price_ttc";
4070  $sql .= " FROM ".MAIN_DB_PREFIX."product_price";
4071  $sql .= " WHERE fk_product = ".$fromId;
4072  $sql .= " ORDER BY date_price DESC";
4073  if ($conf->global->PRODUIT_MULTIPRICES_LIMIT > 0) {
4074  $sql .= " LIMIT ".$conf->global->PRODUIT_MULTIPRICES_LIMIT;
4075  }
4076 
4077  dol_syslog(__METHOD__, LOG_DEBUG);
4078  $resql = $this->db->query($sql);
4079  if (!$resql) {
4080  $this->db->rollback();
4081  return -1;
4082  }
4083 
4084  $this->db->commit();
4085  return 1;
4086  }
4087 
4088  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4096  public function clone_associations($fromId, $toId)
4097  {
4098  // phpcs:enable
4099  $this->db->begin();
4100 
4101  $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'product_association (fk_product_pere, fk_product_fils, qty)';
4102  $sql .= " SELECT ".$toId.", fk_product_fils, qty FROM ".MAIN_DB_PREFIX."product_association";
4103  $sql .= " WHERE fk_product_pere = ".$fromId;
4104 
4105  dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
4106  if (!$this->db->query($sql)) {
4107  $this->db->rollback();
4108  return -1;
4109  }
4110 
4111  $this->db->commit();
4112  return 1;
4113  }
4114 
4115  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4123  public function clone_fournisseurs($fromId, $toId)
4124  {
4125  // phpcs:enable
4126  $this->db->begin();
4127 
4128  $now = dol_now();
4129 
4130  // les fournisseurs
4131  /*$sql = "INSERT ".MAIN_DB_PREFIX."product_fournisseur ("
4132  . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
4133  . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
4134  . " FROM ".MAIN_DB_PREFIX."product_fournisseur"
4135  . " WHERE fk_product = ".$fromId;
4136 
4137  if ( ! $this->db->query($sql ) )
4138  {
4139  $this->db->rollback();
4140  return -1;
4141  }*/
4142 
4143  // les prix de fournisseurs.
4144  $sql = "INSERT ".MAIN_DB_PREFIX."product_fournisseur_price (";
4145  $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user)";
4146  $sql .= " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, price, quantity, fk_user";
4147  $sql .= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
4148  $sql .= " WHERE fk_product = ".$fromId;
4149 
4150  dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
4151  $resql = $this->db->query($sql);
4152  if (!$resql) {
4153  $this->db->rollback();
4154  return -1;
4155  } else {
4156  $this->db->commit();
4157  return 1;
4158  }
4159  }
4160 
4161  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4173  public function fetch_prod_arbo($prod, $compl_path = "", $multiply = 1, $level = 1, $id_parent = 0)
4174  {
4175  // phpcs:enable
4176  global $conf, $langs;
4177 
4178  $tmpproduct = null;
4179  //var_dump($prod);
4180  foreach ($prod as $id_product => $desc_pere) // $id_product is 0 (first call starting with root top) or an id of a sub_product
4181  {
4182  if (is_array($desc_pere)) // If desc_pere is an array, this means it's a child
4183  {
4184  $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
4185  $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
4186  $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
4187  $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
4188  $incdec = !empty($desc_pere[4]) ? $desc_pere[4] : 0;
4189 
4190  if ($multiply < 1) { $multiply = 1;
4191  }
4192 
4193  //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
4194  if (is_null($tmpproduct)) $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
4195  $tmpproduct->fetch($id); // Load product to get ->ref
4196  $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
4197  //$this->fetch($id); // Load product to get ->ref
4198  //$this->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
4199  $this->res[] = array(
4200  'id'=>$id, // Id product
4201  'id_parent'=>$id_parent,
4202  'ref'=>$tmpproduct->ref, // Ref product
4203  'nb'=>$nb, // Nb of units that compose parent product
4204  'nb_total'=>$nb * $multiply, // Nb of units for all nb of product
4205  'stock'=>$tmpproduct->stock_reel, // Stock
4206  'stock_alert'=>$tmpproduct->seuil_stock_alerte, // Stock alert
4207  'label'=>$label,
4208  'fullpath'=>$compl_path.$label, // Label
4209  'type'=>$type, // Nb of units that compose parent product
4210  'desiredstock'=>$tmpproduct->desiredstock,
4211  'level'=>$level,
4212  'incdec'=>$incdec,
4213  'entity'=>$tmpproduct->entity
4214  );
4215 
4216  // Recursive call if there is childs to child
4217  if (is_array($desc_pere['childs'])) {
4218  //print 'YYY We go down for '.$desc_pere[3]." -> \n";
4219  $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1] * $multiply, $level + 1, $id);
4220  }
4221  }
4222  }
4223  }
4224 
4225  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4233  public function get_arbo_each_prod($multiply = 1)
4234  {
4235  // phpcs:enable
4236  $this->res = array();
4237  if (isset($this->sousprods) && is_array($this->sousprods)) {
4238  foreach ($this->sousprods as $prod_name => $desc_product) {
4239  if (is_array($desc_product)) {
4240  $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id);
4241  }
4242  }
4243  }
4244  //var_dump($this->res);
4245  return $this->res;
4246  }
4247 
4255  public function hasFatherOrChild($mode = 0)
4256  {
4257  $nb = 0;
4258 
4259  $sql = "SELECT COUNT(pa.rowid) as nb";
4260  $sql .= " FROM ".MAIN_DB_PREFIX."product_association as pa";
4261  if ($mode == 0) {
4262  $sql .= " WHERE pa.fk_product_fils = ".$this->id." OR pa.fk_product_pere = ".$this->id;
4263  } elseif ($mode == -1) {
4264  $sql .= " WHERE pa.fk_product_fils = ".$this->id; // We are a child, so we found lines that link to parents (can have several parents)
4265  } elseif ($mode == 1) {
4266  $sql .= " WHERE pa.fk_product_pere = ".$this->id; // We are a parent, so we found lines that link to children (can have several children)
4267  }
4268 
4269  $resql = $this->db->query($sql);
4270  if ($resql) {
4271  $obj = $this->db->fetch_object($resql);
4272  if ($obj) { $nb = $obj->nb; }
4273  } else {
4274  return -1;
4275  }
4276 
4277  return $nb;
4278  }
4279 
4285  public function hasVariants()
4286  {
4287  $nb = 0;
4288  $sql = "SELECT count(rowid) as nb FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_parent = ".$this->id;
4289  $sql .= " AND entity IN (".getEntity('product').")";
4290 
4291  $resql = $this->db->query($sql);
4292  if ($resql) {
4293  $obj = $this->db->fetch_object($resql);
4294  if ($obj) { $nb = $obj->nb;
4295  }
4296  }
4297 
4298  return $nb;
4299  }
4300 
4301 
4307  public function isVariant()
4308  {
4309  global $conf;
4310  if (!empty($conf->variants->enabled)) {
4311  $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_child = ".$this->id." AND entity IN (".getEntity('product').")";
4312 
4313  $query = $this->db->query($sql);
4314 
4315  if ($query) {
4316  if (!$this->db->num_rows($query)) {
4317  return false;
4318  }
4319  return true;
4320  } else {
4321  dol_print_error($this->db);
4322  return -1;
4323  }
4324  } else {
4325  return false;
4326  }
4327  }
4328 
4335  public function getFather()
4336  {
4337  $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";
4338  $sql .= " FROM ".MAIN_DB_PREFIX."product_association as pa,";
4339  $sql .= " ".MAIN_DB_PREFIX."product as p";
4340  $sql .= " WHERE p.rowid = pa.fk_product_pere";
4341  $sql .= " AND pa.fk_product_fils = ".$this->id;
4342 
4343  $res = $this->db->query($sql);
4344  if ($res) {
4345  $prods = array();
4346  while ($record = $this->db->fetch_array($res))
4347  {
4348  // $record['id'] = $record['rowid'] = id of father
4349  $prods[$record['id']]['id'] = $record['rowid'];
4350  $prods[$record['id']]['ref'] = $record['ref'];
4351  $prods[$record['id']]['label'] = $record['label'];
4352  $prods[$record['id']]['qty'] = $record['qty'];
4353  $prods[$record['id']]['incdec'] = $record['incdec'];
4354  $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
4355  $prods[$record['id']]['entity'] = $record['entity'];
4356  }
4357  return $prods;
4358  } else {
4359  dol_print_error($this->db);
4360  return -1;
4361  }
4362  }
4363 
4364 
4373  public function getChildsArbo($id, $firstlevelonly = 0, $level = 1)
4374  {
4375  global $alreadyfound;
4376 
4377  if (empty($id)) {
4378  return array();
4379  }
4380 
4381  $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
4382  $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec";
4383  $sql .= " FROM ".MAIN_DB_PREFIX."product as p,";
4384  $sql .= " ".MAIN_DB_PREFIX."product_association as pa";
4385  $sql .= " WHERE p.rowid = pa.fk_product_fils";
4386  $sql .= " AND pa.fk_product_pere = ".$id;
4387  $sql .= " AND pa.fk_product_fils != ".$id; // This should not happens, it is to avoid infinite loop if it happens
4388 
4389  dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level, LOG_DEBUG);
4390 
4391  if ($level == 1) { $alreadyfound = array($id=>1); // We init array of found object to start of tree, so if we found it later (should not happened), we stop immediatly
4392  }
4393  // Protection against infinite loop
4394  if ($level > 30) {
4395  return array();
4396  }
4397 
4398  $res = $this->db->query($sql);
4399  if ($res) {
4400  $prods = array();
4401  while ($rec = $this->db->fetch_array($res))
4402  {
4403  if (!empty($alreadyfound[$rec['rowid']])) {
4404  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);
4405  continue;
4406  }
4407  $alreadyfound[$rec['rowid']] = 1;
4408  $prods[$rec['rowid']] = array(
4409  0=>$rec['rowid'],
4410  1=>$rec['qty'],
4411  2=>$rec['fk_product_type'],
4412  3=>$this->db->escape($rec['label']),
4413  4=>$rec['incdec'],
4414  5=>$rec['ref']
4415  );
4416  //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
4417  //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
4418  if (empty($firstlevelonly)) {
4419  $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1);
4420  foreach ($listofchilds as $keyChild => $valueChild)
4421  {
4422  $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
4423  }
4424  }
4425  }
4426 
4427  return $prods;
4428  } else {
4429  dol_print_error($this->db);
4430  return -1;
4431  }
4432  }
4433 
4434  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4441  public function get_sousproduits_arbo()
4442  {
4443  // phpcs:enable
4444  $parent = array();
4445 
4446  foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
4447  {
4448  $parent[$this->label][$keyChild] = $valueChild;
4449  }
4450  foreach ($parent as $key => $value) // key=label, value is array of childs
4451  {
4452  $this->sousprods[$key] = $value;
4453  }
4454  }
4455 
4466  public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0)
4467  {
4468  global $conf, $langs, $hookmanager;
4469  include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
4470 
4471  $result = ''; $label = '';
4472 
4473  $newref = $this->ref;
4474  if ($maxlength) {
4475  $newref = dol_trunc($newref, $maxlength, 'middle');
4476  }
4477 
4478  if (!empty($this->entity)) {
4479  $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80);
4480  if ($this->nbphoto > 0) {
4481  $label .= '<div class="photointooltip">';
4482  $label .= $tmpphoto;
4483  $label .= '</div><div style="clear: both;"></div>';
4484  }
4485  }
4486 
4487  if ($this->type == Product::TYPE_PRODUCT) {
4488  $label .= img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
4489  } elseif ($this->type == Product::TYPE_SERVICE) {
4490  $label .= img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
4491  }
4492  if (isset($this->status) && isset($this->status_buy)) {
4493  $label .= ' '.$this->getLibStatut(5, 0);
4494  $label .= ' '.$this->getLibStatut(5, 1);
4495  }
4496 
4497  if (!empty($this->ref)) {
4498  $label .= '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
4499  }
4500  if (!empty($this->label)) {
4501  $label .= '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label;
4502  }
4503  if ($this->type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
4504  if (!empty($conf->productbatch->enabled)) {
4505  $langs->load("productbatch");
4506  $label .= "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
4507  }
4508  }
4509  if (!empty($conf->barcode->enabled)) {
4510  $label .= '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
4511  }
4512 
4513  if ($this->type == Product::TYPE_PRODUCT)
4514  {
4515  if ($this->weight) {
4516  $label .= "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
4517  }
4518  $labelsize = "";
4519  if ($this->length) {
4520  $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
4521  }
4522  if ($this->width) {
4523  $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
4524  }
4525  if ($this->height) {
4526  $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
4527  }
4528  if ($labelsize) $label .= "<br>".$labelsize;
4529 
4530  $labelsurfacevolume = "";
4531  if ($this->surface) {
4532  $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
4533  }
4534  if ($this->volume) {
4535  $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
4536  }
4537  if ($labelsurfacevolume) $label .= "<br>".$labelsurfacevolume;
4538  }
4539 
4540  if (!empty($conf->accounting->enabled) && $this->status) {
4541  include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
4542  $label .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
4543  $label .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
4544  $label .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
4545  }
4546  if (!empty($conf->accounting->enabled) && $this->status_buy) {
4547  include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
4548  $label .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
4549  $label .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
4550  $label .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
4551  }
4552 
4553  $linkclose = '';
4554  if (empty($notooltip)) {
4555  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
4556  $label = $langs->trans("ShowProduct");
4557  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
4558  }
4559 
4560  $linkclose .= ' title="'.dol_escape_htmltag($label, 1, 1).'"';
4561  $linkclose .= ' class="nowraponall classfortooltip"';
4562  } else {
4563  $linkclose = ' class="nowraponall"';
4564  }
4565 
4566  if ($option == 'supplier' || $option == 'category') {
4567  $url = DOL_URL_ROOT.'/product/fournisseurs.php?id='.$this->id;
4568  } elseif ($option == 'stock') {
4569  $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
4570  } elseif ($option == 'composition') {
4571  $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
4572  } else {
4573  $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
4574  }
4575 
4576  if ($option !== 'nolink') {
4577  // Add param to save lastsearch_values or not
4578  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
4579  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) { $add_save_lastsearch_values = 1;
4580  }
4581  if ($add_save_lastsearch_values) { $url .= '&save_lastsearch_values=1';
4582  }
4583  }
4584 
4585  $linkstart = '<a href="'.$url.'"';
4586  $linkstart .= $linkclose.'>';
4587  $linkend = '</a>';
4588 
4589  $result .= $linkstart;
4590  if ($withpicto)
4591  {
4592  if ($this->type == Product::TYPE_PRODUCT) {
4593  $result .= (img_object(($notooltip ? '' : $label), 'product', ($notooltip ? 'class="paddingright"' : 'class="paddingright classfortooltip"'), 0, 0, $notooltip ? 0 : 1));
4594  }
4595  if ($this->type == Product::TYPE_SERVICE) {
4596  $result .= (img_object(($notooltip ? '' : $label), 'service', ($notooltip ? 'class="paddinright"' : 'class="paddingright classfortooltip"'), 0, 0, $notooltip ? 0 : 1));
4597  }
4598  }
4599  $result .= $newref;
4600  $result .= $linkend;
4601 
4602  global $action;
4603  $hookmanager->initHooks(array('productdao'));
4604  $parameters = array('id'=>$this->id, 'getnomurl'=>$result);
4605  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4606  if ($reshook > 0) {
4607  $result = $hookmanager->resPrint;
4608  } else {
4609  $result .= $hookmanager->resPrint;
4610  }
4611 
4612  return $result;
4613  }
4614 
4615 
4626  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
4627  {
4628  global $conf, $user, $langs;
4629 
4630  $langs->load("products");
4631  $outputlangs->load("products");
4632 
4633  // Positionne le modele sur le nom du modele a utiliser
4634  if (!dol_strlen($modele)) {
4635  if (!empty($conf->global->PRODUCT_ADDON_PDF)) {
4636  $modele = $conf->global->PRODUCT_ADDON_PDF;
4637  } else {
4638  $modele = 'strato';
4639  }
4640  }
4641 
4642  $modelpath = "core/modules/product/doc/";
4643 
4644  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
4645  }
4646 
4654  public function getLibStatut($mode = 0, $type = 0)
4655  {
4656  switch ($type)
4657  {
4658  case 0:
4659  return $this->LibStatut($this->status, $mode, $type);
4660  case 1:
4661  return $this->LibStatut($this->status_buy, $mode, $type);
4662  case 2:
4663  return $this->LibStatut($this->status_batch, $mode, $type);
4664  default:
4665  //Simulate previous behavior but should return an error string
4666  return $this->LibStatut($this->status_buy, $mode, $type);
4667  }
4668  }
4669 
4670  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4679  public function LibStatut($status, $mode = 0, $type = 0)
4680  {
4681  // phpcs:enable
4682  global $conf, $langs;
4683 
4684  $labelStatus = $labelStatusShort = '';
4685 
4686  $langs->load('products');
4687  if (!empty($conf->productbatch->enabled)) { $langs->load("productbatch");
4688  }
4689 
4690  if ($type == 2) {
4691  switch ($mode)
4692  {
4693  case 0:
4694  $label = ($status == 0 ? $langs->trans('ProductStatusNotOnBatch') : $langs->trans('ProductStatusOnBatch'));
4695  return dolGetStatus($label);
4696  case 1:
4697  $label = ($status == 0 ? $langs->trans('ProductStatusNotOnBatchShort') : $langs->trans('ProductStatusOnBatchShort'));
4698  return dolGetStatus($label);
4699  case 2:
4700  return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
4701  case 3:
4702  return dolGetStatus($langs->trans('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
4703  case 4:
4704  return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
4705  case 5:
4706  return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
4707  default:
4708  return dolGetStatus($langs->trans('Unknown'));
4709  }
4710  }
4711 
4712  $statuttrans = empty($status) ? 'status5' : 'status4';
4713 
4714  if ($status == 0) {
4715  // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
4716  if ($type == 0) {
4717  $labelStatus = $langs->trans('ProductStatusNotOnSellShort');
4718  $labelStatusShort = $langs->trans('ProductStatusNotOnSell');
4719  } elseif ($type == 1) {
4720  $labelStatus = $langs->trans('ProductStatusNotOnBuyShort');
4721  $labelStatusShort = $langs->trans('ProductStatusNotOnBuy');
4722  } elseif ($type == 2) {
4723  $labelStatus = $langs->trans('ProductStatusNotOnBatch');
4724  $labelStatusShort = $langs->trans('ProductStatusNotOnBatchShort');
4725  }
4726  } elseif ($status == 1) {
4727  // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
4728  if ($type == 0) {
4729  $labelStatus = $langs->trans('ProductStatusOnSellShort');
4730  $labelStatusShort = $langs->trans('ProductStatusOnSell');
4731  } elseif ($type == 1) {
4732  $labelStatus = $langs->trans('ProductStatusOnBuyShort');
4733  $labelStatusShort = $langs->trans('ProductStatusOnBuy');
4734  } elseif ($type == 2) {
4735  $labelStatus = $langs->trans('ProductStatusOnBatch');
4736  $labelStatusShort = $langs->trans('ProductStatusOnBatchShort');
4737  }
4738  }
4739 
4740 
4741  if ($mode > 6) {
4742  return dolGetStatus($langs->trans('Unknown'), '', '', 'status0', 0);
4743  } else {
4744  return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
4745  }
4746  }
4747 
4748 
4754  public function getLibFinished()
4755  {
4756  global $langs;
4757  $langs->load('products');
4758 
4759  if (isset($this->finished) && $this->finished >= 0) {
4760  $sql = 'SELECT label, code FROM '.MAIN_DB_PREFIX.'c_product_nature where code='.((int) $this->finished).' AND active=1';
4761  $resql = $this->db->query($sql);
4762  if ($resql && $this->db->num_rows($resql) > 0) {
4763  $res = $this->db->fetch_array($resql);
4764  $label = $langs->trans($res['label']);
4765  $this->db->free($resql);
4766  return $label;
4767  } else {
4768  $this->error = $this->db->error().' sql='.$sql;
4769  dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
4770  return -1;
4771  }
4772  }
4773 
4774  return '';
4775  }
4776 
4777 
4778  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4794  public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0)
4795  {
4796  // phpcs:enable
4797  if ($id_entrepot) {
4798  $this->db->begin();
4799 
4800  include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
4801 
4802  $op[0] = "+".trim($nbpiece);
4803  $op[1] = "-".trim($nbpiece);
4804 
4805  $movementstock = new MouvementStock($this->db);
4806  $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin and ->origin->id
4807  $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
4808 
4809  if ($result >= 0) {
4810  $this->db->commit();
4811  return 1;
4812  } else {
4813  $this->error = $movementstock->error;
4814  $this->errors = $movementstock->errors;
4815 
4816  $this->db->rollback();
4817  return -1;
4818  }
4819  }
4820  }
4821 
4822  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4841  public function correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $dlc = '', $dluo = '', $lot = '', $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0)
4842  {
4843  // phpcs:enable
4844  if ($id_entrepot) {
4845  $this->db->begin();
4846 
4847  include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
4848 
4849  $op[0] = "+".trim($nbpiece);
4850  $op[1] = "-".trim($nbpiece);
4851 
4852  $movementstock = new MouvementStock($this->db);
4853  $movementstock->setOrigin($origin_element, $origin_id);
4854  $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct);
4855 
4856  if ($result >= 0) {
4857  $this->db->commit();
4858  return 1;
4859  } else {
4860  $this->error = $movementstock->error;
4861  $this->errors = $movementstock->errors;
4862 
4863  $this->db->rollback();
4864  return -1;
4865  }
4866  }
4867  }
4868 
4869  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4880  public function load_stock($option = '', $includedraftpoforvirtual = null)
4881  {
4882  // phpcs:enable
4883  global $conf;
4884 
4885  $this->stock_reel = 0;
4886  $this->stock_warehouse = array();
4887  $this->stock_theorique = 0;
4888 
4889  $warehouseStatus = array();
4890 
4891  if (preg_match('/warehouseclosed/', $option)) {
4892  $warehouseStatus[] = Entrepot::STATUS_CLOSED;
4893  }
4894  if (preg_match('/warehouseopen/', $option)) {
4895  $warehouseStatus[] = Entrepot::STATUS_OPEN_ALL;
4896  }
4897  if (preg_match('/warehouseinternal/', $option)) {
4898  $warehouseStatus[] = Entrepot::STATUS_OPEN_INTERNAL;
4899  }
4900 
4901  $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
4902  $sql .= " FROM ".MAIN_DB_PREFIX."product_stock as ps";
4903  $sql .= ", ".MAIN_DB_PREFIX."entrepot as w";
4904  $sql .= " WHERE w.entity IN (".getEntity('stock').")";
4905  $sql .= " AND w.rowid = ps.fk_entrepot";
4906  $sql .= " AND ps.fk_product = ".$this->id;
4907  if (!empty($conf->global->ENTREPOT_EXTRA_STATUS) && count($warehouseStatus)) {
4908  $sql .= " AND w.statut IN (".$this->db->sanitize($this->db->escape(implode(',', $warehouseStatus))).")";
4909  }
4910 
4911  dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
4912  $result = $this->db->query($sql);
4913  if ($result) {
4914  $num = $this->db->num_rows($result);
4915  $i = 0;
4916  if ($num > 0) {
4917  while ($i < $num)
4918  {
4919  $row = $this->db->fetch_object($result);
4920  $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
4921  $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
4922  $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
4923  if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
4924  $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
4925  }
4926  $this->stock_reel += $row->reel;
4927  $i++;
4928  }
4929  }
4930  $this->db->free($result);
4931 
4932  if (!preg_match('/novirtual/', $option)) {
4933  $this->load_virtual_stock($includedraftpoforvirtual); // This also load all arrays stats_xxx...
4934  }
4935 
4936  return 1;
4937  } else {
4938  $this->error = $this->db->lasterror();
4939  return -1;
4940  }
4941  }
4942 
4943 
4944  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4953  public function load_virtual_stock($includedraftpoforvirtual = null)
4954  {
4955  // phpcs:enable
4956  global $conf, $hookmanager, $action;
4957 
4958  $stock_commande_client = 0;
4959  $stock_commande_fournisseur = 0;
4960  $stock_sending_client = 0;
4961  $stock_reception_fournisseur = 0;
4962  $stock_inproduction = 0;
4963 
4964  //dol_syslog("load_virtual_stock");
4965 
4966  if (!empty($conf->commande->enabled))
4967  {
4968  $result = $this->load_stats_commande(0, '1,2', 1);
4969  if ($result < 0) dol_print_error($this->db, $this->error);
4970  $stock_commande_client = $this->stats_commande['qty'];
4971  }
4972  if (!empty($conf->expedition->enabled))
4973  {
4974  require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
4975  $filterShipmentStatus = '';
4976  if (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT)) {
4977  $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
4978  } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
4979  $filterShipmentStatus = Expedition::STATUS_CLOSED;
4980  }
4981  $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
4982  if ($result < 0) dol_print_error($this->db, $this->error);
4983  $stock_sending_client = $this->stats_expedition['qty'];
4984  }
4985  if (!empty($conf->fournisseur->enabled) && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) || !empty($conf->supplier_order->enabled))
4986  {
4987  $filterStatus = '1,2,3,4';
4988  if (isset($includedraftpoforvirtual)) $filterStatus = '0,'.$filterStatus;
4989  $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1);
4990  if ($result < 0) dol_print_error($this->db, $this->error);
4991  $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
4992  }
4993  if ((!empty($conf->fournisseur->enabled) && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) || !empty($conf->supplier_order->enabled) || !empty($conf->supplier_invoice->enabled)) && empty($conf->reception->enabled))
4994  {
4995  $filterStatus = '4';
4996  if (isset($includedraftpoforvirtual)) $filterStatus = '0,'.$filterStatus;
4997  $result = $this->load_stats_reception(0, $filterStatus, 1);
4998  if ($result < 0) dol_print_error($this->db, $this->error);
4999  $stock_reception_fournisseur = $this->stats_reception['qty'];
5000  }
5001  if ((!empty($conf->fournisseur->enabled) && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) || !empty($conf->supplier_order->enabled) || !empty($conf->supplier_invoice->enabled)) && empty($conf->reception->enabled))
5002  {
5003  $filterStatus = '4';
5004  if (isset($includedraftpoforvirtual)) $filterStatus = '0,'.$filterStatus;
5005  $result = $this->load_stats_reception(0, $filterStatus, 1); // Use same tables than when module reception is not used.
5006  if ($result < 0) dol_print_error($this->db, $this->error);
5007  $stock_reception_fournisseur = $this->stats_reception['qty'];
5008  }
5009  if (!empty($conf->mrp->enabled))
5010  {
5011  $result = $this->load_stats_inproduction(0, '1,2', 1);
5012  if ($result < 0) dol_print_error($this->db, $this->error);
5013  $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
5014  }
5015 
5016  $this->stock_theorique = $this->stock_reel + $stock_inproduction;
5017 
5018  // Stock decrease mode
5019  if (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
5020  $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
5021  } elseif (!empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER)) {
5022  $this->stock_theorique += 0;
5023  } elseif (!empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
5024  $this->stock_theorique -= $stock_commande_client;
5025  }
5026  // Stock Increase mode
5027  if (!empty($conf->global->STOCK_CALCULATE_ON_RECEPTION) || !empty($conf->global->STOCK_CALCULATE_ON_RECEPTION_CLOSE)) {
5028  $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5029  } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER)) {
5030  $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5031  } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER)) {
5032  $this->stock_theorique -= $stock_reception_fournisseur;
5033  } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_BILL)) {
5034  $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5035  }
5036 
5037  if (!is_object($hookmanager)) {
5038  include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
5039  $hookmanager = new HookManager($this->db);
5040  }
5041  $hookmanager->initHooks(array('productdao'));
5042  $parameters = array('id'=>$this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
5043  // Note that $action and $object may have been modified by some hooks
5044  $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
5045  if ($reshook > 0) $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
5046 
5047  return 1;
5048  }
5049 
5050 
5058  public function loadBatchInfo($batch)
5059  {
5060  $result = array();
5061 
5062  $sql = "SELECT pb.batch, pb.eatby, pb.sellby, SUM(pb.qty) AS qty FROM ".MAIN_DB_PREFIX."product_batch as pb, ".MAIN_DB_PREFIX."product_stock as ps";
5063  $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".$this->id." AND pb.batch = '".$this->db->escape($batch)."'";
5064  $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
5065  dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
5066  $resql = $this->db->query($sql);
5067  if ($resql) {
5068  $num = $this->db->num_rows($resql);
5069  $i = 0;
5070  while ($i < $num)
5071  {
5072  $obj = $this->db->fetch_object($resql);
5073  $result[] = array('batch'=>$batch, 'eatby'=>$this->db->jdate($obj->eatby), 'sellby'=>$this->db->jdate($obj->sellby), 'qty'=>$obj->qty);
5074  $i++;
5075  }
5076  return $result;
5077  } else {
5078  dol_print_error($this->db);
5079  $this->db->rollback();
5080  return array();
5081  }
5082  }
5083 
5084  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5092  public function add_photo($sdir, $file)
5093  {
5094  // phpcs:enable
5095  global $conf;
5096 
5097  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5098 
5099  $result = 0;
5100 
5101  $dir = $sdir;
5102  if (!empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) {
5103  $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
5104  } else {
5105  $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
5106  }
5107 
5108  dol_mkdir($dir);
5109 
5110  $dir_osencoded = $dir;
5111 
5112  if (is_dir($dir_osencoded)) {
5113  $originImage = $dir.'/'.$file['name'];
5114 
5115  // Cree fichier en taille origine
5116  $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
5117 
5118  if (file_exists(dol_osencode($originImage))) {
5119  // Create thumbs
5120  $this->addThumbs($originImage);
5121  }
5122  }
5123 
5124  if (is_numeric($result) && $result > 0) {
5125  return 1;
5126  } else {
5127  return -1;
5128  }
5129  }
5130 
5131  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5138  public function is_photo_available($sdir)
5139  {
5140  // phpcs:enable
5141  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5142  include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5143 
5144  global $conf;
5145 
5146  $dir = $sdir;
5147  if (!empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) {
5148  $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
5149  } else {
5150  $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
5151  }
5152 
5153  $nbphoto = 0;
5154 
5155  $dir_osencoded = dol_osencode($dir);
5156  if (file_exists($dir_osencoded)) {
5157  $handle = opendir($dir_osencoded);
5158  if (is_resource($handle)) {
5159  while (($file = readdir($handle)) !== false)
5160  {
5161  if (!utf8_check($file)) {
5162  $file = utf8_encode($file); // To be sure data is stored in UTF8 in memory
5163  }
5164  if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
5165  return true;
5166  }
5167  }
5168  }
5169  }
5170  return false;
5171  }
5172 
5173  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5181  public function liste_photos($dir, $nbmax = 0)
5182  {
5183  // phpcs:enable
5184  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5185  include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5186 
5187  $nbphoto = 0;
5188  $tabobj = array();
5189 
5190  $dir_osencoded = dol_osencode($dir);
5191  $handle = @opendir($dir_osencoded);
5192  if (is_resource($handle)) {
5193  while (($file = readdir($handle)) !== false)
5194  {
5195  if (!utf8_check($file)) { $file = utf8_encode($file); // readdir returns ISO
5196  }
5197  if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
5198  $nbphoto++;
5199 
5200  // On determine nom du fichier vignette
5201  $photo = $file;
5202  $photo_vignette = '';
5203  if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
5204  $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
5205  }
5206 
5207  $dirthumb = $dir.'thumbs/';
5208 
5209  // Objet
5210  $obj = array();
5211  $obj['photo'] = $photo;
5212  if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) { $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
5213  } else { $obj['photo_vignette'] = "";
5214  }
5215 
5216  $tabobj[$nbphoto - 1] = $obj;
5217 
5218  // On continue ou on arrete de boucler ?
5219  if ($nbmax && $nbphoto >= $nbmax) { break;
5220  }
5221  }
5222  }
5223 
5224  closedir($handle);
5225  }
5226 
5227  return $tabobj;
5228  }
5229 
5230  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5237  public function delete_photo($file)
5238  {
5239  // phpcs:enable
5240  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5241  include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5242 
5243  $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
5244  $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
5245  $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
5246 
5247  // On efface l'image d'origine
5248  dol_delete_file($file, 0, 0, 0, $this); // For triggers
5249 
5250  // Si elle existe, on efface la vignette
5251  if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
5252  $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
5253  if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
5254  dol_delete_file($dirthumb.$photo_vignette);
5255  }
5256 
5257  $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
5258  if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
5259  dol_delete_file($dirthumb.$photo_vignette);
5260  }
5261  }
5262  }
5263 
5264  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5271  public function get_image_size($file)
5272  {
5273  // phpcs:enable
5274  $file_osencoded = dol_osencode($file);
5275  $infoImg = getimagesize($file_osencoded); // Get information on image
5276  $this->imgWidth = $infoImg[0]; // Largeur de l'image
5277  $this->imgHeight = $infoImg[1]; // Hauteur de l'image
5278  }
5279 
5280  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5286  public function load_state_board()
5287  {
5288  // phpcs:enable
5289  global $conf, $user, $hookmanager;
5290 
5291  $this->nb = array();
5292 
5293  $sql = "SELECT count(p.rowid) as nb, fk_product_type";
5294  $sql .= " FROM ".MAIN_DB_PREFIX."product as p";
5295  $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
5296  // Add where from hooks
5297  if (is_object($hookmanager)) {
5298  $parameters = array();
5299  $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook
5300  $sql .= $hookmanager->resPrint;
5301  }
5302  $sql .= ' GROUP BY fk_product_type';
5303 
5304  $resql = $this->db->query($sql);
5305  if ($resql) {
5306  while ($obj = $this->db->fetch_object($resql))
5307  {
5308  if ($obj->fk_product_type == 1) { $this->nb["services"] = $obj->nb;
5309  } else { $this->nb["products"] = $obj->nb;
5310  }
5311  }
5312  $this->db->free($resql);
5313  return 1;
5314  } else {
5315  dol_print_error($this->db);
5316  $this->error = $this->db->error();
5317  return -1;
5318  }
5319  }
5320 
5326  public function isProduct()
5327  {
5328  return ($this->type == Product::TYPE_PRODUCT ? true : false);
5329  }
5330 
5336  public function isService()
5337  {
5338  return ($this->type == Product::TYPE_SERVICE ? true : false);
5339  }
5340 
5341  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5350  public function get_barcode($object, $type = '')
5351  {
5352  // phpcs:enable
5353  global $conf;
5354 
5355  $result = '';
5356  if (!empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
5357  $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
5358  foreach ($dirsociete as $dirroot)
5359  {
5360  $res = dol_include_once($dirroot.$conf->global->BARCODE_PRODUCT_ADDON_NUM.'.php');
5361  if ($res) { break;
5362  }
5363  }
5364  $var = $conf->global->BARCODE_PRODUCT_ADDON_NUM;
5365  $mod = new $var;
5366 
5367  $result = $mod->getNextValue($object, $type);
5368 
5369  dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
5370  }
5371  return $result;
5372  }
5373 
5381  public function initAsSpecimen()
5382  {
5383  global $user, $langs, $conf, $mysoc;
5384 
5385  $now = dol_now();
5386 
5387  // Initialize parameters
5388  $this->specimen = 1;
5389  $this->id = 0;
5390  $this->ref = 'PRODUCT_SPEC';
5391  $this->label = 'PRODUCT SPECIMEN';
5392  $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
5393  $this->specimen = 1;
5394  $this->country_id = 1;
5395  $this->tosell = 1;
5396  $this->tobuy = 1;
5397  $this->tobatch = 0;
5398  $this->note = 'This is a comment (private)';
5399  $this->date_creation = $now;
5400  $this->date_modification = $now;
5401 
5402  $this->weight = 4;
5403  $this->weight_units = 3;
5404 
5405  $this->length = 5;
5406  $this->length_units = 1;
5407  $this->width = 6;
5408  $this->width_units = 0;
5409  $this->height = null;
5410  $this->height_units = null;
5411 
5412  $this->surface = 30;
5413  $this->surface_units = 0;
5414  $this->volume = 300;
5415  $this->volume_units = 0;
5416 
5417  $this->barcode = -1; // Create barcode automatically
5418  }
5419 
5426  public function getLabelOfUnit($type = 'long')
5427  {
5428  global $langs;
5429 
5430  if (!$this->fk_unit) {
5431  return '';
5432  }
5433 
5434  $langs->load('products');
5435 
5436  $label_type = 'label';
5437  if ($type == 'short') {
5438  $label_type = 'short_label';
5439  }
5440 
5441  $sql = 'select '.$label_type.', code from '.MAIN_DB_PREFIX.'c_units where rowid='.$this->fk_unit;
5442  $resql = $this->db->query($sql);
5443  if ($resql && $this->db->num_rows($resql) > 0) {
5444  $res = $this->db->fetch_array($resql);
5445  $label = ($label_type == 'short_label' ? $res[$label_type] : 'unit'.$res['code']);
5446  $this->db->free($resql);
5447  return $label;
5448  } else {
5449  $this->error = $this->db->error().' sql='.$sql;
5450  dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
5451  return -1;
5452  }
5453  }
5454 
5460  public function hasbatch()
5461  {
5462  return ($this->status_batch == 1 ? true : false);
5463  }
5464 
5465 
5466  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5472  public function min_recommended_price()
5473  {
5474  // phpcs:enable
5475  global $conf;
5476 
5477  $maxpricesupplier = 0;
5478 
5479  if (!empty($conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE)) {
5480  include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
5481  $product_fourn = new ProductFournisseur($this->db);
5482  $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
5483 
5484  if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
5485  foreach ($product_fourn_list as $productfourn)
5486  {
5487  if ($productfourn->fourn_unitprice > $maxpricesupplier) {
5488  $maxpricesupplier = $productfourn->fourn_unitprice;
5489  }
5490  }
5491 
5492  $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
5493  }
5494  }
5495 
5496  return $maxpricesupplier;
5497  }
5498 
5499 
5510  public function setCategories($categories)
5511  {
5512  // Handle single category
5513  if (!is_array($categories)) {
5514  $categories = array($categories);
5515  }
5516 
5517  // Get current categories
5518  include_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
5519  $c = new Categorie($this->db);
5520  $existing = $c->containing($this->id, Categorie::TYPE_PRODUCT, 'id');
5521 
5522  // Diff
5523  if (is_array($existing)) {
5524  $to_del = array_diff($existing, $categories);
5525  $to_add = array_diff($categories, $existing);
5526  } else {
5527  $to_del = array(); // Nothing to delete
5528  $to_add = $categories;
5529  }
5530 
5531  // Process
5532  foreach ($to_del as $del) {
5533  if ($c->fetch($del) > 0) {
5534  $c->del_type($this, Categorie::TYPE_PRODUCT);
5535  }
5536  }
5537  foreach ($to_add as $add) {
5538  if ($c->fetch($add) > 0) {
5539  $c->add_type($this, Categorie::TYPE_PRODUCT);
5540  }
5541  }
5542 
5543  return;
5544  }
5545 
5554  public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
5555  {
5556  $tables = array(
5557  'product_customer_price',
5558  'product_customer_price_log'
5559  );
5560 
5561  return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
5562  }
5563 
5575  public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
5576  {
5577  global $conf, $db;
5578 
5579  $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".MAIN_DB_PREFIX."product_pricerules";
5580  $query = $this->db->query($sql);
5581 
5582  $rules = array();
5583 
5584  while ($result = $this->db->fetch_object($query)) {
5585  $rules[$result->level] = $result;
5586  }
5587 
5588  //Because prices can be based on other level's prices, we temporarily store them
5589  $prices = array(
5590  1 => $baseprice
5591  );
5592 
5593  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
5594  $price = $baseprice;
5595  $price_min = $baseprice;
5596 
5597  //We have to make sure it does exist and it is > 0
5598  //First price level only allows changing min_price
5599  if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
5600  $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
5601  }
5602 
5603  $prices[$i] = $price;
5604 
5605  //We have to make sure it does exist and it is > 0
5606  if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
5607  $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
5608  }
5609 
5610  //Little check to make sure the price is modified before triggering generation
5611  $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
5612  $check_type = ($baseprice == $this->multiprices_base_type[$i]);
5613 
5614  if ($check_amount && $check_type) {
5615  continue;
5616  }
5617 
5618  if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
5619  return -1;
5620  }
5621  }
5622 
5623  return 1;
5624  }
5625 
5631  public function getRights()
5632  {
5633  global $user;
5634 
5635  if ($this->isProduct()) {
5636  return $user->rights->produit;
5637  } else {
5638  return $user->rights->service;
5639  }
5640  }
5641 
5648  public function info($id)
5649  {
5650  $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
5651  $sql .= " p.fk_user_author, p.fk_user_modif";
5652  $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
5653  $sql .= " WHERE p.rowid = ".$id;
5654 
5655  $result = $this->db->query($sql);
5656  if ($result) {
5657  if ($this->db->num_rows($result)) {
5658  $obj = $this->db->fetch_object($result);
5659 
5660  $this->id = $obj->rowid;
5661 
5662  if ($obj->fk_user_author) {
5663  $cuser = new User($this->db);
5664  $cuser->fetch($obj->fk_user_author);
5665  $this->user_creation = $cuser;
5666  }
5667 
5668  if ($obj->fk_user_modif) {
5669  $muser = new User($this->db);
5670  $muser->fetch($obj->fk_user_modif);
5671  $this->user_modification = $muser;
5672  }
5673 
5674  $this->ref = $obj->ref;
5675  $this->date_creation = $this->db->jdate($obj->date_creation);
5676  $this->date_modification = $this->db->jdate($obj->date_modification);
5677  }
5678 
5679  $this->db->free($result);
5680  } else {
5681  dol_print_error($this->db);
5682  }
5683  }
5684 }
5685 
5686 
5687 
5693 {
5694  public $picto = 'service';
5695 }
if(!function_exists('dol_getprefix')) dol_include_once($relpath, $classname= '')
Make an include_once using default root and alternate root if it fails.
is_sousproduit($fk_parent, $fk_child)
Check if it is a sub-product into a kit.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
File of class to manage predefined price products or services by customer.
update_sousproduit($id_pere, $id_fils, $qty, $incdec=1)
Modify composed product.
load_stats_inproduction($socid=0, $filtrestatut= '', $forVirtualStock=0)
Charge tableau des stats production pour le produit/service.
$multiprices
Arrays for multiprices.
hasVariants()
Return if a product has variants or not.
Class to manage stock movements.
getLibFinished()
Retour label of nature of product.
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_vente($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter= '')
Return nb of units or customers invoices in which product is included.
getRights()
Returns the rights used for this class.
const STATUS_CLOSED
Warehouse closed, inactive.
create($user, $notrigger=0)
Insert product into database.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm= 'auto', $check=1)
Return a timestamp date built from detailed informations (by default a local PHP server timestamp) Re...
Class to parse product price expressions.
addThumbs($file)
Build thumb.
get_nb_achat($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter= '')
Return nb of units or supplier invoices in which product is included.
load_stats_commande($socid=0, $filtrestatut= '', $forVirtualStock=0)
Charge tableau des stats commande client pour le produit/service.
const STATUS_OPEN_ALL
Warehouse open and operations for customer shipping, supplier dispatch, internal stock transfers/corr...
static replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
isVariant()
Return if loaded product is a variant.
setPriceExpression($expression_id)
Sets the supplier price expression.
</td > param sortfield sortorder printFieldListOption< tdclass="liste_titremaxwidthsearchright"></td ></tr >< trclass="liste_titre">< inputtype="checkbox"onClick="toggle(this)"/> Ref p ref Label p label Duration p duration center DesiredStock p desiredstock right StockLimitShort p seuil_stock_alerte right stock_physique right stock_real_warehouse right Ordered right StockToBuy right SupplierRef right param sortfield sortorder printFieldListTitle warehouseinternal SELECT description FROM product_lang WHERE qty< br > qty qty qty StockTooLow StockTooLow help help help< trclass="oddeven">< td >< inputtype="checkbox"class="check"name="choose'.$i.'"></td >< tdclass="nowrap"> stock</td >< td >< inputtype="hidden"name="desc'.$i.'"value="'.dol_escape_htmltag($objp-> description
Only used if Module[ID]Desc translation string is not found.
Definition: replenish.php:750
load_stats_propale($socid=0)
Charge tableau des stats propale pour le produit/service.
isObjectUsed($id=0)
Function to check if an object is used by others.
get_barcode($object, $type= '')
Get a barcode from the module to generate barcode values.
correct_stock($user, $id_entrepot, $nbpiece, $movement, $label= '', $price=0, $inventorycode= '', $origin_element= '', $origin_id=null, $disablestockchangeforsubproduct=0)
Adjust stock in a warehouse for product.
$duration_unit
Exoiration unit.
loadBatchInfo($batch)
Load existing information about a serial.
$product_fourn_id
Id du fournisseur.
Class to manage products or services.
dol_now($mode= 'auto')
Return date for now.
const TYPE_ASSEMBLYKIT
Advanced feature: assembly kit.
$table_ref_field
{}
verify()
Check properties of product are ok (like name, barcode, ...).
getChildsArbo($id, $firstlevelonly=0, $level=1)
Return childs of product $id.
Class to manage Dolibarr users.
Definition: user.class.php:44
load_stats_commande_fournisseur($socid=0, $filtrestatut= '', $forVirtualStock=0)
Charge tableau des stats commande fournisseur pour le produit/service.
const TYPE_STOCKKIT
Advanced feature: stock kit.
Class to manage Dolibarr database access.
$weight
Metric of products.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
setMultiLangs($user)
Update or add a translation for a product.
isProduct()
Return if object is a product.
clone_price($fromId, $toId)
Recopie les prix d&#39;un produit/service sur un autre.
const TYPE_SERVICE
Service.
getFather()
Return all parent products for current product (first level only)
$product_id_already_linked
Product ID already linked to a reference supplier.
Class to manage products or services.
load_stats_sending($socid=0, $filtrestatut= '', $forVirtualStock=0, $filterShipmentStatus= '')
Charge tableau des stats expedition client pour le produit/service.
updatePrice($newprice, $newpricebase, $user, $newvat= '', $newminprice=0, $level=0, $newnpr=0, $newpbq=0, $ignore_autogen=0, $localtaxes_array=array(), $newdefaultvatcode= '')
Modify customer price of a product/Service.
LibStatut($status, $mode=0, $type=0)
Return label of a given status.
const TYPE_PRODUCT
Regular product.
info($id)
Load information for tab info.
is_photo_available($sdir)
Return if at least one photo is available.
get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Fonction qui renvoie si tva doit etre tva percue recuperable.
liste_photos($dir, $nbmax=0)
Retourne tableau de toutes les photos du produit.
get_localtax($vatrate, $local, $thirdparty_buyer="", $thirdparty_seller="", $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller Note: This function applies same rules than get_default_tva.
del_sousproduit($fk_parent, $fk_child)
Retire le lien entre un sousproduit et un produit/service.
get_arbo_each_prod($multiply=1)
Build the tree of subproducts into an array this-&gt;sousprods is loaded by this-&gt;get_sousproduits_arbo(...
clone_fournisseurs($fromId, $toId)
Recopie les fournisseurs et prix fournisseurs d&#39;un produit/service sur un autre.
const STATUS_CLOSED
Closed status.
$conf db
API class for accounts.
Definition: inc.php:54
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
Definition: images.lib.php:39
$desiredstock
Ask for replenishment when $desiredstock &lt; $stock_reel.
$localtax1_tx
Other local taxes.
load_stats_facture($socid=0)
Charge tableau des stats facture pour le produit/service.
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...
$default_vat_code
Default VAT code for product (link to code into llx_c_tva but without foreign keys) ...
getLabelOfUnit($type= 'long')
Returns the text label from units dictionary.
$price_by_qty
Price by quantity arrays.
insertExtraFields($trigger= '', $userused=null)
Add/Update all extra fields values for the current object.
initAsSpecimen()
Initialise an instance with random values.
dol_string_nospecial($str, $newstr= '_', $badcharstoreplace= '')
Clean a string from all punctuation characters to use it as a ref or login.
Class to manage hooks.
list_suppliers()
Return list of suppliers providing the product or service.
$tva_tx
Default VAT rate of product.
$stock_warehouse
Contains detail of stock of product into each warehouse.
add_sousproduit($id_pere, $id_fils, $qty, $incdec=1)
Link a product/service to a parent product/service.
load_stats_bom($socid=0)
Charge tableau des stats OF pour le produit/service.
load_stock($option= '', $includedraftpoforvirtual=null)
Load information about stock of a product into -&gt;stock_reel, -&gt;stock_warehouse[] (including stock_war...
add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
Add a supplier price for the product.
const STATUS_OPEN_INTERNAL
Warehouse open and operations for stock transfers/corrections allowed (not for customer shipping and ...
check_barcode($valuetotest, $typefortest)
Check barcode.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
Class to manage categories.
$tva_npr
French VAT NPR (0 or 1)
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0)
Create a document onto disk according to template module.
dol_strlen($string, $stringencoding= 'UTF-8')
Make a strlen call.
price2num($amount, $rounding= '', $option=0)
Function that return a number with universal decimal format (decimal separator is &#39;...
delete_photo($file)
Efface la photo du produit et sa vignette.
measuring_units_squared($unit)
Transform a given unit scale into the square of that unit, if known.
static commonReplaceThirdparty(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
img_picto($titlealt, $picto, $moreatt= '', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt= '', $morecss= '', $marginleftonlyshort=2)
Show picto whatever it&#39;s its name (generic function)
get_image_size($file)
Load size of image file.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename= '', $restricttologhandler= '', $logcontext=null)
Write log message into outputs.
_log_price($user, $level=0)
Insert a track that we changed a customer price.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories) ...
Definition: files.lib.php:1286
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1)
Remove a file or several files with a mask.
Definition: files.lib.php:1144
img_object($titlealt, $picto, $moreatt= '', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
min_recommended_price()
Return minimum product recommended price.
deleteExtraFields()
Delete all extra fields values for the current object.
dol_sanitizeFileName($str, $newstr= '_', $unaccent=1)
Clean a string to use it as a file name.
_get_stats($sql, $mode, $year=0)
Return an array formated for showing graphs.
utf8_check($str)
Check if a string is in UTF8.
Class ProductCombination Used to represent a product combination.
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this-&gt;array_options This method is in most cases call...
dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disablevirusscan=0, $uploaderrorcode=0, $nohook=0, $varfiles= 'addedfile', $upload_dir= '')
Make control on an uploaded file from an GUI page and move it to final destination.
Definition: files.lib.php:999
isService()
Return if object is a product.
dol_is_file($pathoffile)
Return if path is a file.
Definition: files.lib.php:457
add_photo($sdir, $file)
Move an uploaded file described into $file array into target directory $sdir.
const STATUS_VALIDATED
Validated status.
fetch($id= '', $ref= '', $ref_ext= '', $barcode= '', $ignore_expression=0, $ignore_price_load=0, $ignore_lang_load=0)
Load a product in memory from database.
print $_SERVER["PHP_SELF"]
Edit parameters.
load_virtual_stock($includedraftpoforvirtual=null)
Load value -&gt;stock_theorique of a product.
fetch_prod_arbo($prod, $compl_path="", $multiply=1, $level=1, $id_parent=0)
Fonction recursive uniquement utilisee par get_arbo_each_prod, recompose l&#39;arborescence des sousprodu...
static findAll($db, $fk_product_stock, $with_qty=0, $fk_product=0)
Return all batch detail records for a given product and warehouse.
setAccountancyCode($type, $value)
Sets an accountancy code for a product.
load_stats_reception($socid=0, $filtrestatut= '', $forVirtualStock=0)
Charge tableau des stats réception fournisseur pour le produit/service.
delMultiLangs($langtodelete, $user)
Delete a language for this product.
Manage record for batch number management.
$pmp
Average price value for product entry into stock (PMP)
dol_print_date($time, $format= '', $tzoutput= 'auto', $outputlangs= '', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
call_trigger($triggerName, $user)
Call trigger based on this instance.
update($id, $user, $notrigger=false, $action= 'update', $updatetype=false)
Update a record into database.
measuring_units_cubed($unit)
Transform a given unit scale into the cube of that unit, if known.
getNomUrl($withpicto=0, $option= '', $maxlength=0, $save_lastsearch_value=-1, $notooltip=0)
Return clicable link of object (with eventually picto)
if(!empty($conf->facture->enabled)&&$user->rights->facture->lire) if((!empty($conf->fournisseur->enabled)&&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)||!empty($conf->supplier_invoice->enabled))&&$user->rights->fournisseur->facture->lire) if(!empty($conf->don->enabled)&&$user->rights->don->lire) if(!empty($conf->tax->enabled)&&$user->rights->tax->charges->lire) if(!empty($conf->facture->enabled)&&!empty($conf->commande->enabled)&&$user->rights->commande->lire &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) if(!empty($conf->facture->enabled)&&$user->rights->facture->lire) if((!empty($conf->fournisseur->enabled)&&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)||!empty($conf->supplier_invoice->enabled))&&$user->rights->fournisseur->facture->lire) $resql
Social contributions to pay.
Definition: index.php:1232
dol_print_error($db= '', $error= '', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
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) Si ...
get_sousproduits_arbo()
Return tree of all subproducts for product.
$imgWidth
Size of image.
get_buyprice($prodfournprice, $qty, $product_id=0, $fourn_ref= '', $fk_soc=0)
Read price used by a provider.
dol_trunc($string, $size=40, $trunc= 'right', $stringencoding= 'UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding &#39;...&#39; if string larger than length.
dolGetStatus($statusLabel= '', $statusLabelShort= '', $html= '', $statusType= 'status0', $displayMode=0, $url= '', $params=array())
Output the badge of a status.
load_state_board()
Load indicators this-&gt;nb for the dashboard.
log_price_delete($user, $rowid)
Delete a price line.
clone_associations($fromId, $toId)
Clone links between products.
show_photos($modulepart, $sdir, $size=0, $nbmax=0, $nbbyrow=5, $showfilename=0, $showaction=0, $maxHeight=120, $maxWidth=160, $nolink=0, $notitle=0, $usesharelink=0)
Show photos of an object (nbmax maximum), into several columns.
get_nb_order($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter= '')
Return nb of units in orders in which product is included.
setCategories($categories)
Sets object to supplied categories.
getMultiLangs()
Load array this-&gt;multilangs.
get_nb_mos($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter= '')
Return nb of units in orders in which product is included.
correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label= '', $price=0, $dlc= '', $dluo= '', $lot= '', $inventorycode= '', $origin_element= '', $origin_id=null, $disablestockchangeforsubproduct=0)
Adjust stock in a warehouse for product with batch number.
hasFatherOrChild($mode=0)
Count all parent and children products for current product (first level only)
load_stats_contrat($socid=0)
Charge tableau des stats contrat pour le produit/service.
check()
Check that ref and label are ok.
length_accountg($account)
Return General accounting account with defined length (used for product and miscellaneous) ...
get_nb_ordersupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter= '')
Return nb of units in orders in which product is included.
getLibStatut($mode=0, $type=0)
Return label of status of object.
get_nb_propalsupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter= '')
Return nb of units in proposals in which product is included.
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition: repair.php:105
load_stats_proposal_supplier($socid=0)
Charge tableau des stats propale pour le produit/service.
getCountry($searchkey, $withcode= '', $dbtouse=0, $outputlangs= '', $entconv=1, $searchlabel= '')
Return country label, code or id from an id, code or label.
generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
Generates prices for a product based on product multiprice generation rules.
get_nb_propal($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter= '')
Return nb of units in proposals in which product is included.
Parent class of all other business classes (invoices, contracts, proposals, orders, ...)
Class to manage predefined suppliers products.
if(!empty($search_group)) natural_search(array("g.nom"g note
Definition: list.php:122
load_stats_mo($socid=0)
Charge tableau des stats OF pour le produit/service.
dol_mkdir($dir, $dataroot= '', $newmask=null)
Creation of a directory (this can create recursive subdir)
getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp=0)
Return price of sell of a product for a seller/buyer/product.
measuringUnitString($unit, $measuring_style= '', $scale= '', $use_short_label=0, $outputlangs=null)
Return translation label of a unit key.
hasbatch()
Return if object has a sell-by date or eat-by date.
__construct($db)
Constructor.