dolibarr  7.0.0-beta
product.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2001-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3  * Copyright (C) 2004-2014 Laurent Destailleur <eldy@users.sourceforge.net>
4  * Copyright (C) 2005-2015 Regis Houssin <regis.houssin@capnetworks.com>
5  * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
6  * Copyright (C) 2007-2011 Jean Heimburger <jean@tiaris.info>
7  * Copyright (C) 2010-2013 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-2017 Alexandre Spangaro <aspangaro@zendsi.com>
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-2017 Ferran Marcet <fmarcet@2byte.es>
16  * Copyright (C) 2017 Gustavo Novaro
17  *
18  * This program is free software; you can redistribute it and/or modify
19  * it under the terms of the GNU General Public License as published by
20  * the Free Software Foundation; either version 3 of the License, or
21  * (at your option) any later version.
22  *
23  * This program is distributed in the hope that it will be useful,
24  * but WITHOUT ANY WARRANTY; without even the implied warranty of
25  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26  * GNU General Public License for more details.
27  *
28  * You should have received a copy of the GNU General Public License
29  * along with this program. If not, see <http://www.gnu.org/licenses/>.
30  */
31 
37 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
38 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
39 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
40 
44 class Product extends CommonObject
45 {
46  public $element='product';
47  public $table_element='product';
48  public $fk_element='fk_product';
49  protected $childtables=array('supplier_proposaldet', 'propaldet','commandedet','facturedet','contratdet','facture_fourn_det','commande_fournisseurdet'); // To test if we can delete object
50  public $ismultientitymanaged = 1; // 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
51 
55  protected $table_ref_field = 'ref';
56 
57  public $regeximgext='\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.xpm|\.xbm'; // See also into images.lib.php
58 
59  /*
60  * @deprecated
61  * @see label
62  */
63  public $libelle;
68  public $label;
69 
74  public $description;
75 
80  public $type = self::TYPE_PRODUCT;
81 
86  public $price; // Price net
87 
92  public $price_ttc;
93 
98  public $price_min;
99 
104  public $price_min_ttc;
105 
106  /*
107  * Base price ('TTC' for price including tax or 'HT' for net price)
108  * @var float
109  */
110  public $price_base_type;
111 
113  public $multiprices=array();
114  public $multiprices_ttc=array();
115  public $multiprices_base_type=array();
116  public $multiprices_min=array();
117  public $multiprices_min_ttc=array();
118  public $multiprices_tva_tx=array();
119  public $multiprices_recuperableonly=array();
120 
123  public $prices_by_qty=array();
124  public $prices_by_qty_id=array();
125  public $prices_by_qty_list=array();
126 
129 
131  public $tva_tx;
132 
134  public $tva_npr=0;
135 
138  public $localtax2_tx;
139  public $localtax1_type;
140  public $localtax2_type;
141 
146  public $stock_reel = 0;
147 
152  public $stock_theorique;
153 
158  public $cost_price;
159 
161  public $pmp;
162 
167  public $seuil_stock_alerte;
168 
173 
174  /*
175  * Service expiration
176  */
177  public $duration_value;
178 
183 
188  public $status;
189 
194  public $status_buy;
195 
200  public $finished;
201 
206  public $status_batch;
207 
212  public $customcode;
213 
218  public $url;
219 
221  public $weight;
222  public $weight_units;
223  public $length;
224  public $length_units;
225  public $surface;
226  public $surface_units;
227  public $volume;
228  public $volume_units;
229 
230  public $accountancy_code_buy;
231  public $accountancy_code_buy_intra;
232  public $accountancy_code_buy_export;
233  public $accountancy_code_sell;
234 
240  public $barcode;
241 
246  public $barcodes_extra=array();
247 
248  public $stats_propale=array();
249  public $stats_commande=array();
250  public $stats_contrat=array();
251  public $stats_facture=array();
252  public $stats_commande_fournisseur=array();
253 
254  public $multilangs=array();
255 
257  public $imgWidth;
258  public $imgHeight;
259 
260  public $date_creation;
261  public $date_modification;
262 
265 
268 
269  public $nbphoto;
270 
272  public $stock_warehouse=array();
273 
274  public $oldcopy;
275 
276  public $fk_price_expression;
277 
282  public $buyprice;
283  public $fourn_pu;
284 
285  public $fourn_price_base_type;
286 
291  public $ref_fourn;
292  public $ref_supplier;
293 
298  public $fk_unit;
299 
304  public $price_autogen = 0;
305 
306 
310  const TYPE_PRODUCT = 0;
314  const TYPE_SERVICE = 1;
318  const TYPE_ASSEMBLYKIT = 2;
322  const TYPE_STOCKKIT = 3;
323 
324 
330  function __construct($db)
331  {
332  global $langs;
333 
334  $this->db = $db;
335  $this->status = 0;
336  $this->status_buy = 0;
337  $this->nbphoto = 0;
338  $this->stock_reel = 0;
339  $this->seuil_stock_alerte = 0;
340  $this->desiredstock = 0;
341  $this->canvas = '';
342  $this->status_batch=0;
343  }
344 
350  function check()
351  {
352  $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
353 
354  $err = 0;
355  if (dol_strlen(trim($this->ref)) == 0)
356  $err++;
357 
358  if (dol_strlen(trim($this->label)) == 0)
359  $err++;
360 
361  if ($err > 0)
362  {
363  return 0;
364  }
365  else
366  {
367  return 1;
368  }
369  }
370 
378  function create($user,$notrigger=0)
379  {
380  global $conf, $langs;
381 
382  $error=0;
383 
384  // Clean parameters
385  $this->ref = dol_string_nospecial(trim($this->ref));
386  $this->label = trim($this->label);
387  $this->price_ttc=price2num($this->price_ttc);
388  $this->price=price2num($this->price);
389  $this->price_min_ttc=price2num($this->price_min_ttc);
390  $this->price_min=price2num($this->price_min);
391  if (empty($this->tva_tx)) $this->tva_tx = 0;
392  if (empty($this->tva_npr)) $this->tva_npr = 0;
393  //Local taxes
394  if (empty($this->localtax1_tx)) $this->localtax1_tx = 0;
395  if (empty($this->localtax2_tx)) $this->localtax2_tx = 0;
396  if (empty($this->localtax1_type)) $this->localtax1_type = '0';
397  if (empty($this->localtax2_type)) $this->localtax2_type = '0';
398 
399  if (empty($this->price)) $this->price = 0;
400  if (empty($this->price_min)) $this->price_min = 0;
401 
402  // Price by quantity
403  if (empty($this->price_by_qty)) $this->price_by_qty = 0;
404 
405  if (empty($this->status)) $this->status = 0;
406  if (empty($this->status_buy)) $this->status_buy = 0;
407 
408  $price_ht=0;
409  $price_ttc=0;
410  $price_min_ht=0;
411  $price_min_ttc=0;
412 
413  //
414  if ($this->price_base_type == 'TTC' && $this->price_ttc > 0)
415  {
416  $price_ttc = price2num($this->price_ttc,'MU');
417  $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)),'MU');
418  }
419 
420  //
421  if ($this->price_base_type != 'TTC' && $this->price > 0)
422  {
423  $price_ht = price2num($this->price,'MU');
424  $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)),'MU');
425  }
426 
427  //
428  if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC'))
429  {
430  $price_min_ttc = price2num($this->price_min_ttc,'MU');
431  $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)),'MU');
432  }
433 
434  //
435  if (($this->price_min > 0) && ($this->price_base_type != 'TTC'))
436  {
437  $price_min_ht = price2num($this->price_min,'MU');
438  $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)),'MU');
439  }
440 
441  $this->accountancy_code_buy = trim($this->accountancy_code_buy);
442  $this->accountancy_code_sell= trim($this->accountancy_code_sell);
443  $this->accountancy_code_sell_intra= trim($this->accountancy_code_sell_intra);
444  $this->accountancy_code_sell_export= trim($this->accountancy_code_sell_export);
445 
446  // Barcode value
447  $this->barcode=trim($this->barcode);
448 
449  // Check parameters
450  if (empty($this->label))
451  {
452  $this->error='ErrorMandatoryParametersNotProvided';
453  return -1;
454  }
455 
456  if (empty($this->ref))
457  {
458  // Load object modCodeProduct
459  $module=(! empty($conf->global->PRODUCT_CODEPRODUCT_ADDON)?$conf->global->PRODUCT_CODEPRODUCT_ADDON:'mod_codeproduct_leopard');
460  if ($module != 'mod_codeproduct_leopard') // Do not load module file for leopard
461  {
462  if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php')
463  {
464  $module = substr($module, 0, dol_strlen($module)-4);
465  }
466  dol_include_once('/core/modules/product/'.$module.'.php');
467  $modCodeProduct = new $module;
468  if (! empty($modCodeProduct->code_auto))
469  {
470  $this->ref = $modCodeProduct->getNextValue($this,$this->type);
471  }
472  unset($modCodeProduct);
473  }
474 
475  if (empty($this->ref))
476  {
477  $this->error='ProductModuleNotSetupForAutoRef';
478  return -2;
479  }
480  }
481 
482  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);
483 
484  $now=dol_now();
485 
486  $this->db->begin();
487 
488  // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
489  if ($this->barcode == -1) $this->barcode = $this->get_barcode($this,$this->barcode_type_code);
490 
491  // Check more parameters
492  // If error, this->errors[] is filled
493  $result = $this->verify();
494 
495  if ($result >= 0)
496  {
497  $sql = "SELECT count(*) as nb";
498  $sql.= " FROM ".MAIN_DB_PREFIX."product";
499  $sql.= " WHERE entity IN (".getEntity('product').")";
500  $sql.= " AND ref = '" .$this->db->escape($this->ref)."'";
501 
502  $result = $this->db->query($sql);
503  if ($result)
504  {
505  $obj = $this->db->fetch_object($result);
506  if ($obj->nb == 0)
507  {
508  // Produit non deja existant
509  $sql = "INSERT INTO ".MAIN_DB_PREFIX."product (";
510  $sql.= "datec";
511  $sql.= ", entity";
512  $sql.= ", ref";
513  $sql.= ", ref_ext";
514  $sql.= ", price_min";
515  $sql.= ", price_min_ttc";
516  $sql.= ", label";
517  $sql.= ", fk_user_author";
518  $sql.= ", fk_product_type";
519  $sql.= ", price";
520  $sql.= ", price_ttc";
521  $sql.= ", price_base_type";
522  $sql.= ", tobuy";
523  $sql.= ", tosell";
524  $sql.= ", accountancy_code_buy";
525  $sql.= ", accountancy_code_sell";
526  $sql.= ", accountancy_code_sell_intra";
527  $sql.= ", accountancy_code_sell_export";
528  $sql.= ", canvas";
529  $sql.= ", finished";
530  $sql.= ", tobatch";
531  $sql.= ", fk_unit";
532  $sql.= ") VALUES (";
533  $sql.= "'".$this->db->idate($now)."'";
534  $sql.= ", ".$conf->entity;
535  $sql.= ", '".$this->db->escape($this->ref)."'";
536  $sql.= ", ".(! empty($this->ref_ext)?"'".$this->db->escape($this->ref_ext)."'":"null");
537  $sql.= ", ".price2num($price_min_ht);
538  $sql.= ", ".price2num($price_min_ttc);
539  $sql.= ", ".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null");
540  $sql.= ", ".$user->id;
541  $sql.= ", ".$this->type;
542  $sql.= ", ".price2num($price_ht);
543  $sql.= ", ".price2num($price_ttc);
544  $sql.= ", '".$this->db->escape($this->price_base_type)."'";
545  $sql.= ", ".$this->status;
546  $sql.= ", ".$this->status_buy;
547  $sql.= ", '".$this->db->escape($this->accountancy_code_buy)."'";
548  $sql.= ", '".$this->db->escape($this->accountancy_code_sell)."'";
549  $sql.= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
550  $sql.= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
551  $sql.= ", '".$this->db->escape($this->canvas)."'";
552  $sql.= ", ".((! isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'null' : (int) $this->finished);
553  $sql.= ", ".((empty($this->status_batch) || $this->status_batch < 0)? '0':$this->status_batch);
554  $sql.= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
555  $sql.= ")";
556 
557  dol_syslog(get_class($this)."::Create", LOG_DEBUG);
558  $result = $this->db->query($sql);
559  if ( $result )
560  {
561  $id = $this->db->last_insert_id(MAIN_DB_PREFIX."product");
562 
563  if ($id > 0)
564  {
565  $this->id = $id;
566  $this->price = $price_ht;
567  $this->price_ttc = $price_ttc;
568  $this->price_min = $price_min_ht;
569  $this->price_min_ttc = $price_min_ttc;
570 
571  $result = $this->_log_price($user);
572  if ($result > 0)
573  {
574  if ($this->update($id, $user, true, 'add') <= 0)
575  {
576  $error++;
577  }
578  }
579  else
580  {
581  $error++;
582  $this->error=$this->db->lasterror();
583  }
584  }
585  else
586  {
587  $error++;
588  $this->error='ErrorFailedToGetInsertedId';
589  }
590  }
591  else
592  {
593  $error++;
594  $this->error=$this->db->lasterror();
595  }
596  }
597  else
598  {
599  // Product already exists with this ref
600  $langs->load("products");
601  $error++;
602  $this->error = "ErrorProductAlreadyExists";
603  }
604  }
605  else
606  {
607  $error++;
608  $this->error=$this->db->lasterror();
609  }
610 
611  if (! $error && ! $notrigger)
612  {
613  // Call trigger
614  $result=$this->call_trigger('PRODUCT_CREATE',$user);
615  if ($result < 0) { $error++; }
616  // End call triggers
617  }
618 
619  if (! $error)
620  {
621  $this->db->commit();
622  return $this->id;
623  }
624  else
625  {
626  $this->db->rollback();
627  return -$error;
628  }
629  }
630  else
631  {
632  $this->db->rollback();
633  dol_syslog(get_class($this)."::Create fails verify ".join(',',$this->errors), LOG_WARNING);
634  return -3;
635  }
636 
637  }
638 
639 
646  function verify()
647  {
648  $this->errors=array();
649 
650  $result = 0;
651  $this->ref = trim($this->ref);
652 
653  if (! $this->ref)
654  {
655  $this->errors[] = 'ErrorBadRef';
656  $result = -2;
657  }
658 
659  $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
660  if ($rescode)
661  {
662  if ($rescode == -1)
663  {
664  $this->errors[] = 'ErrorBadBarCodeSyntax';
665  }
666  elseif ($rescode == -2)
667  {
668  $this->errors[] = 'ErrorBarCodeRequired';
669  }
670  elseif ($rescode == -3)
671  {
672  // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
673  $this->errors[] = 'ErrorBarCodeAlreadyUsed';
674  }
675 
676  $result = -3;
677  }
678 
679  return $result;
680  }
681 
692  function check_barcode($valuetotest,$typefortest)
693  {
694  global $conf;
695  if (! empty($conf->barcode->enabled) && ! empty($conf->global->BARCODE_PRODUCT_ADDON_NUM))
696  {
697  $module=strtolower($conf->global->BARCODE_PRODUCT_ADDON_NUM);
698 
699  $dirsociete=array_merge(array('/core/modules/barcode/'),$conf->modules_parts['barcode']);
700  foreach ($dirsociete as $dirroot)
701  {
702  $res=dol_include_once($dirroot.$module.'.php');
703  if ($res) break;
704  }
705 
706  $mod = new $module();
707 
708  dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
709  $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
710  return $result;
711  }
712  else
713  {
714  return 0;
715  }
716  }
717 
728  function update($id, $user, $notrigger=false, $action='update')
729  {
730  global $langs, $conf, $hookmanager;
731 
732  $error=0;
733 
734  // Check parameters
735  if (! $this->label) $this->label = 'MISSING LABEL';
736 
737  // Clean parameters
738  $this->ref = dol_string_nospecial(trim($this->ref));
739  $this->label = trim($this->label);
740  $this->description = trim($this->description);
741  $this->note = (isset($this->note) ? trim($this->note) : null);
742  $this->weight = price2num($this->weight);
743  $this->weight_units = trim($this->weight_units);
744  $this->length = price2num($this->length);
745  $this->length_units = trim($this->length_units);
746  $this->width = price2num($this->width);
747  $this->width_units = trim($this->width_units);
748  $this->height = price2num($this->height);
749  $this->height_units = trim($this->height_units);
750  // set unit not defined
751  if ($this->length_units) $this->width_units = $this->length_units; // Not used yet
752  if ($this->length_units) $this->height_units = $this->length_units; // Not used yet
753  // Automated compute surface and volume if not filled
754  if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units)
755  {
756  $this->surface = $this->length * $this->width;
757  $this->surface_units = measuring_units_squared($this->length_units);
758  }
759  if (empty($this->volume) && !empty($this->surface_units) && !empty($this->height) && $this->length_units == $this->height_units)
760  {
761  $this->volume = $this->surface * $this->height;
762  $this->volume_units = measuring_units_cubed($this->height_units);
763  }
764 
765  $this->surface = price2num($this->surface);
766  $this->surface_units = trim($this->surface_units);
767  $this->volume = price2num($this->volume);
768  $this->volume_units = trim($this->volume_units);
769  if (empty($this->tva_tx)) $this->tva_tx = 0;
770  if (empty($this->tva_npr)) $this->tva_npr = 0;
771  if (empty($this->localtax1_tx)) $this->localtax1_tx = 0;
772  if (empty($this->localtax2_tx)) $this->localtax2_tx = 0;
773  if (empty($this->localtax1_type)) $this->localtax1_type = '0';
774  if (empty($this->localtax2_type)) $this->localtax2_type = '0';
775  if (empty($this->status)) $this->status = 0;
776  if (empty($this->status_buy)) $this->status_buy = 0;
777 
778  if (empty($this->country_id)) $this->country_id = 0;
779 
780  // Barcode value
781  $this->barcode=trim($this->barcode);
782 
783  $this->accountancy_code_buy = trim($this->accountancy_code_buy);
784  $this->accountancy_code_sell= trim($this->accountancy_code_sell);
785  $this->accountancy_code_sell_intra= trim($this->accountancy_code_sell_intra);
786  $this->accountancy_code_sell_export= trim($this->accountancy_code_sell_export);
787 
788 
789  $this->db->begin();
790 
791  // Check name is required and codes are ok or unique.
792  // If error, this->errors[] is filled
793  if ($action != 'add')
794  {
795  $result = $this->verify(); // We don't check when update called during a create because verify was already done
796  }
797 
798  if ($result >= 0)
799  {
800  if (empty($this->oldcopy))
801  {
802  $org=new self($this->db);
803  $org->fetch($this->id);
804  $this->oldcopy=$org;
805  }
806 
807  // Test if batch management is activated on existing product
808  // If yes, we create missing entries into product_batch
809  if ($this->hasbatch() && !$this->oldcopy->hasbatch())
810  {
811  //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
812  $valueforundefinedlot = '000000';
813 
814  dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
815 
816  $this->load_stock();
817  foreach ($this->stock_warehouse as $idW => $ObjW) // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
818  {
819  $qty_batch = 0;
820  foreach ($ObjW->detail_batch as $detail) // Each lines of detail in product_batch of the current $ObjW = product_stock
821  {
822  if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined')
823  {
824  // We discard this line, we will create it later
825  $sqlclean="DELETE FROM ".MAIN_DB_PREFIX."product_batch WHERE batch in('Undefined', '".$valueforundefinedlot."') AND fk_product_stock = ".$ObjW->id;
826  $result = $this->db->query($sqlclean);
827  if (! $result)
828  {
829  dol_print_error($this->db);
830  exit;
831  }
832  continue;
833  }
834 
835  $qty_batch += $detail->qty;
836  }
837  // Quantities in batch details are not same as stock quantity,
838  // so we add a default batch record to complete and get same qty in parent and child table
839  if ($ObjW->real <> $qty_batch)
840  {
841  $ObjBatch = new Productbatch($this->db);
842  $ObjBatch->batch = $valueforundefinedlot;
843  $ObjBatch->qty = ($ObjW->real - $qty_batch);
844  $ObjBatch->fk_product_stock = $ObjW->id;
845 
846  if ($ObjBatch->create($user,1) < 0)
847  {
848  $error++;
849  $this->errors=$ObjBatch->errors;
850  }
851  }
852  }
853  }
854 
855  // For automatic creation
856  if ($this->barcode == -1) $this->barcode = $this->get_barcode($this,$this->barcode_type_code);
857 
858  $sql = "UPDATE ".MAIN_DB_PREFIX."product";
859  $sql.= " SET label = '" . $this->db->escape($this->label) ."'";
860  $sql.= ", ref = '" . $this->db->escape($this->ref) ."'";
861  $sql.= ", ref_ext = ".(! empty($this->ref_ext)?"'".$this->db->escape($this->ref_ext)."'":"null");
862  $sql.= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
863  $sql.= ", tva_tx = " . $this->tva_tx;
864  $sql.= ", recuperableonly = " . $this->tva_npr;
865  $sql.= ", localtax1_tx = " . $this->localtax1_tx;
866  $sql.= ", localtax2_tx = " . $this->localtax2_tx;
867  $sql.= ", localtax1_type = " . ($this->localtax1_type!=''?"'".$this->db->escape($this->localtax1_type)."'":"'0'");
868  $sql.= ", localtax2_type = " . ($this->localtax2_type!=''?"'".$this->db->escape($this->localtax2_type)."'":"'0'");
869 
870  $sql.= ", barcode = ". (empty($this->barcode)?"null":"'".$this->db->escape($this->barcode)."'");
871  $sql.= ", fk_barcode_type = ". (empty($this->barcode_type)?"null":$this->db->escape($this->barcode_type));
872 
873  $sql.= ", tosell = " . $this->status;
874  $sql.= ", tobuy = " . $this->status_buy;
875  $sql.= ", tobatch = " . ((empty($this->status_batch) || $this->status_batch < 0) ? '0' : $this->status_batch);
876  $sql.= ", finished = " . ((! isset($this->finished) || $this->finished < 0) ? "null" : (int) $this->finished);
877  $sql.= ", weight = " . ($this->weight!='' ? "'".$this->db->escape($this->weight)."'" : 'null');
878  $sql.= ", weight_units = " . ($this->weight_units!='' ? "'".$this->db->escape($this->weight_units)."'": 'null');
879  $sql.= ", length = " . ($this->length!='' ? "'".$this->db->escape($this->length)."'" : 'null');
880  $sql.= ", length_units = " . ($this->length_units!='' ? "'".$this->db->escape($this->length_units)."'" : 'null');
881  $sql.= ", width= " . ($this->width!='' ? "'".$this->db->escape($this->width)."'" : 'null');
882  $sql.= ", width_units = " . ($this->width_units!='' ? "'".$this->db->escape($this->width_units)."'" : 'null');
883  $sql.= ", height = " . ($this->height!='' ? "'".$this->db->escape($this->height)."'" : 'null');
884  $sql.= ", height_units = " . ($this->height_units!='' ? "'".$this->db->escape($this->height_units)."'" : 'null');
885  $sql.= ", surface = " . ($this->surface!='' ? "'".$this->db->escape($this->surface)."'" : 'null');
886  $sql.= ", surface_units = " . ($this->surface_units!='' ? "'".$this->db->escape($this->surface_units)."'" : 'null');
887  $sql.= ", volume = " . ($this->volume!='' ? "'".$this->db->escape($this->volume)."'" : 'null');
888  $sql.= ", volume_units = " . ($this->volume_units!='' ? "'".$this->db->escape($this->volume_units)."'" : 'null');
889  $sql.= ", seuil_stock_alerte = " . ((isset($this->seuil_stock_alerte) && $this->seuil_stock_alerte != '') ? "'".$this->db->escape($this->seuil_stock_alerte)."'" : "null");
890  $sql.= ", description = '" . $this->db->escape($this->description) ."'";
891  $sql.= ", url = " . ($this->url?"'".$this->db->escape($this->url)."'":'null');
892  $sql.= ", customcode = '" . $this->db->escape($this->customcode) ."'";
893  $sql.= ", fk_country = " . ($this->country_id > 0 ? $this->country_id : 'null');
894  $sql.= ", note = ".(isset($this->note) ? "'" .$this->db->escape($this->note)."'" : 'null');
895  $sql.= ", duration = '" . $this->db->escape($this->duration_value . $this->duration_unit) ."'";
896  $sql.= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy)."'";
897  $sql.= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell)."'";
898  $sql.= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra)."'";
899  $sql.= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export)."'";
900  $sql.= ", desiredstock = " . ((isset($this->desiredstock) && $this->desiredstock != '') ? $this->desiredstock : "null");
901  $sql.= ", cost_price = " . ($this->cost_price != '' ? $this->db->escape($this->cost_price) : 'null');
902  $sql.= ", fk_unit= " . (!$this->fk_unit ? 'NULL' : $this->fk_unit);
903  $sql.= ", price_autogen = " . (!$this->price_autogen ? 0 : 1);
904  $sql.= ", fk_price_expression = ".($this->fk_price_expression != 0 ? $this->fk_price_expression : 'NULL');
905  $sql.= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL');
906  // stock field is not here because it is a denormalized value from product_stock.
907  $sql.= " WHERE rowid = " . $id;
908 
909  dol_syslog(get_class($this)."::update", LOG_DEBUG);
910 
911  $resql=$this->db->query($sql);
912  if ($resql)
913  {
914  $this->id = $id;
915 
916  // Multilangs
917  if (! empty($conf->global->MAIN_MULTILANGS))
918  {
919  if ( $this->setMultiLangs($user) < 0)
920  {
921  $this->error=$langs->trans("Error")." : ".$this->db->error()." - ".$sql;
922  return -2;
923  }
924  }
925 
926  $action='update';
927 
928  // Actions on extra fields (by external module or standard code)
929  $hookmanager->initHooks(array('productdao'));
930  $parameters=array('id'=>$this->id);
931  $reshook=$hookmanager->executeHooks('insertExtraFields',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks
932  if (empty($reshook))
933  {
934  if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
935  {
936  $result=$this->insertExtraFields();
937  if ($result < 0)
938  {
939  $error++;
940  }
941  }
942  }
943  else if ($reshook < 0) $error++;
944 
945  if (! $error && ! $notrigger)
946  {
947  // Call trigger
948  $result=$this->call_trigger('PRODUCT_MODIFY',$user);
949  if ($result < 0) { $error++; }
950  // End call triggers
951  }
952 
953  if (! $error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref))
954  {
955  // We remove directory
956  if ($conf->product->dir_output)
957  {
958  $olddir = $conf->product->dir_output . "/" . dol_sanitizeFileName($this->oldcopy->ref);
959  $newdir = $conf->product->dir_output . "/" . dol_sanitizeFileName($this->ref);
960  if (file_exists($olddir))
961  {
962  //include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
963  //$res = dol_move($olddir, $newdir);
964  // do not use dol_move with directory
965  $res = @rename($olddir, $newdir);
966  if (! $res)
967  {
968  $langs->load("errors");
969  $this->error=$langs->trans('ErrorFailToRenameDir',$olddir,$newdir);
970  $error++;
971  }
972  }
973  }
974  }
975 
976  if (! $error)
977  {
978  if ($conf->variants->enabled) {
979 
980  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
981 
982  $comb = new ProductCombination($this->db);
983 
984  foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
985  $currcomb->updateProperties($this);
986  }
987  }
988 
989  $this->db->commit();
990  return 1;
991  }
992  else
993  {
994  $this->db->rollback();
995  return -$error;
996  }
997  }
998  else
999  {
1000  if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS')
1001  {
1002  $langs->load("errors");
1003  if (empty($conf->barcode->enabled)) $this->error=$langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists",$this->ref);
1004  else $this->error=$langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists",$this->barcode);
1005  $this->errors[]=$this->error;
1006  $this->db->rollback();
1007  return -1;
1008  }
1009  else
1010  {
1011  $this->error=$langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1012  $this->errors[]=$this->error;
1013  $this->db->rollback();
1014  return -2;
1015  }
1016  }
1017  }
1018  else
1019  {
1020  $this->db->rollback();
1021  dol_syslog(get_class($this)."::Update fails verify ".join(',',$this->errors), LOG_WARNING);
1022  return -3;
1023  }
1024  }
1025 
1033  function delete(User $user, $notrigger=0)
1034  {
1035  // Deprecation warning
1036  if ($id > 0) {
1037  dol_syslog(__METHOD__ . " with parameter is deprecated", LOG_WARNING);
1038  }
1039 
1040  global $conf, $langs;
1041  require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1042 
1043  $error=0;
1044 
1045  // Clean parameters
1046  if (empty($id)) $id=$this->id;
1047  else $this->fetch($id);
1048 
1049  // Check parameters
1050  if (empty($id))
1051  {
1052  $this->error = "Object must be fetched before calling delete";
1053  return -1;
1054  }
1055  if (($this->type == Product::TYPE_PRODUCT && empty($user->rights->produit->supprimer)) || ($this->type == Product::TYPE_SERVICE && empty($user->rights->service->supprimer)))
1056  {
1057  $this->error = "ErrorForbidden";
1058  return 0;
1059  }
1060 
1061  $objectisused = $this->isObjectUsed($id);
1062  if (empty($objectisused))
1063  {
1064  $this->db->begin();
1065 
1066  if (! $error && empty($notrigger))
1067  {
1068  // Call trigger
1069  $result=$this->call_trigger('PRODUCT_DELETE',$user);
1070  if ($result < 0) { $error++; }
1071  // End call triggers
1072  }
1073 
1074  // Delete from product_batch on product delete
1075  if (! $error)
1076  {
1077  $sql = "DELETE FROM ".MAIN_DB_PREFIX.'product_batch';
1078  $sql.= " WHERE fk_product_stock IN (";
1079  $sql.= "SELECT rowid FROM ".MAIN_DB_PREFIX.'product_stock';
1080  $sql.= " WHERE fk_product = ".$id.")";
1081  dol_syslog(get_class($this).'::delete', LOG_DEBUG);
1082  $result = $this->db->query($sql);
1083  if (! $result)
1084  {
1085  $error++;
1086  $this->errors[] = $this->db->lasterror();
1087  }
1088  }
1089 
1090  // Delete all child tables
1091  if (! $error)
1092  {
1093  $elements = array('product_fournisseur_price','product_price','product_lang','categorie_product','product_stock','product_customer_price','product_lot'); // product_batch is done before
1094  foreach($elements as $table)
1095  {
1096  if (! $error)
1097  {
1098  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$table;
1099  $sql.= " WHERE fk_product = ".$id;
1100  dol_syslog(get_class($this).'::delete', LOG_DEBUG);
1101  $result = $this->db->query($sql);
1102  if (! $result)
1103  {
1104  $error++;
1105  $this->errors[] = $this->db->lasterror();
1106  }
1107  }
1108  }
1109  }
1110 
1111  if (!$error) {
1112 
1113  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1114  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1115 
1116  //If it is a parent product, then we remove the association with child products
1117  $prodcomb = new ProductCombination($this->db);
1118 
1119  if ($prodcomb->deleteByFkProductParent($id) < 0) {
1120  $error++;
1121  $this->errors[] = 'Error deleting combinations';
1122  }
1123 
1124  //We also check if it is a child product
1125  if (!$error && ($prodcomb->fetchByFkProductChild($id) > 0) && ($prodcomb->delete($user) < 0)) {
1126  $error++;
1127  $this->errors[] = 'Error deleting child combination';
1128  }
1129  }
1130 
1131  // Delete product
1132  if (! $error)
1133  {
1134  $sqlz = "DELETE FROM ".MAIN_DB_PREFIX."product";
1135  $sqlz.= " WHERE rowid = ".$id;
1136  dol_syslog(get_class($this).'::delete', LOG_DEBUG);
1137  $resultz = $this->db->query($sqlz);
1138  if ( ! $resultz )
1139  {
1140  $error++;
1141  $this->errors[] = $this->db->lasterror();
1142  }
1143  }
1144 
1145  if (! $error)
1146  {
1147  // We remove directory
1148  $ref = dol_sanitizeFileName($this->ref);
1149  if ($conf->product->dir_output)
1150  {
1151  $dir = $conf->product->dir_output . "/" . $ref;
1152  if (file_exists($dir))
1153  {
1154  $res=@dol_delete_dir_recursive($dir);
1155  if (! $res)
1156  {
1157  $this->errors[] = 'ErrorFailToDeleteDir';
1158  $error++;
1159  }
1160  }
1161  }
1162  }
1163 
1164  // Remove extrafields
1165  if ((! $error) && (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED))) // For avoid conflicts if trigger used
1166  {
1167  $result=$this->deleteExtraFields();
1168  if ($result < 0)
1169  {
1170  $error++;
1171  dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1172  }
1173  }
1174 
1175  if (! $error)
1176  {
1177  $this->db->commit();
1178  return 1;
1179  }
1180  else
1181  {
1182  foreach($this->errors as $errmsg)
1183  {
1184  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1185  $this->error.=($this->error?', '.$errmsg:$errmsg);
1186  }
1187  $this->db->rollback();
1188  return -$error;
1189  }
1190  }
1191  else
1192  {
1193  $this->error = "ErrorRecordIsUsedCantDelete";
1194  return 0;
1195  }
1196  }
1197 
1204  function setMultiLangs($user)
1205  {
1206  global $conf, $langs;
1207 
1208  $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1209  $current_lang = $langs->getDefaultLang();
1210 
1211  foreach ($langs_available as $key => $value)
1212  {
1213  if ($key == $current_lang)
1214  {
1215  $sql = "SELECT rowid";
1216  $sql.= " FROM ".MAIN_DB_PREFIX."product_lang";
1217  $sql.= " WHERE fk_product=".$this->id;
1218  $sql.= " AND lang='".$key."'";
1219 
1220  $result = $this->db->query($sql);
1221 
1222  if ($this->db->num_rows($result)) // if there is already a description line for this language
1223  {
1224  $sql2 = "UPDATE ".MAIN_DB_PREFIX."product_lang";
1225  $sql2.= " SET ";
1226  $sql2.= " label='".$this->db->escape($this->label)."',";
1227  $sql2.= " description='".$this->db->escape($this->description)."'";
1228  if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) $sql2.= ", note='".$this->db->escape($this->note)."'";
1229  $sql2.= " WHERE fk_product=".$this->id." AND lang='".$this->db->escape($key)."'";
1230  }
1231  else
1232  {
1233  $sql2 = "INSERT INTO ".MAIN_DB_PREFIX."product_lang (fk_product, lang, label, description";
1234  if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) $sql2.=", note";
1235  $sql2.= ")";
1236  $sql2.= " VALUES(".$this->id.",'".$this->db->escape($key)."','". $this->db->escape($this->label)."',";
1237  $sql2.= " '".$this->db->escape($this->description)."'";
1238  if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) $sql2.= ", '".$this->db->escape($this->note)."'";
1239  $sql2.= ")";
1240  }
1241  dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
1242  if (! $this->db->query($sql2))
1243  {
1244  $this->error=$this->db->lasterror();
1245  return -1;
1246  }
1247  }
1248  else if (isset($this->multilangs[$key]))
1249  {
1250  $sql = "SELECT rowid";
1251  $sql.= " FROM ".MAIN_DB_PREFIX."product_lang";
1252  $sql.= " WHERE fk_product=".$this->id;
1253  $sql.= " AND lang='".$key."'";
1254 
1255  $result = $this->db->query($sql);
1256 
1257  if ($this->db->num_rows($result)) // if there is already a description line for this language
1258  {
1259  $sql2 = "UPDATE ".MAIN_DB_PREFIX."product_lang";
1260  $sql2.= " SET ";
1261  $sql2.= " label='".$this->db->escape($this->multilangs["$key"]["label"])."',";
1262  $sql2.= " description='".$this->db->escape($this->multilangs["$key"]["description"])."'";
1263  if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) $sql2.= ", note='".$this->db->escape($this->multilangs["$key"]["note"])."'";
1264  $sql2.= " WHERE fk_product=".$this->id." AND lang='".$this->db->escape($key)."'";
1265  }
1266  else
1267  {
1268  $sql2 = "INSERT INTO ".MAIN_DB_PREFIX."product_lang (fk_product, lang, label, description";
1269  if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) $sql2.=", note";
1270  $sql2.= ")";
1271  $sql2.= " VALUES(".$this->id.",'".$this->db->escape($key)."','". $this->db->escape($this->multilangs["$key"]["label"])."',";
1272  $sql2.= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1273  if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) $sql2.= ", '".$this->db->escape($this->note)."'";
1274  $sql2.= ")";
1275  }
1276 
1277  // We do not save if main fields are empty
1278  if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"])
1279  {
1280  if (! $this->db->query($sql2))
1281  {
1282  $this->error=$this->db->lasterror();
1283  return -1;
1284  }
1285  }
1286  }
1287  else
1288  {
1289  // language is not current language and we didn't provide a multilang description for this language
1290  }
1291  }
1292 
1293  // Call trigger
1294  $result = $this->call_trigger('PRODUCT_SET_MULTILANGS',$user);
1295  if ($result < 0) {
1296  $this->error = $this->db->lasterror();
1297  return -1;
1298  }
1299  // End call triggers
1300 
1301  return 1;
1302  }
1303 
1312  function delMultiLangs($langtodelete, $user)
1313  {
1314  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_lang";
1315  $sql.= " WHERE fk_product=".$this->id." AND lang='".$this->db->escape($langtodelete)."'";
1316 
1317  dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
1318  $result = $this->db->query($sql);
1319  if ($result)
1320  {
1321  // Call trigger
1322  $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS',$user);
1323  if ($result < 0) {
1324  $this->error = $this->db->lasterror();
1325  dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1326  return -1;
1327  }
1328  // End call triggers
1329  return 1;
1330  }
1331  else
1332  {
1333  $this->error=$this->db->lasterror();
1334  dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1335  return -1;
1336  }
1337  }
1338 
1339  /*
1340  * Sets an accountancy code for a product.
1341  * Also calls PRODUCT_MODIFY trigger when modified
1342  *
1343  * @param string $type It can be 'buy', 'sell', 'sell_intra' or 'sell_export'
1344  * @param string $value Accountancy code
1345  * @return int <0 KO >0 OK
1346  */
1347  public function setAccountancyCode($type, $value)
1348  {
1349  global $user, $langs, $conf;
1350 
1351  $this->db->begin();
1352 
1353  if ($type == 'buy') {
1354  $field = 'accountancy_code_buy';
1355  } elseif ($type == 'sell') {
1356  $field = 'accountancy_code_sell';
1357  } elseif ($type == 'sell_intra') {
1358  $field = 'accountancy_code_sell_intra';
1359  } elseif ($type == 'sell_export') {
1360  $field = 'accountancy_code_sell_export';
1361  } else {
1362  return -1;
1363  }
1364 
1365  $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET ";
1366  $sql.= "$field = '".$this->db->escape($value)."'";
1367  $sql.= " WHERE rowid = ".$this->id;
1368 
1369  dol_syslog(get_class($this)."::".__FUNCTION__." sql=".$sql, LOG_DEBUG);
1370  $resql = $this->db->query($sql);
1371 
1372  if ($resql)
1373  {
1374  // Call triggers
1375  include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
1376  $interface=new Interfaces($this->db);
1377  $result=$interface->run_triggers('PRODUCT_MODIFY',$this,$user,$langs,$conf);
1378  if ($result < 0)
1379  {
1380  $this->errors=$interface->errors;
1381  $this->db->rollback();
1382  return -1;
1383  }
1384  // End call triggers
1385 
1386  $this->$field = $value;
1387 
1388  $this->db->commit();
1389  return 1;
1390  }
1391  else
1392  {
1393  $this->error=$this->db->lasterror();
1394  $this->db->rollback();
1395  return -1;
1396  }
1397  }
1398 
1404  function getMultiLangs()
1405  {
1406  global $langs;
1407 
1408  $current_lang = $langs->getDefaultLang();
1409 
1410  $sql = "SELECT lang, label, description, note as other";
1411  $sql.= " FROM ".MAIN_DB_PREFIX."product_lang";
1412  $sql.= " WHERE fk_product=".$this->id;
1413 
1414  $result = $this->db->query($sql);
1415  if ($result)
1416  {
1417  while ($obj = $this->db->fetch_object($result))
1418  {
1419  //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
1420  if ($obj->lang == $current_lang) // si on a les traduct. dans la langue courante on les charge en infos principales.
1421  {
1422  $this->label = $obj->label;
1423  $this->description = $obj->description;
1424  $this->other = $obj->other;
1425  }
1426  $this->multilangs["$obj->lang"]["label"] = $obj->label;
1427  $this->multilangs["$obj->lang"]["description"] = $obj->description;
1428  $this->multilangs["$obj->lang"]["other"] = $obj->other;
1429  }
1430  return 1;
1431  }
1432  else
1433  {
1434  $this->error="Error: ".$this->db->lasterror()." - ".$sql;
1435  return -1;
1436  }
1437  }
1438 
1439 
1440 
1448  function _log_price($user,$level=0)
1449  {
1450  global $conf;
1451 
1452  $now=dol_now();
1453 
1454  // Clean parameters
1455  if (empty($this->price_by_qty)) $this->price_by_qty=0;
1456 
1457  // Add new price
1458  $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,";
1459  $sql.= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
1460  $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.",";
1461  $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');
1462  $sql.= ")";
1463 
1464  dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
1465  $resql=$this->db->query($sql);
1466  if(! $resql)
1467  {
1468  $this->error=$this->db->lasterror();
1469  dol_print_error($this->db);
1470  return -1;
1471  }
1472  else
1473  {
1474  return 1;
1475  }
1476  }
1477 
1478 
1486  function log_price_delete($user, $rowid)
1487  {
1488  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_price_by_qty";
1489  $sql.= " WHERE fk_product_price=".$rowid;
1490  $resql=$this->db->query($sql);
1491 
1492  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_price";
1493  $sql.= " WHERE rowid=".$rowid;
1494  $resql=$this->db->query($sql);
1495  if ($resql)
1496  {
1497  return 1;
1498  }
1499  else
1500  {
1501  $this->error=$this->db->lasterror();
1502  return -1;
1503  }
1504  }
1505 
1506 
1519  function get_buyprice($prodfournprice, $qty, $product_id=0, $fourn_ref='', $fk_soc=0)
1520  {
1521  global $conf;
1522  $result = 0;
1523 
1524  // 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)
1525  $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent,";
1526  $sql.= " pfp.fk_product, pfp.ref_fourn, pfp.fk_soc, pfp.tva_tx, pfp.fk_supplier_price_expression";
1527  $sql.= " ,pfp.default_vat_code";
1528  $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
1529  $sql.= " WHERE pfp.rowid = ".$prodfournprice;
1530  if ($qty > 0) $sql.= " AND pfp.quantity <= ".$qty;
1531  $sql.= " ORDER BY pfp.quantity DESC";
1532 
1533  dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
1534  $resql = $this->db->query($sql);
1535  if ($resql)
1536  {
1537  $obj = $this->db->fetch_object($resql);
1538  if ($obj && $obj->quantity > 0) // If we found a supplier prices from the id of supplier price
1539  {
1540  if (!empty($conf->dynamicprices->enabled) && !empty($obj->fk_supplier_price_expression))
1541  {
1542  require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
1543  $prod_supplier = new ProductFournisseur($this->db);
1544  $prod_supplier->product_fourn_price_id = $obj->rowid;
1545  $prod_supplier->id = $obj->fk_product;
1546  $prod_supplier->fourn_qty = $obj->quantity;
1547  $prod_supplier->fourn_tva_tx = $obj->tva_tx;
1548  $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
1549  $priceparser = new PriceParser($this->db);
1550  $price_result = $priceparser->parseProductSupplier($prod_supplier);
1551  if ($price_result >= 0) {
1552  $obj->price = $price_result;
1553  }
1554  }
1555  $this->product_fourn_price_id = $obj->rowid;
1556  $this->buyprice = $obj->price; // deprecated
1557  $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
1558  $this->fourn_price_base_type = 'HT'; // Price base type
1559  $this->ref_fourn = $obj->ref_fourn; // deprecated
1560  $this->ref_supplier = $obj->ref_fourn; // Ref supplier
1561  $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
1562  $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
1563  $this->default_vat_code = $obj->default_vat_code; // Vat code supplier
1564  $result=$obj->fk_product;
1565  return $result;
1566  }
1567  else // If not found
1568  {
1569  // 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.
1570  $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.fk_soc,";
1571  $sql.= " pfp.fk_product, pfp.ref_fourn as ref_supplier, pfp.tva_tx, pfp.fk_supplier_price_expression";
1572  $sql.= " ,pfp.default_vat_code";
1573  $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
1574  $sql.= " WHERE pfp.fk_product = ".$product_id;
1575  if ($fourn_ref != 'none') $sql.= " AND pfp.ref_fourn = '".$fourn_ref."'";
1576  if ($fk_soc > 0) $sql.= " AND pfp.fk_soc = ".$fk_soc;
1577  if ($qty > 0) $sql.= " AND pfp.quantity <= ".$qty;
1578  $sql.= " ORDER BY pfp.quantity DESC";
1579  $sql.= " LIMIT 1";
1580 
1581  dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
1582  $resql = $this->db->query($sql);
1583  if ($resql)
1584  {
1585  $obj = $this->db->fetch_object($resql);
1586  if ($obj && $obj->quantity > 0) // If found
1587  {
1588  if (!empty($conf->dynamicprices->enabled) && !empty($obj->fk_supplier_price_expression))
1589  {
1590  require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
1591  $prod_supplier = new ProductFournisseur($this->db);
1592  $prod_supplier->product_fourn_price_id = $obj->rowid;
1593  $prod_supplier->id = $obj->fk_product;
1594  $prod_supplier->fourn_qty = $obj->quantity;
1595  $prod_supplier->fourn_tva_tx = $obj->tva_tx;
1596  $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
1597  $priceparser = new PriceParser($this->db);
1598  $price_result = $priceparser->parseProductSupplier($prod_supplier);
1599  if ($result >= 0) {
1600  $obj->price = $price_result;
1601  }
1602  }
1603  $this->product_fourn_price_id = $obj->rowid;
1604  $this->buyprice = $obj->price; // deprecated
1605  $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
1606  $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
1607  $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
1608  $this->ref_fourn = $obj->ref_supplier; // deprecated
1609  $this->ref_supplier = $obj->ref_supplier; // Ref supplier
1610  $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
1611  $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
1612  $this->default_vat_code = $obj->default_vat_code; // Vat code supplier
1613  $result=$obj->fk_product;
1614  return $result;
1615  }
1616  else
1617  {
1618  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é.
1619  }
1620  }
1621  else
1622  {
1623  $this->error=$this->db->lasterror();
1624  return -3;
1625  }
1626  }
1627  }
1628  else
1629  {
1630  $this->error=$this->db->lasterror();
1631  return -2;
1632  }
1633  }
1634 
1635 
1652  function updatePrice($newprice, $newpricebase, $user, $newvat='',$newminprice='', $level=0, $newnpr=0, $newpbq=0, $ignore_autogen=0, $localtaxes_array=array(), $newdefaultvatcode='')
1653  {
1654  global $conf,$langs;
1655 
1656  $id=$this->id;
1657 
1658  dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
1659 
1660  // Clean parameters
1661  if (empty($this->tva_tx)) $this->tva_tx=0;
1662  if (empty($newnpr)) $newnpr=0;
1663 
1664  // Check parameters
1665  if ($newvat == '') $newvat=$this->tva_tx;
1666 
1667  // If multiprices are enabled, then we check if the current product is subject to price autogeneration
1668  // Price will be modified ONLY when the first one is the one that is being modified
1669  if (!empty($conf->global->PRODUIT_MULTIPRICES) && !$ignore_autogen && $this->price_autogen && ($level == 1))
1670  {
1671  return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
1672  }
1673 
1674  if (! empty($newminprice) && ($newminprice > $newprice))
1675  {
1676  $this->error='ErrorPriceCantBeLowerThanMinPrice';
1677  return -1;
1678  }
1679 
1680  if ($newprice!='' || $newprice==0)
1681  {
1682  if ($newpricebase == 'TTC')
1683  {
1684  $price_ttc = price2num($newprice,'MU');
1685  $price = price2num($newprice) / (1 + ($newvat / 100));
1686  $price = price2num($price,'MU');
1687 
1688  if ($newminprice!='' || $newminprice==0)
1689  {
1690  $price_min_ttc = price2num($newminprice,'MU');
1691  $price_min = price2num($newminprice) / (1 + ($newvat / 100));
1692  $price_min = price2num($price_min,'MU');
1693  }
1694  else
1695  {
1696  $price_min=0;
1697  $price_min_ttc=0;
1698  }
1699  }
1700  else
1701  {
1702  $price = price2num($newprice,'MU');
1703  $price_ttc = ( $newnpr != 1 ) ? price2num($newprice) * (1 + ($newvat / 100)) : $price;
1704  $price_ttc = price2num($price_ttc,'MU');
1705 
1706  if ($newminprice!='' || $newminprice==0)
1707  {
1708  $price_min = price2num($newminprice,'MU');
1709  $price_min_ttc = price2num($newminprice) * (1 + ($newvat / 100));
1710  $price_min_ttc = price2num($price_min_ttc,'MU');
1711  //print 'X'.$newminprice.'-'.$price_min;
1712  }
1713  else
1714  {
1715  $price_min=0;
1716  $price_min_ttc=0;
1717  }
1718  }
1719  //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
1720 
1721  if (count($localtaxes_array) > 0)
1722  {
1723  $localtaxtype1=$localtaxes_array['0'];
1724  $localtax1=$localtaxes_array['1'];
1725  $localtaxtype2=$localtaxes_array['2'];
1726  $localtax2=$localtaxes_array['3'];
1727  }
1728  else // old method. deprecated because ot can't retreive type
1729  {
1730  $localtaxtype1='0';
1731  $localtax1=get_localtax($newvat,1);
1732  $localtaxtype2='0';
1733  $localtax2=get_localtax($newvat,2);
1734  }
1735  if (empty($localtax1)) $localtax1=0; // If = '' then = 0
1736  if (empty($localtax2)) $localtax2=0; // If = '' then = 0
1737 
1738  $this->db->begin();
1739 
1740  // Ne pas mettre de quote sur les numeriques decimaux.
1741  // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
1742  $sql = "UPDATE ".MAIN_DB_PREFIX."product SET";
1743  $sql.= " price_base_type='".$newpricebase."',";
1744  $sql.= " price=".$price.",";
1745  $sql.= " price_ttc=".$price_ttc.",";
1746  $sql.= " price_min=".$price_min.",";
1747  $sql.= " price_min_ttc=".$price_min_ttc.",";
1748  $sql.= " localtax1_tx=".($localtax1>=0?$localtax1:'NULL').",";
1749  $sql.= " localtax2_tx=".($localtax2>=0?$localtax2:'NULL').",";
1750  $sql.= " localtax1_type=".($localtaxtype1!=''?"'".$localtaxtype1."'":"'0'").",";
1751  $sql.= " localtax2_type=".($localtaxtype2!=''?"'".$localtaxtype2."'":"'0'").",";
1752  $sql.= " default_vat_code=".($newdefaultvatcode?"'".$this->db->escape($newdefaultvatcode)."'":"null").",";
1753  $sql.= " tva_tx='".price2num($newvat)."',";
1754  $sql.= " recuperableonly='".$newnpr."'";
1755  $sql.= " WHERE rowid = ".$id;
1756 
1757  dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
1758  $resql=$this->db->query($sql);
1759  if ($resql)
1760  {
1761  $this->multiprices[$level] = $price;
1762  $this->multiprices_ttc[$level] = $price_ttc;
1763  $this->multiprices_min[$level]= $price_min;
1764  $this->multiprices_min_ttc[$level]= $price_min_ttc;
1765  $this->multiprices_base_type[$level]= $newpricebase;
1766  $this->multiprices_default_vat_code[$level]= $newdefaultvatcode;
1767  $this->multiprices_tva_tx[$level]= $newvat;
1768  $this->multiprices_recuperableonly[$level]= $newnpr;
1769 
1770  $this->price = $price;
1771  $this->price_ttc = $price_ttc;
1772  $this->price_min = $price_min;
1773  $this->price_min_ttc = $price_min_ttc;
1774  $this->price_base_type = $newpricebase;
1775  $this->default_vat_code = $newdefaultvatcode;
1776  $this->tva_tx = $newvat;
1777  $this->tva_npr = $newnpr;
1778  //Local taxes
1779  $this->localtax1_tx = $localtax1;
1780  $this->localtax2_tx = $localtax2;
1781  $this->localtax1_type = $localtaxtype1;
1782  $this->localtax2_type = $localtaxtype2;
1783 
1784  // Price by quantity
1785  $this->price_by_qty = $newpbq;
1786 
1787  $this->_log_price($user,$level); // Save price for level into table product_price
1788 
1789  $this->level = $level; // Store level of price edited for trigger
1790 
1791  // Call trigger
1792  $result=$this->call_trigger('PRODUCT_PRICE_MODIFY',$user);
1793  if ($result < 0)
1794  {
1795  $this->db->rollback();
1796  return -1;
1797  }
1798  // End call triggers
1799 
1800  $this->db->commit();
1801  }
1802  else
1803  {
1804  $this->db->rollback();
1805  dol_print_error($this->db);
1806  }
1807  }
1808 
1809  return 1;
1810  }
1811 
1819  function setPriceExpression($expression_id)
1820  {
1821  global $user;
1822 
1823  $this->fk_price_expression = $expression_id;
1824 
1825  return $this->update($this->id, $user);
1826  }
1827 
1837  function fetch($id='', $ref='', $ref_ext='', $ignore_expression=0)
1838  {
1839  include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1840 
1841  global $langs, $conf;
1842 
1843  dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
1844 
1845  // Check parameters
1846  if (! $id && ! $ref && ! $ref_ext)
1847  {
1848  $this->error='ErrorWrongParameters';
1849  dol_print_error(get_class($this)."::fetch ".$this->error);
1850  return -1;
1851  }
1852 
1853  $sql = "SELECT rowid, ref, ref_ext, label, description, url, note as note_private, customcode, fk_country, price, price_ttc,";
1854  $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,";
1855  $sql.= " tobuy, fk_product_type, duration, seuil_stock_alerte, canvas, weight, weight_units,";
1856  $sql.= " length, length_units, width, width_units, height, height_units,";
1857  $sql.= " surface, surface_units, volume, volume_units, barcode, fk_barcode_type, finished,";
1858  $sql.= " accountancy_code_buy, accountancy_code_sell, accountancy_code_sell_intra, accountancy_code_sell_export, stock, pmp,";
1859  $sql.= " datec, tms, import_key, entity, desiredstock, tobatch, fk_unit,";
1860  $sql.= " fk_price_expression, price_autogen";
1861  $sql.= " FROM ".MAIN_DB_PREFIX."product";
1862  if ($id) $sql.= " WHERE rowid = ".$this->db->escape($id);
1863  else
1864  {
1865  $sql.= " WHERE entity IN (".getEntity($this->element).")";
1866  if ($ref) $sql.= " AND ref = '".$this->db->escape($ref)."'";
1867  else if ($ref_ext) $sql.= " AND ref_ext = '".$this->db->escape($ref_ext)."'";
1868  }
1869 
1870  $resql = $this->db->query($sql);
1871  if ( $resql )
1872  {
1873  if ($this->db->num_rows($resql) > 0)
1874  {
1875  $obj = $this->db->fetch_object($resql);
1876 
1877  $this->id = $obj->rowid;
1878  $this->ref = $obj->ref;
1879  $this->ref_ext = $obj->ref_ext;
1880  $this->label = $obj->label;
1881  $this->description = $obj->description;
1882  $this->url = $obj->url;
1883  $this->note_private = $obj->note_private;
1884  $this->note = $obj->note_private; // deprecated
1885 
1886  $this->type = $obj->fk_product_type;
1887  $this->status = $obj->tosell;
1888  $this->status_buy = $obj->tobuy;
1889  $this->status_batch = $obj->tobatch;
1890 
1891  $this->customcode = $obj->customcode;
1892  $this->country_id = $obj->fk_country;
1893  $this->country_code = getCountry($this->country_id,2,$this->db);
1894  $this->price = $obj->price;
1895  $this->price_ttc = $obj->price_ttc;
1896  $this->price_min = $obj->price_min;
1897  $this->price_min_ttc = $obj->price_min_ttc;
1898  $this->price_base_type = $obj->price_base_type;
1899  $this->cost_price = $obj->cost_price;
1900  $this->default_vat_code = $obj->default_vat_code;
1901  $this->tva_tx = $obj->tva_tx;
1903  $this->tva_npr = $obj->tva_npr;
1904  $this->recuperableonly = $obj->tva_npr; // For backward compatibility
1906  $this->localtax1_tx = $obj->localtax1_tx;
1907  $this->localtax2_tx = $obj->localtax2_tx;
1908  $this->localtax1_type = $obj->localtax1_type;
1909  $this->localtax2_type = $obj->localtax2_type;
1910 
1911  $this->finished = $obj->finished;
1912  $this->duration = $obj->duration;
1913  $this->duration_value = substr($obj->duration,0,dol_strlen($obj->duration)-1);
1914  $this->duration_unit = substr($obj->duration,-1);
1915  $this->canvas = $obj->canvas;
1916  $this->weight = $obj->weight;
1917  $this->weight_units = $obj->weight_units;
1918  $this->length = $obj->length;
1919  $this->length_units = $obj->length_units;
1920  $this->width = $obj->width;
1921  $this->width_units = $obj->width_units;
1922  $this->height = $obj->height;
1923  $this->height_units = $obj->height_units;
1924 
1925  $this->surface = $obj->surface;
1926  $this->surface_units = $obj->surface_units;
1927  $this->volume = $obj->volume;
1928  $this->volume_units = $obj->volume_units;
1929  $this->barcode = $obj->barcode;
1930  $this->barcode_type = $obj->fk_barcode_type;
1931 
1932  $this->accountancy_code_buy = $obj->accountancy_code_buy;
1933  $this->accountancy_code_sell = $obj->accountancy_code_sell;
1934  $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
1935  $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
1936 
1937  $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
1938  $this->desiredstock = $obj->desiredstock;
1939  $this->stock_reel = $obj->stock;
1940  $this->pmp = $obj->pmp;
1941 
1942  $this->date_creation = $obj->datec;
1943  $this->date_modification = $obj->tms;
1944  $this->import_key = $obj->import_key;
1945  $this->entity = $obj->entity;
1946 
1947  $this->ref_ext = $obj->ref_ext;
1948  $this->fk_price_expression = $obj->fk_price_expression;
1949  $this->fk_unit = $obj->fk_unit;
1950  $this->price_autogen = $obj->price_autogen;
1951 
1952  $this->db->free($resql);
1953 
1954 
1955  // Retreive all extrafield for current object
1956  // fetch optionals attributes and labels
1957  require_once(DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php');
1958  $extrafields=new ExtraFields($this->db);
1959  $extralabels=$extrafields->fetch_name_optionals_label($this->table_element,true);
1960  $this->fetch_optionals($this->id,$extralabels);
1961 
1962  // multilangs
1963  if (! empty($conf->global->MAIN_MULTILANGS)) $this->getMultiLangs();
1964 
1965  // Load multiprices array
1966  if (! empty($conf->global->PRODUIT_MULTIPRICES)) // prices per segment
1967  {
1968  for ($i=1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++)
1969  {
1970  $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
1971  $sql.= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
1972  $sql.= " FROM ".MAIN_DB_PREFIX."product_price";
1973  $sql.= " WHERE entity IN (".getEntity('productprice').")";
1974  $sql.= " AND price_level=".$i;
1975  $sql.= " AND fk_product = ".$this->id;
1976  $sql.= " ORDER BY date_price DESC, rowid DESC";
1977  $sql.= " LIMIT 1";
1978  $resql = $this->db->query($sql);
1979  if ($resql)
1980  {
1981  $result = $this->db->fetch_array($resql);
1982 
1983  $this->multiprices[$i]=$result["price"];
1984  $this->multiprices_ttc[$i]=$result["price_ttc"];
1985  $this->multiprices_min[$i]=$result["price_min"];
1986  $this->multiprices_min_ttc[$i]=$result["price_min_ttc"];
1987  $this->multiprices_base_type[$i]=$result["price_base_type"];
1988  // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
1989  $this->multiprices_tva_tx[$i]=$result["tva_tx"]; // TODO Add ' ('.$result['default_vat_code'].')'
1990  $this->multiprices_recuperableonly[$i]=$result["recuperableonly"];
1991 
1992  // Price by quantity
1993  /*
1994  $this->prices_by_qty[$i]=$result["price_by_qty"];
1995  $this->prices_by_qty_id[$i]=$result["rowid"];
1996  // Récuperation de la liste des prix selon qty si flag positionné
1997  if ($this->prices_by_qty[$i] == 1)
1998  {
1999  $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2000  $sql.= " FROM ".MAIN_DB_PREFIX."product_price_by_qty";
2001  $sql.= " WHERE fk_product_price = ".$this->prices_by_qty_id[$i];
2002  $sql.= " ORDER BY quantity ASC";
2003  $resultat=array();
2004  $resql = $this->db->query($sql);
2005  if ($resql)
2006  {
2007  $ii=0;
2008  while ($result= $this->db->fetch_array($resql)) {
2009  $resultat[$ii]=array();
2010  $resultat[$ii]["rowid"]=$result["rowid"];
2011  $resultat[$ii]["price"]= $result["price"];
2012  $resultat[$ii]["unitprice"]= $result["unitprice"];
2013  $resultat[$ii]["quantity"]= $result["quantity"];
2014  $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2015  $resultat[$ii]["remise"]= $result["remise"]; // deprecated
2016  $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2017  $ii++;
2018  }
2019  $this->prices_by_qty_list[$i]=$resultat;
2020  }
2021  else
2022  {
2023  dol_print_error($this->db);
2024  return -1;
2025  }
2026  }*/
2027  }
2028  else
2029  {
2030  dol_print_error($this->db);
2031  return -1;
2032  }
2033  }
2034  }
2035  elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES)) // prices per customers
2036  {
2037  // Nothing loaded by default. List may be very long.
2038  }
2039  else if (! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY)) // prices per quantity
2040  {
2041  $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2042  $sql.= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
2043  $sql.= " FROM ".MAIN_DB_PREFIX."product_price";
2044  $sql.= " WHERE fk_product = ".$this->id;
2045  $sql.= " ORDER BY date_price DESC, rowid DESC";
2046  $sql.= " LIMIT 1";
2047  $resql = $this->db->query($sql);
2048  if ($resql)
2049  {
2050  $result = $this->db->fetch_array($resql);
2051 
2052  // Price by quantity
2053  $this->prices_by_qty[0]=$result["price_by_qty"];
2054  $this->prices_by_qty_id[0]=$result["rowid"];
2055  // Récuperation de la liste des prix selon qty si flag positionné
2056  if ($this->prices_by_qty[0] == 1)
2057  {
2058  $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
2059  $sql.= " FROM ".MAIN_DB_PREFIX."product_price_by_qty";
2060  $sql.= " WHERE fk_product_price = ".$this->prices_by_qty_id[0];
2061  $sql.= " ORDER BY quantity ASC";
2062  $resultat=array();
2063  $resql = $this->db->query($sql);
2064  if ($resql)
2065  {
2066  $ii=0;
2067  while ($result= $this->db->fetch_array($resql)) {
2068  $resultat[$ii]=array();
2069  $resultat[$ii]["rowid"]=$result["rowid"];
2070  $resultat[$ii]["price"]= $result["price"];
2071  $resultat[$ii]["unitprice"]= $result["unitprice"];
2072  $resultat[$ii]["quantity"]= $result["quantity"];
2073  $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2074  //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
2075  $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2076  $ii++;
2077  }
2078  $this->prices_by_qty_list[0]=$resultat;
2079  }
2080  else
2081  {
2082  dol_print_error($this->db);
2083  return -1;
2084  }
2085  }
2086  }
2087  else
2088  {
2089  dol_print_error($this->db);
2090  return -1;
2091  }
2092  }
2093  else if (! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) // prices per customer and quantity
2094  {
2095  // Not yet implemented
2096  }
2097 
2098  if (!empty($conf->dynamicprices->enabled) && !empty($this->fk_price_expression) && empty($ignore_expression))
2099  {
2100  require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2101  $priceparser = new PriceParser($this->db);
2102  $price_result = $priceparser->parseProduct($this);
2103  if ($price_result >= 0)
2104  {
2105  $this->price = $price_result;
2106  // Calculate the VAT
2107  $this->price_ttc = price2num($this->price) * (1 + ($this->tva_tx / 100));
2108  $this->price_ttc = price2num($this->price_ttc,'MU');
2109  }
2110  }
2111 
2112  // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
2113  // Instead we just init the stock_warehouse array
2114  $this->stock_warehouse = array();
2115 
2116  return 1;
2117  }
2118  else
2119  {
2120  return 0;
2121  }
2122  }
2123  else
2124  {
2125  dol_print_error($this->db);
2126  return -1;
2127  }
2128  }
2129 
2130 
2137  function load_stats_propale($socid=0)
2138  {
2139  global $conf;
2140  global $user;
2141 
2142  $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
2143  $sql.= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
2144  $sql.= " FROM ".MAIN_DB_PREFIX."propaldet as pd";
2145  $sql.= ", ".MAIN_DB_PREFIX."propal as p";
2146  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2147  if (!$user->rights->societe->client->voir && !$socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2148  $sql.= " WHERE p.rowid = pd.fk_propal";
2149  $sql.= " AND p.fk_soc = s.rowid";
2150  $sql.= " AND p.entity IN (".getEntity('propal').")";
2151  $sql.= " AND pd.fk_product = ".$this->id;
2152  if (!$user->rights->societe->client->voir && !$socid) $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2153  //$sql.= " AND pr.fk_statut != 0";
2154  if ($socid > 0) $sql.= " AND p.fk_soc = ".$socid;
2155 
2156  $result = $this->db->query($sql);
2157  if ( $result )
2158  {
2159  $obj=$this->db->fetch_object($result);
2160  $this->stats_propale['customers']=$obj->nb_customers;
2161  $this->stats_propale['nb']=$obj->nb;
2162  $this->stats_propale['rows']=$obj->nb_rows;
2163  $this->stats_propale['qty']=$obj->qty?$obj->qty:0;
2164  return 1;
2165  }
2166  else
2167  {
2168  $this->error=$this->db->error();
2169  return -1;
2170  }
2171  }
2172 
2173 
2180  function load_stats_proposal_supplier($socid=0)
2181  {
2182  global $conf;
2183  global $user;
2184 
2185  $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
2186  $sql.= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
2187  $sql.= " FROM ".MAIN_DB_PREFIX."supplier_proposaldet as pd";
2188  $sql.= ", ".MAIN_DB_PREFIX."supplier_proposal as p";
2189  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2190  if (!$user->rights->societe->client->voir && !$socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2191  $sql.= " WHERE p.rowid = pd.fk_supplier_proposal";
2192  $sql.= " AND p.fk_soc = s.rowid";
2193  $sql.= " AND p.entity IN (".getEntity('supplier_proposal').")";
2194  $sql.= " AND pd.fk_product = ".$this->id;
2195  if (!$user->rights->societe->client->voir && !$socid) $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2196  //$sql.= " AND pr.fk_statut != 0";
2197  if ($socid > 0) $sql.= " AND p.fk_soc = ".$socid;
2198 
2199  $result = $this->db->query($sql);
2200  if ( $result )
2201  {
2202  $obj=$this->db->fetch_object($result);
2203  $this->stats_proposal_supplier['suppliers']=$obj->nb_suppliers;
2204  $this->stats_proposal_supplier['nb']=$obj->nb;
2205  $this->stats_proposal_supplier['rows']=$obj->nb_rows;
2206  $this->stats_proposal_supplier['qty']=$obj->qty?$obj->qty:0;
2207  return 1;
2208  }
2209  else
2210  {
2211  $this->error=$this->db->error();
2212  return -1;
2213  }
2214  }
2215 
2216 
2225  function load_stats_commande($socid=0,$filtrestatut='', $forVirtualStock = 0)
2226  {
2227  global $conf,$user;
2228 
2229  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2230  $sql.= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2231  $sql.= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
2232  $sql.= ", ".MAIN_DB_PREFIX."commande as c";
2233  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2234  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2235  $sql.= " WHERE c.rowid = cd.fk_commande";
2236  $sql.= " AND c.fk_soc = s.rowid";
2237  $sql.= " AND c.entity IN (".getEntity('commande').")";
2238  $sql.= " AND cd.fk_product = ".$this->id;
2239  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2240  if ($socid > 0) $sql.= " AND c.fk_soc = ".$socid;
2241  if ($filtrestatut <> '') $sql.= " AND c.fk_statut in (".$filtrestatut.")";
2242 
2243  $result = $this->db->query($sql);
2244  if ( $result )
2245  {
2246  $obj=$this->db->fetch_object($result);
2247  $this->stats_commande['customers']=$obj->nb_customers;
2248  $this->stats_commande['nb']=$obj->nb;
2249  $this->stats_commande['rows']=$obj->nb_rows;
2250  $this->stats_commande['qty']=$obj->qty?$obj->qty:0;
2251 
2252  // if it's a virtual product, maybe it is in order by extension
2253  if (! empty($conf->global->ORDER_ADD_ORDERS_WITH_PARENT_PROD_IF_INCDEC))
2254  {
2255  $TFather = $this->getFather();
2256  if (is_array($TFather) && !empty($TFather)) {
2257  foreach($TFather as &$fatherData) {
2258  $pFather = new Product($this->db);
2259  $pFather->id = $fatherData['id'];
2260  $qtyCoef = $fatherData['qty'];
2261 
2262  if ($fatherData['incdec']) {
2263  $pFather->load_stats_commande($socid, $filtrestatut);
2264 
2265  $this->stats_commande['customers']+=$pFather->stats_commande['customers'];
2266  $this->stats_commande['nb']+=$pFather->stats_commande['nb'];
2267  $this->stats_commande['rows']+=$pFather->stats_commande['rows'];
2268  $this->stats_commande['qty']+=$pFather->stats_commande['qty'] * $qtyCoef;
2269 
2270  }
2271  }
2272  }
2273  }
2274 
2275  return 1;
2276  }
2277  else
2278  {
2279  $this->error=$this->db->error();
2280  return -1;
2281  }
2282  }
2283 
2292  function load_stats_commande_fournisseur($socid=0,$filtrestatut='', $forVirtualStock = 0)
2293  {
2294  global $conf,$user;
2295 
2296  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
2297  $sql.= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2298  $sql.= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd";
2299  $sql.= ", ".MAIN_DB_PREFIX."commande_fournisseur as c";
2300  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2301  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2302  $sql.= " WHERE c.rowid = cd.fk_commande";
2303  $sql.= " AND c.fk_soc = s.rowid";
2304  $sql.= " AND c.entity IN (".getEntity('supplier_order').")";
2305  $sql.= " AND cd.fk_product = ".$this->id;
2306  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2307  if ($socid > 0) $sql.= " AND c.fk_soc = ".$socid;
2308  if ($filtrestatut != '') $sql.= " AND c.fk_statut in (".$filtrestatut.")"; // Peut valoir 0
2309 
2310  $result = $this->db->query($sql);
2311  if ( $result )
2312  {
2313  $obj=$this->db->fetch_object($result);
2314  $this->stats_commande_fournisseur['suppliers']=$obj->nb_suppliers;
2315  $this->stats_commande_fournisseur['nb']=$obj->nb;
2316  $this->stats_commande_fournisseur['rows']=$obj->nb_rows;
2317  $this->stats_commande_fournisseur['qty']=$obj->qty?$obj->qty:0;
2318  return 1;
2319  }
2320  else
2321  {
2322  $this->error=$this->db->error().' sql='.$sql;
2323  return -1;
2324  }
2325  }
2326 
2335  function load_stats_sending($socid=0,$filtrestatut='', $forVirtualStock = 0)
2336  {
2337  global $conf,$user;
2338 
2339  $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
2340  $sql.= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
2341  $sql.= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed";
2342  $sql.= ", ".MAIN_DB_PREFIX."commandedet as cd";
2343  $sql.= ", ".MAIN_DB_PREFIX."commande as c";
2344  $sql.= ", ".MAIN_DB_PREFIX."expedition as e";
2345  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2346  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2347  $sql.= " WHERE e.rowid = ed.fk_expedition";
2348  $sql.= " AND c.rowid = cd.fk_commande";
2349  $sql.= " AND e.fk_soc = s.rowid";
2350  $sql.= " AND e.entity IN (".getEntity('expedition').")";
2351  $sql.= " AND ed.fk_origin_line = cd.rowid";
2352  $sql.= " AND cd.fk_product = ".$this->id;
2353  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) $sql.= " AND e.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2354  if ($socid > 0) $sql.= " AND e.fk_soc = ".$socid;
2355  if ($filtrestatut <> '') $sql.= " AND c.fk_statut in (".$filtrestatut.")";
2356 
2357  $result = $this->db->query($sql);
2358  if ( $result )
2359  {
2360  $obj=$this->db->fetch_object($result);
2361  $this->stats_expedition['customers']=$obj->nb_customers;
2362  $this->stats_expedition['nb']=$obj->nb;
2363  $this->stats_expedition['rows']=$obj->nb_rows;
2364  $this->stats_expedition['qty']=$obj->qty?$obj->qty:0;
2365  return 1;
2366  }
2367  else
2368  {
2369  $this->error=$this->db->error();
2370  return -1;
2371  }
2372  }
2373 
2382  function load_stats_reception($socid=0,$filtrestatut='', $forVirtualStock = 0)
2383  {
2384  global $conf,$user;
2385 
2386  $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_customers, COUNT(DISTINCT cf.rowid) as nb,";
2387  $sql.= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
2388  $sql.= " FROM ".MAIN_DB_PREFIX."commande_fournisseur_dispatch as fd";
2389  $sql.= ", ".MAIN_DB_PREFIX."commande_fournisseur as cf";
2390  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2391  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2392  $sql.= " WHERE cf.rowid = fd.fk_commande";
2393  $sql.= " AND cf.fk_soc = s.rowid";
2394  $sql.= " AND cf.entity IN (".getEntity('supplier_order').")";
2395  $sql.= " AND fd.fk_product = ".$this->id;
2396  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) $sql.= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2397  if ($socid > 0) $sql.= " AND cf.fk_soc = ".$socid;
2398  if ($filtrestatut <> '') $sql.= " AND cf.fk_statut in (".$filtrestatut.")";
2399 
2400  $result = $this->db->query($sql);
2401  if ( $result )
2402  {
2403  $obj=$this->db->fetch_object($result);
2404  $this->stats_reception['suppliers']=$obj->nb_customers;
2405  $this->stats_reception['nb']=$obj->nb;
2406  $this->stats_reception['rows']=$obj->nb_rows;
2407  $this->stats_reception['qty']=$obj->qty?$obj->qty:0;
2408  return 1;
2409  }
2410  else
2411  {
2412  $this->error=$this->db->error();
2413  return -1;
2414  }
2415  }
2416 
2423  function load_stats_contrat($socid=0)
2424  {
2425  global $conf;
2426  global $user;
2427 
2428  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2429  $sql.= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2430  $sql.= " FROM ".MAIN_DB_PREFIX."contratdet as cd";
2431  $sql.= ", ".MAIN_DB_PREFIX."contrat as c";
2432  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2433  if (!$user->rights->societe->client->voir && !$socid) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2434  $sql.= " WHERE c.rowid = cd.fk_contrat";
2435  $sql.= " AND c.fk_soc = s.rowid";
2436  $sql.= " AND c.entity IN (".getEntity('contract').")";
2437  $sql.= " AND cd.fk_product = ".$this->id;
2438  if (!$user->rights->societe->client->voir && !$socid) $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2439  //$sql.= " AND c.statut != 0";
2440  if ($socid > 0) $sql.= " AND c.fk_soc = ".$socid;
2441 
2442  $result = $this->db->query($sql);
2443  if ( $result )
2444  {
2445  $obj=$this->db->fetch_object($result);
2446  $this->stats_contrat['customers']=$obj->nb_customers;
2447  $this->stats_contrat['nb']=$obj->nb;
2448  $this->stats_contrat['rows']=$obj->nb_rows;
2449  $this->stats_contrat['qty']=$obj->qty?$obj->qty:0;
2450  return 1;
2451  }
2452  else
2453  {
2454  $this->error=$this->db->error().' sql='.$sql;
2455  return -1;
2456  }
2457  }
2458 
2465  function load_stats_facture($socid=0)
2466  {
2467  global $conf;
2468  global $user;
2469 
2470  $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
2471  $sql.= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
2472  $sql.= " FROM ".MAIN_DB_PREFIX."facturedet as fd";
2473  $sql.= ", ".MAIN_DB_PREFIX."facture as f";
2474  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2475  if (!$user->rights->societe->client->voir && !$socid) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2476  $sql.= " WHERE f.rowid = fd.fk_facture";
2477  $sql.= " AND f.fk_soc = s.rowid";
2478  $sql.= " AND f.entity IN (".getEntity('facture').")";
2479  $sql.= " AND fd.fk_product = ".$this->id;
2480  if (!$user->rights->societe->client->voir && !$socid) $sql.= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2481  //$sql.= " AND f.fk_statut != 0";
2482  if ($socid > 0) $sql .= " AND f.fk_soc = ".$socid;
2483 
2484  $result = $this->db->query($sql);
2485  if ( $result )
2486  {
2487  $obj=$this->db->fetch_object($result);
2488  $this->stats_facture['customers']=$obj->nb_customers;
2489  $this->stats_facture['nb']=$obj->nb;
2490  $this->stats_facture['rows']=$obj->nb_rows;
2491  $this->stats_facture['qty']=$obj->qty?$obj->qty:0;
2492  return 1;
2493  }
2494  else
2495  {
2496  $this->error=$this->db->error();
2497  return -1;
2498  }
2499  }
2500 
2508  {
2509  global $conf;
2510  global $user;
2511 
2512  $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
2513  $sql.= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
2514  $sql.= " FROM ".MAIN_DB_PREFIX."facture_fourn_det as fd";
2515  $sql.= ", ".MAIN_DB_PREFIX."facture_fourn as f";
2516  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2517  if (!$user->rights->societe->client->voir && !$socid) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2518  $sql.= " WHERE f.rowid = fd.fk_facture_fourn";
2519  $sql.= " AND f.fk_soc = s.rowid";
2520  $sql.= " AND f.entity IN (".getEntity('facture_fourn').")";
2521  $sql.= " AND fd.fk_product = ".$this->id;
2522  if (!$user->rights->societe->client->voir && !$socid) $sql.= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2523  //$sql.= " AND f.fk_statut != 0";
2524  if ($socid > 0) $sql .= " AND f.fk_soc = ".$socid;
2525 
2526  $result = $this->db->query($sql);
2527  if ( $result )
2528  {
2529  $obj=$this->db->fetch_object($result);
2530  $this->stats_facture_fournisseur['suppliers']=$obj->nb_suppliers;
2531  $this->stats_facture_fournisseur['nb']=$obj->nb;
2532  $this->stats_facture_fournisseur['rows']=$obj->nb_rows;
2533  $this->stats_facture_fournisseur['qty']=$obj->qty?$obj->qty:0;
2534  return 1;
2535  }
2536  else
2537  {
2538  $this->error=$this->db->error();
2539  return -1;
2540  }
2541  }
2542 
2551  function _get_stats($sql, $mode, $year=0)
2552  {
2553  $resql = $this->db->query($sql);
2554  if ($resql)
2555  {
2556  $num = $this->db->num_rows($resql);
2557  $i = 0;
2558  while ($i < $num)
2559  {
2560  $arr = $this->db->fetch_array($resql);
2561  if ($mode == 'byunit') $tab[$arr[1]] = $arr[0]; // 1st field
2562  if ($mode == 'bynumber') $tab[$arr[1]] = $arr[2]; // 3rd field
2563  $i++;
2564  }
2565  }
2566  else
2567  {
2568  $this->error=$this->db->error().' sql='.$sql;
2569  return -1;
2570  }
2571 
2572  if (empty($year))
2573  {
2574  $year = strftime('%Y',time());
2575  $month = strftime('%m',time());
2576  }
2577  else
2578  {
2579  $month=12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
2580  }
2581  $result = array();
2582 
2583  for ($j = 0 ; $j < 12 ; $j++)
2584  {
2585  $idx=ucfirst(dol_trunc(dol_print_date(dol_mktime(12,0,0,$month,1,$year),"%b"),3,'right','UTF-8',1));
2586  $monthnum=sprintf("%02s",$month);
2587 
2588  $result[$j] = array($idx,isset($tab[$year.$month])?$tab[$year.$month]:0);
2589  // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
2590 
2591  $month = "0".($month - 1);
2592  if (dol_strlen($month) == 3)
2593  {
2594  $month = substr($month,1);
2595  }
2596  if ($month == 0)
2597  {
2598  $month = 12;
2599  $year = $year - 1;
2600  }
2601  }
2602 
2603  return array_reverse($result);
2604  }
2605 
2606 
2617  function get_nb_vente($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
2618  {
2619  global $conf;
2620  global $user;
2621 
2622  $sql = "SELECT sum(d.qty), date_format(f.datef, '%Y%m')";
2623  if ($mode == 'bynumber') $sql.= ", count(DISTINCT f.rowid)";
2624  $sql.= " FROM ".MAIN_DB_PREFIX."facturedet as d, ".MAIN_DB_PREFIX."facture as f, ".MAIN_DB_PREFIX."societe as s";
2625  if ($filteronproducttype >= 0) $sql.=", ".MAIN_DB_PREFIX."product as p";
2626  if (!$user->rights->societe->client->voir && !$socid) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2627  $sql.= " WHERE f.rowid = d.fk_facture";
2628  if ($this->id > 0) $sql.= " AND d.fk_product =".$this->id;
2629  else $sql.=" AND d.fk_product > 0";
2630  if ($filteronproducttype >= 0) $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype;
2631  $sql.= " AND f.fk_soc = s.rowid";
2632  $sql.= " AND f.entity IN (".getEntity('facture').")";
2633  if (!$user->rights->societe->client->voir && !$socid) $sql.= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2634  if ($socid > 0) $sql.= " AND f.fk_soc = $socid";
2635  $sql.=$morefilter;
2636  $sql.= " GROUP BY date_format(f.datef,'%Y%m')";
2637  $sql.= " ORDER BY date_format(f.datef,'%Y%m') DESC";
2638 
2639  return $this->_get_stats($sql,$mode, $year);
2640  }
2641 
2642 
2653  function get_nb_achat($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
2654  {
2655  global $conf;
2656  global $user;
2657 
2658  $sql = "SELECT sum(d.qty), date_format(f.datef, '%Y%m')";
2659  if ($mode == 'bynumber') $sql.= ", count(DISTINCT f.rowid)";
2660  $sql.= " FROM ".MAIN_DB_PREFIX."facture_fourn_det as d, ".MAIN_DB_PREFIX."facture_fourn as f, ".MAIN_DB_PREFIX."societe as s";
2661  if ($filteronproducttype >= 0) $sql.=", ".MAIN_DB_PREFIX."product as p";
2662  if (!$user->rights->societe->client->voir && !$socid) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2663  $sql.= " WHERE f.rowid = d.fk_facture_fourn";
2664  if ($this->id > 0) $sql.= " AND d.fk_product =".$this->id;
2665  else $sql.=" AND d.fk_product > 0";
2666  if ($filteronproducttype >= 0) $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype;
2667  $sql.= " AND f.fk_soc = s.rowid";
2668  $sql.= " AND f.entity IN (".getEntity('facture_fourn').")";
2669  if (!$user->rights->societe->client->voir && !$socid) $sql.= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2670  if ($socid > 0) $sql.= " AND f.fk_soc = $socid";
2671  $sql.=$morefilter;
2672  $sql.= " GROUP BY date_format(f.datef,'%Y%m')";
2673  $sql.= " ORDER BY date_format(f.datef,'%Y%m') DESC";
2674 
2675  return $this->_get_stats($sql,$mode, $year);
2676  }
2677 
2688  function get_nb_propal($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
2689  {
2690  global $conf;
2691  global $user;
2692 
2693  $sql = "SELECT sum(d.qty), date_format(p.datep, '%Y%m')";
2694  if ($mode == 'bynumber') $sql.= ", count(DISTINCT p.rowid)";
2695  $sql.= " FROM ".MAIN_DB_PREFIX."propaldet as d, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."societe as s";
2696  if ($filteronproducttype >= 0) $sql.=", ".MAIN_DB_PREFIX."product as prod";
2697  if (!$user->rights->societe->client->voir && !$socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2698  $sql.= " WHERE p.rowid = d.fk_propal";
2699  if ($this->id > 0) $sql.= " AND d.fk_product =".$this->id;
2700  else $sql.=" AND d.fk_product > 0";
2701  if ($filteronproducttype >= 0) $sql.= " AND prod.rowid = d.fk_product AND prod.fk_product_type =".$filteronproducttype;
2702  $sql.= " AND p.fk_soc = s.rowid";
2703  $sql.= " AND p.entity IN (".getEntity('propal').")";
2704  if (!$user->rights->societe->client->voir && !$socid) $sql.= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2705  if ($socid > 0) $sql.= " AND p.fk_soc = ".$socid;
2706  $sql.=$morefilter;
2707  $sql.= " GROUP BY date_format(p.datep,'%Y%m')";
2708  $sql.= " ORDER BY date_format(p.datep,'%Y%m') DESC";
2709 
2710  return $this->_get_stats($sql,$mode, $year);
2711  }
2712 
2723  function get_nb_propalsupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
2724  {
2725  global $conf;
2726  global $user;
2727 
2728  $sql = "SELECT sum(d.qty), date_format(p.date_valid, '%Y%m')";
2729  if ($mode == 'bynumber') $sql.= ", count(DISTINCT p.rowid)";
2730  $sql.= " FROM ".MAIN_DB_PREFIX."supplier_proposaldet as d, ".MAIN_DB_PREFIX."supplier_proposal as p, ".MAIN_DB_PREFIX."societe as s";
2731  if ($filteronproducttype >= 0) $sql.=", ".MAIN_DB_PREFIX."product as prod";
2732  if (!$user->rights->societe->client->voir && !$socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2733  $sql.= " WHERE p.rowid = d.fk_supplier_proposal";
2734  if ($this->id > 0) $sql.= " AND d.fk_product =".$this->id;
2735  else $sql.=" AND d.fk_product > 0";
2736  if ($filteronproducttype >= 0) $sql.= " AND prod.rowid = d.fk_product AND prod.fk_product_type =".$filteronproducttype;
2737  $sql.= " AND p.fk_soc = s.rowid";
2738  $sql.= " AND p.entity IN (".getEntity('propal').")";
2739  if (!$user->rights->societe->client->voir && !$socid) $sql.= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2740  if ($socid > 0) $sql.= " AND p.fk_soc = ".$socid;
2741  $sql.=$morefilter;
2742  $sql.= " GROUP BY date_format(p.date_valid,'%Y%m')";
2743  $sql.= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
2744 
2745  return $this->_get_stats($sql,$mode, $year);
2746  }
2747 
2758  function get_nb_order($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
2759  {
2760  global $conf, $user;
2761 
2762  $sql = "SELECT sum(d.qty), date_format(c.date_commande, '%Y%m')";
2763  if ($mode == 'bynumber') $sql.= ", count(DISTINCT c.rowid)";
2764  $sql.= " FROM ".MAIN_DB_PREFIX."commandedet as d, ".MAIN_DB_PREFIX."commande as c, ".MAIN_DB_PREFIX."societe as s";
2765  if ($filteronproducttype >= 0) $sql.=", ".MAIN_DB_PREFIX."product as p";
2766  if (!$user->rights->societe->client->voir && !$socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2767  $sql.= " WHERE c.rowid = d.fk_commande";
2768  if ($this->id > 0) $sql.= " AND d.fk_product =".$this->id;
2769  else $sql.=" AND d.fk_product > 0";
2770  if ($filteronproducttype >= 0) $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype;
2771  $sql.= " AND c.fk_soc = s.rowid";
2772  $sql.= " AND c.entity IN (".getEntity('commande').")";
2773  if (!$user->rights->societe->client->voir && !$socid) $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2774  if ($socid > 0) $sql.= " AND c.fk_soc = ".$socid;
2775  $sql.=$morefilter;
2776  $sql.= " GROUP BY date_format(c.date_commande,'%Y%m')";
2777  $sql.= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
2778 
2779  return $this->_get_stats($sql,$mode, $year);
2780  }
2781 
2792  function get_nb_ordersupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
2793  {
2794  global $conf, $user;
2795 
2796  $sql = "SELECT sum(d.qty), date_format(c.date_commande, '%Y%m')";
2797  if ($mode == 'bynumber') $sql.= ", count(DISTINCT c.rowid)";
2798  $sql.= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as d, ".MAIN_DB_PREFIX."commande_fournisseur as c, ".MAIN_DB_PREFIX."societe as s";
2799  if ($filteronproducttype >= 0) $sql.=", ".MAIN_DB_PREFIX."product as p";
2800  if (!$user->rights->societe->client->voir && !$socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2801  $sql.= " WHERE c.rowid = d.fk_commande";
2802  if ($this->id > 0) $sql.= " AND d.fk_product =".$this->id;
2803  else $sql.=" AND d.fk_product > 0";
2804  if ($filteronproducttype >= 0) $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype;
2805  $sql.= " AND c.fk_soc = s.rowid";
2806  $sql.= " AND c.entity IN (".getEntity('supplier_order').")";
2807  if (!$user->rights->societe->client->voir && !$socid) $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2808  if ($socid > 0) $sql.= " AND c.fk_soc = ".$socid;
2809  $sql.=$morefilter;
2810  $sql.= " GROUP BY date_format(c.date_commande,'%Y%m')";
2811  $sql.= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
2812 
2813  return $this->_get_stats($sql,$mode, $year);
2814  }
2815 
2825  function add_sousproduit($id_pere, $id_fils, $qty, $incdec=1)
2826  {
2827  // Clean parameters
2828  if (! is_numeric($id_pere)) $id_pere=0;
2829  if (! is_numeric($id_fils)) $id_fils=0;
2830  if (! is_numeric($incdec)) $incdec=0;
2831 
2832  $result=$this->del_sousproduit($id_pere, $id_fils);
2833  if ($result < 0) return $result;
2834 
2835  // Check not already father of id_pere (to avoid father -> child -> father links)
2836  $sql = 'SELECT fk_product_pere from '.MAIN_DB_PREFIX.'product_association';
2837  $sql .= ' WHERE fk_product_pere = '.$id_fils.' AND fk_product_fils = '.$id_pere;
2838  if (! $this->db->query($sql))
2839  {
2840  dol_print_error($this->db);
2841  return -1;
2842  }
2843  else
2844  {
2845  $result = $this->db->query($sql);
2846  if ($result)
2847  {
2848  $num = $this->db->num_rows($result);
2849  if($num > 0)
2850  {
2851  $this->error="isFatherOfThis";
2852  return -1;
2853  }
2854  else
2855  {
2856  $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'product_association(fk_product_pere,fk_product_fils,qty,incdec)';
2857  $sql .= ' VALUES ('.$id_pere.', '.$id_fils.', '.$qty.', '.$incdec.')';
2858  if (! $this->db->query($sql))
2859  {
2860  dol_print_error($this->db);
2861  return -1;
2862  }
2863  else
2864  {
2865  return 1;
2866  }
2867  }
2868  }
2869  }
2870  }
2871 
2881  function update_sousproduit($id_pere, $id_fils, $qty, $incdec=1)
2882  {
2883  // Clean parameters
2884  if (! is_numeric($id_pere)) $id_pere=0;
2885  if (! is_numeric($id_fils)) $id_fils=0;
2886  if (! is_numeric($incdec)) $incdec=1;
2887  if (! is_numeric($qty)) $qty=1;
2888 
2889  $sql = 'UPDATE '.MAIN_DB_PREFIX.'product_association SET ';
2890  $sql.= 'qty='.$qty;
2891  $sql.= ',incdec='.$incdec;
2892  $sql .= ' WHERE fk_product_pere='.$id_pere.' AND fk_product_fils='.$id_fils;
2893 
2894  if (!$this->db->query($sql))
2895  {
2896  dol_print_error($this->db);
2897  return -1;
2898  }
2899  else
2900  {
2901  return 1;
2902  }
2903 
2904  }
2905 
2913  function del_sousproduit($fk_parent, $fk_child)
2914  {
2915  if (! is_numeric($fk_parent)) $fk_parent=0;
2916  if (! is_numeric($fk_child)) $fk_child=0;
2917 
2918  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_association";
2919  $sql.= " WHERE fk_product_pere = ".$fk_parent;
2920  $sql.= " AND fk_product_fils = ".$fk_child;
2921 
2922  dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
2923  if (! $this->db->query($sql))
2924  {
2925  dol_print_error($this->db);
2926  return -1;
2927  }
2928 
2929  return 1;
2930  }
2931 
2939  function is_sousproduit($fk_parent, $fk_child)
2940  {
2941  $sql = "SELECT fk_product_pere, qty, incdec";
2942  $sql.= " FROM ".MAIN_DB_PREFIX."product_association";
2943  $sql.= " WHERE fk_product_pere = '".$fk_parent."'";
2944  $sql.= " AND fk_product_fils = '".$fk_child."'";
2945 
2946  $result = $this->db->query($sql);
2947  if ($result)
2948  {
2949  $num = $this->db->num_rows($result);
2950 
2951  if($num > 0)
2952  {
2953  $obj = $this->db->fetch_object($result);
2954  $this->is_sousproduit_qty = $obj->qty;
2955  $this->is_sousproduit_incdec = $obj->incdec;
2956 
2957  return true;
2958  }
2959  else
2960  {
2961  return false;
2962  }
2963  }
2964  else
2965  {
2966  dol_print_error($this->db);
2967  return -1;
2968  }
2969  }
2970 
2971 
2982  function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
2983  {
2984  global $conf;
2985 
2986  $now=dol_now();
2987 
2988  dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
2989 
2990  if ($ref_fourn)
2991  {
2992  $sql = "SELECT rowid, fk_product";
2993  $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
2994  $sql.= " WHERE fk_soc = ".$id_fourn;
2995  $sql.= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
2996  $sql.= " AND fk_product != ".$this->id;
2997  $sql.= " AND entity IN (".getEntity('productprice').")";
2998 
2999  $resql=$this->db->query($sql);
3000  if ($resql)
3001  {
3002  $obj = $this->db->fetch_object($resql);
3003  if ($obj)
3004  {
3005  // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
3006  $this->product_id_already_linked = $obj->fk_product;
3007  return -3;
3008  }
3009  $this->db->free($resql);
3010  }
3011  }
3012 
3013  $sql = "SELECT rowid";
3014  $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
3015  $sql.= " WHERE fk_soc = ".$id_fourn;
3016  if ($ref_fourn) $sql.= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
3017  else $sql.= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
3018  $sql.= " AND quantity = '".$quantity."'";
3019  $sql.= " AND fk_product = ".$this->id;
3020  $sql.= " AND entity IN (".getEntity('productprice').")";
3021 
3022  $resql=$this->db->query($sql);
3023  if ($resql)
3024  {
3025  $obj = $this->db->fetch_object($resql);
3026 
3027  // The reference supplier does not exist, we create it for this product.
3028  if (! $obj)
3029  {
3030  $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_fournisseur_price(";
3031  $sql.= "datec";
3032  $sql.= ", entity";
3033  $sql.= ", fk_product";
3034  $sql.= ", fk_soc";
3035  $sql.= ", ref_fourn";
3036  $sql.= ", quantity";
3037  $sql.= ", fk_user";
3038  $sql.= ", tva_tx";
3039  $sql.= ") VALUES (";
3040  $sql.= "'".$this->db->idate($now)."'";
3041  $sql.= ", ".$conf->entity;
3042  $sql.= ", ".$this->id;
3043  $sql.= ", ".$id_fourn;
3044  $sql.= ", '".$this->db->escape($ref_fourn)."'";
3045  $sql.= ", ".$quantity;
3046  $sql.= ", ".$user->id;
3047  $sql.= ", 0";
3048  $sql.= ")";
3049 
3050  if ($this->db->query($sql))
3051  {
3052  $this->product_fourn_price_id = $this->db->last_insert_id(MAIN_DB_PREFIX."product_fournisseur_price");
3053  return 1;
3054  }
3055  else
3056  {
3057  $this->error=$this->db->lasterror();
3058  return -1;
3059  }
3060  }
3061  // If the supplier price already exists for this product and quantity
3062  else
3063  {
3064  $this->product_fourn_price_id = $obj->rowid;
3065  return 0;
3066  }
3067  }
3068  else
3069  {
3070  $this->error=$this->db->lasterror();
3071  return -2;
3072  }
3073  }
3074 
3075 
3081  function list_suppliers()
3082  {
3083  global $conf;
3084 
3085  $list = array();
3086 
3087  $sql = "SELECT DISTINCT p.fk_soc";
3088  $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as p";
3089  $sql.= " WHERE p.fk_product = ".$this->id;
3090  $sql.= " AND p.entity = ".$conf->entity;
3091 
3092  $result = $this->db->query($sql);
3093  if ($result)
3094  {
3095  $num = $this->db->num_rows($result);
3096  $i=0;
3097  while ($i < $num)
3098  {
3099  $obj = $this->db->fetch_object($result);
3100  $list[$i] = $obj->fk_soc;
3101  $i++;
3102  }
3103  }
3104 
3105  return $list;
3106  }
3107 
3115  function clone_price($fromId, $toId)
3116  {
3117  $this->db->begin();
3118 
3119  // les prix
3120  $sql = "INSERT ".MAIN_DB_PREFIX."product_price (";
3121  $sql.= " fk_product, date_price, price, tva_tx, localtax1_tx, localtax2_tx, fk_user_author, tosell)";
3122  $sql.= " SELECT ".$toId . ", date_price, price, tva_tx, localtax1_tx, localtax2_tx, fk_user_author, tosell";
3123  $sql.= " FROM ".MAIN_DB_PREFIX."product_price ";
3124  $sql.= " WHERE fk_product = ". $fromId;
3125 
3126  dol_syslog(get_class($this).'::clone_price', LOG_DEBUG);
3127  if (! $this->db->query($sql))
3128  {
3129  $this->db->rollback();
3130  return -1;
3131  }
3132  $this->db->commit();
3133  return 1;
3134  }
3135 
3143  function clone_associations($fromId, $toId)
3144  {
3145  $this->db->begin();
3146 
3147  $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'product_association (fk_product_pere, fk_product_fils, qty)';
3148  $sql.= " SELECT ".$toId.", fk_product_fils, qty FROM ".MAIN_DB_PREFIX."product_association";
3149  $sql.= " WHERE fk_product_pere = ".$fromId;
3150 
3151  dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
3152  if (! $this->db->query($sql))
3153  {
3154  $this->db->rollback();
3155  return -1;
3156  }
3157 
3158  $this->db->commit();
3159  return 1;
3160  }
3161 
3169  function clone_fournisseurs($fromId, $toId)
3170  {
3171  $this->db->begin();
3172 
3173  $now=dol_now();
3174 
3175  // les fournisseurs
3176  /*$sql = "INSERT ".MAIN_DB_PREFIX."product_fournisseur ("
3177  . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
3178  . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
3179  . " FROM ".MAIN_DB_PREFIX."product_fournisseur"
3180  . " WHERE fk_product = ".$fromId;
3181 
3182  if ( ! $this->db->query($sql ) )
3183  {
3184  $this->db->rollback();
3185  return -1;
3186  }*/
3187 
3188  // les prix de fournisseurs.
3189  $sql = "INSERT ".MAIN_DB_PREFIX."product_fournisseur_price (";
3190  $sql.= " datec, fk_product, fk_soc, price, quantity, fk_user)";
3191  $sql.= " SELECT '".$this->db->idate($now)."', ".$toId. ", fk_soc, price, quantity, fk_user";
3192  $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
3193  $sql.= " WHERE fk_product = ".$fromId;
3194 
3195  dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
3196  $resql=$this->db->query($sql);
3197  if (! $resql)
3198  {
3199  $this->db->rollback();
3200  return -1;
3201  }
3202  else
3203  {
3204  $this->db->commit();
3205  return 1;
3206  }
3207  }
3208 
3220  function fetch_prod_arbo($prod, $compl_path="", $multiply=1, $level=1, $id_parent=0)
3221  {
3222  global $conf,$langs;
3223 
3224  $product = new Product($this->db);
3225  //var_dump($prod);
3226  foreach($prod as $id_product => $desc_pere) // $id_product is 0 (first call starting with root top) or an id of a sub_product
3227  {
3228  if (is_array($desc_pere)) // If desc_pere is an array, this means it's a child
3229  {
3230  $id=(! empty($desc_pere[0]) ? $desc_pere[0] :'');
3231  $nb=(! empty($desc_pere[1]) ? $desc_pere[1] :'');
3232  $type=(! empty($desc_pere[2]) ? $desc_pere[2] :'');
3233  $label=(! empty($desc_pere[3]) ? $desc_pere[3] :'');
3234  $incdec=!empty($desc_pere[4]) ? $desc_pere[4] : 0;
3235 
3236  if ($multiply < 1) $multiply=1;
3237 
3238  //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
3239  $this->fetch($id); // Load product
3240  $this->load_stock('nobatch,novirtual'); // Load stock to get true this->stock_reel
3241  $this->res[]= array(
3242  'id'=>$id, // Id product
3243  'id_parent'=>$id_parent,
3244  'ref'=>$this->ref, // Ref product
3245  'nb'=>$nb, // Nb of units that compose parent product
3246  'nb_total'=>$nb*$multiply, // Nb of units for all nb of product
3247  'stock'=>$this->stock_reel, // Stock
3248  'stock_alert'=>$this->seuil_stock_alerte, // Stock alert
3249  'label'=>$label,
3250  'fullpath'=>$compl_path.$label, // Label
3251  'type'=>$type, // Nb of units that compose parent product
3252  'desiredstock'=>$this->desiredstock,
3253  'level'=>$level,
3254  'incdec'=>$incdec,
3255  'entity'=>$this->entity
3256  );
3257 
3258  // Recursive call if there is childs to child
3259  if (is_array($desc_pere['childs']))
3260  {
3261  //print 'YYY We go down for '.$desc_pere[3]." -> \n";
3262  $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1]*$multiply, $level+1, $id);
3263  }
3264  }
3265  }
3266  }
3267 
3274  function fetch_prods($prod)
3275  {
3276  $this->res;
3277  foreach($prod as $nom_pere => $desc_pere)
3278  {
3279  // on est dans une sous-categorie
3280  if(is_array($desc_pere))
3281  $this->res[]= array($desc_pere[1],$desc_pere[0]);
3282  if(count($desc_pere) >1)
3283  {
3284  $this->fetch_prods($desc_pere);
3285  }
3286  }
3287  }
3288 
3295  function get_arbo_each_prod($multiply=1)
3296  {
3297  $this->res = array();
3298  if (isset($this->sousprods) && is_array($this->sousprods))
3299  {
3300  foreach($this->sousprods as $prod_name => $desc_product)
3301  {
3302  if (is_array($desc_product)) $this->fetch_prod_arbo($desc_product,"",$multiply,1,$this->id);
3303  }
3304  }
3305  //var_dump($this->res);
3306  return $this->res;
3307  }
3308 
3314  function get_each_prod()
3315  {
3316  $this->res = array();
3317  if (is_array($this->sousprods))
3318  {
3319  foreach($this->sousprods as $nom_pere => $desc_pere)
3320  {
3321  if (count($desc_pere) >1) $this->fetch_prods($desc_pere);
3322  }
3323  sort($this->res);
3324  }
3325  return $this->res;
3326  }
3327 
3328 
3334  function hasFatherOrChild()
3335  {
3336  $nb = 0;
3337 
3338  $sql = "SELECT COUNT(pa.rowid) as nb";
3339  $sql.= " FROM ".MAIN_DB_PREFIX."product_association as pa";
3340  $sql.= " WHERE pa.fk_product_fils = ".$this->id." OR pa.fk_product_pere = ".$this->id;
3341  $resql = $this->db->query($sql);
3342  if ($resql)
3343  {
3344  $obj = $this->db->fetch_object($resql);
3345  if ($obj) $nb = $obj->nb;
3346  }
3347  else
3348  {
3349  return -1;
3350  }
3351 
3352  return $nb;
3353  }
3354 
3360  function getFather()
3361  {
3362  $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";
3363  $sql.= " FROM ".MAIN_DB_PREFIX."product_association as pa,";
3364  $sql.= " ".MAIN_DB_PREFIX."product as p";
3365  $sql.= " WHERE p.rowid = pa.fk_product_pere";
3366  $sql.= " AND pa.fk_product_fils = ".$this->id;
3367 
3368  $res = $this->db->query($sql);
3369  if ($res)
3370  {
3371  $prods = array ();
3372  while ($record = $this->db->fetch_array($res))
3373  {
3374  // $record['id'] = $record['rowid'] = id of father
3375  $prods[$record['id']]['id'] = $record['rowid'];
3376  $prods[$record['id']]['ref'] = $record['ref'];
3377  $prods[$record['id']]['label'] = $record['label'];
3378  $prods[$record['id']]['qty'] = $record['qty'];
3379  $prods[$record['id']]['incdec'] = $record['incdec'];
3380  $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
3381  $prods[$record['id']]['entity'] = $record['entity'];
3382  }
3383  return $prods;
3384  }
3385  else
3386  {
3387  dol_print_error($this->db);
3388  return -1;
3389  }
3390  }
3391 
3392 
3401  function getChildsArbo($id, $firstlevelonly=0, $level=1)
3402  {
3403  global $alreadyfound;
3404 
3405  $sql = "SELECT p.rowid, p.label as label, pa.qty as qty, pa.fk_product_fils as id, p.fk_product_type, pa.incdec";
3406  $sql.= " FROM ".MAIN_DB_PREFIX."product as p";
3407  $sql.= ", ".MAIN_DB_PREFIX."product_association as pa";
3408  $sql.= " WHERE p.rowid = pa.fk_product_fils";
3409  $sql.= " AND pa.fk_product_pere = ".$id;
3410  $sql.= " AND pa.fk_product_fils != ".$id; // This should not happens, it is to avoid infinite loop if it happens
3411 
3412  dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level, LOG_DEBUG);
3413 
3414  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
3415  // Protection against infinite loop
3416  if ($level > 30) return array();
3417 
3418  $res = $this->db->query($sql);
3419  if ($res)
3420  {
3421  $prods = array();
3422  while ($rec = $this->db->fetch_array($res))
3423  {
3424  if (! empty($alreadyfound[$rec['rowid']]))
3425  {
3426  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);
3427  continue;
3428  }
3429  $alreadyfound[$rec['rowid']]=1;
3430  $prods[$rec['rowid']]= array(
3431  0=>$rec['rowid'],
3432  1=>$rec['qty'],
3433  2=>$rec['fk_product_type'],
3434  3=>$this->db->escape($rec['label']),
3435  4=>$rec['incdec']
3436  );
3437  //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
3438  //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
3439  if (empty($firstlevelonly))
3440  {
3441  $listofchilds=$this->getChildsArbo($rec['rowid'], 0, $level + 1);
3442  foreach($listofchilds as $keyChild => $valueChild)
3443  {
3444  $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
3445  }
3446  }
3447  }
3448 
3449  return $prods;
3450  }
3451  else
3452  {
3453  dol_print_error($this->db);
3454  return -1;
3455  }
3456  }
3457 
3465  {
3466  $parent=array();
3467 
3468  foreach($this->getChildsArbo($this->id) as $keyChild => $valueChild) // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
3469  {
3470  $parent[$this->label][$keyChild] = $valueChild;
3471  }
3472  foreach($parent as $key => $value) // key=label, value is array of childs
3473  {
3474  $this->sousprods[$key] = $value;
3475  }
3476  }
3477 
3487  function getNomUrl($withpicto=0, $option='', $maxlength=0, $save_lastsearch_value=-1)
3488  {
3489  global $conf, $langs, $hookmanager;
3490  include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
3491 
3492  $result='';
3493  $newref=$this->ref;
3494  if ($maxlength) $newref=dol_trunc($newref,$maxlength,'middle');
3495 
3496  if ($this->type == Product::TYPE_PRODUCT) $label = '<u>' . $langs->trans("ShowProduct") . '</u>';
3497  if ($this->type == Product::TYPE_SERVICE) $label = '<u>' . $langs->trans("ShowService") . '</u>';
3498  if (! empty($this->ref))
3499  $label .= '<br><b>' . $langs->trans('ProductRef') . ':</b> ' . $this->ref;
3500  if (! empty($this->label))
3501  $label .= '<br><b>' . $langs->trans('ProductLabel') . ':</b> ' . $this->label;
3502 
3503  if ($this->type == Product::TYPE_PRODUCT)
3504  {
3505  if ($this->weight) $label.="<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuring_units_string($this->weight_units,"weight");
3506  if ($this->length) $label.="<br><b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuring_units_string($this->length_units,'length');
3507  if ($this->surface) $label.="<br><b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuring_units_string($this->surface_units,'surface');
3508  if ($this->volume) $label.="<br><b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuring_units_string($this->volume_units,'volume');
3509  }
3510 
3511  if ($this->type == Product::TYPE_PRODUCT || ! empty($conf->global->STOCK_SUPPORTS_SERVICES))
3512  {
3513  if (! empty($conf->productbatch->enabled))
3514  {
3515  $langs->load("productbatch");
3516  $label.="<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0,2);
3517  }
3518  }
3519  if ($this->type == Product::TYPE_SERVICE)
3520  {
3521  //
3522  }
3523  if (! empty($this->entity))
3524  {
3525  $tmpphoto = $this->show_photos($conf->product->multidir_output[$this->entity],1,1,0,0,0,80);
3526  if ($this->nbphoto > 0) $label .= '<br>' . $tmpphoto;
3527  }
3528 
3529  $linkclose='';
3530  if (empty($notooltip))
3531  {
3532  if (! empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
3533  {
3534  $label=$langs->trans("ShowOrder");
3535  $linkclose.=' alt="'.dol_escape_htmltag($label, 1).'"';
3536  }
3537 
3538  $linkclose.= ' title="'.dol_escape_htmltag($label, 1, 1).'"';
3539  $linkclose.= ' class="classfortooltip"';
3540 
3541  if (! is_object($hookmanager))
3542  {
3543  include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
3544  $hookmanager=new HookManager($this->db);
3545  }
3546  $hookmanager->initHooks(array('productdao'));
3547  $parameters=array('id'=>$this->id);
3548  $reshook=$hookmanager->executeHooks('getnomurltooltip',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks
3549  if ($reshook > 0) $linkclose = $hookmanager->resPrint;
3550  }
3551 
3552  if ($option == 'supplier' || $option == 'category') {
3553  $url = DOL_URL_ROOT.'/product/fournisseurs.php?id='.$this->id;
3554  } else if ($option == 'stock') {
3555  $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
3556  } else if ($option == 'composition') {
3557  $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
3558  } else {
3559  $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
3560  }
3561 
3562  if ($option !== 'nolink')
3563  {
3564  // Add param to save lastsearch_values or not
3565  $add_save_lastsearch_values=($save_lastsearch_value == 1 ? 1 : 0);
3566  if ($save_lastsearch_value == -1 && preg_match('/list\.php/',$_SERVER["PHP_SELF"])) $add_save_lastsearch_values=1;
3567  if ($add_save_lastsearch_values) $url.='&save_lastsearch_values=1';
3568  }
3569 
3570  $linkstart = '<a href="'.$url.'"';
3571  $linkstart.=$linkclose.'>';
3572  $linkend='</a>';
3573 
3574  $result.=$linkstart;
3575  if ($withpicto) {
3576  if ($this->type == Product::TYPE_PRODUCT) $result.=(img_object(($notooltip?'':$label), 'product', ($notooltip?'class="paddingright"':'class="paddingright classfortooltip"'), 0, 0, $notooltip?0:1));
3577  if ($this->type == Product::TYPE_SERVICE) $result.=(img_object(($notooltip?'':$label), 'service', ($notooltip?'class="paddinright"':'class="paddingright classfortooltip"'), 0, 0, $notooltip?0:1));
3578  }
3579  $result.= $newref;
3580  $result.= $linkend;
3581  return $result;
3582  }
3583 
3584 
3595  public function generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0)
3596  {
3597  global $conf,$user,$langs;
3598 
3599  $langs->load("products");
3600 
3601  // Positionne le modele sur le nom du modele a utiliser
3602  if (! dol_strlen($modele))
3603  {
3604  if (! empty($conf->global->PRODUCT_ADDON_PDF))
3605  {
3606  $modele = $conf->global->PRODUCT_ADDON_PDF;
3607  }
3608  else
3609  {
3610  $modele = 'strato';
3611  }
3612  }
3613 
3614  $modelpath = "core/modules/product/doc/";
3615 
3616  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
3617  }
3618 
3626  function getLibStatut($mode=0, $type=0)
3627  {
3628  switch ($type)
3629  {
3630  case 0:
3631  return $this->LibStatut($this->status,$mode,$type);
3632  case 1:
3633  return $this->LibStatut($this->status_buy,$mode,$type);
3634  case 2:
3635  return $this->LibStatut($this->status_batch,$mode,$type);
3636  default:
3637  //Simulate previous behavior but should return an error string
3638  return $this->LibStatut($this->status_buy,$mode,$type);
3639  }
3640  }
3641 
3650  function LibStatut($status,$mode=0,$type=0)
3651  {
3652  global $conf, $langs;
3653 
3654  $langs->load('products');
3655  if (! empty($conf->productbatch->enabled)) $langs->load("productbatch");
3656 
3657  if ($type == 2)
3658  {
3659  switch ($mode)
3660  {
3661  case 0:
3662  return ($status == 0 ? $langs->trans('ProductStatusNotOnBatch') : $langs->trans('ProductStatusOnBatch'));
3663  case 1:
3664  return ($status == 0 ? $langs->trans('ProductStatusNotOnBatchShort') : $langs->trans('ProductStatusOnBatchShort'));
3665  case 2:
3666  return $this->LibStatut($status,3,2).' '.$this->LibStatut($status,1,2);
3667  case 3:
3668  if ($status == 0)
3669  {
3670  return img_picto($langs->trans('ProductStatusNotOnBatch'),'statut5');
3671  }
3672  return img_picto($langs->trans('ProductStatusOnBatch'),'statut4');
3673  case 4:
3674  return $this->LibStatut($status,3,2).' '.$this->LibStatut($status,0,2);
3675  case 5:
3676  return $this->LibStatut($status,1,2).' '.$this->LibStatut($status,3,2);
3677  default:
3678  return $langs->trans('Unknown');
3679  }
3680  }
3681  if ($mode == 0)
3682  {
3683  if ($status == 0) return ($type==0 ? $langs->trans('ProductStatusNotOnSellShort'):$langs->trans('ProductStatusNotOnBuyShort'));
3684  if ($status == 1) return ($type==0 ? $langs->trans('ProductStatusOnSellShort'):$langs->trans('ProductStatusOnBuyShort'));
3685  }
3686  if ($mode == 1)
3687  {
3688  if ($status == 0) return ($type==0 ? $langs->trans('ProductStatusNotOnSell'):$langs->trans('ProductStatusNotOnBuy'));
3689  if ($status == 1) return ($type==0 ? $langs->trans('ProductStatusOnSell'):$langs->trans('ProductStatusOnBuy'));
3690  }
3691  if ($mode == 2)
3692  {
3693  if ($status == 0) return img_picto($langs->trans('ProductStatusNotOnSell'),'statut5', 'class="pictostatus"').' '.($type==0 ? $langs->trans('ProductStatusNotOnSellShort'):$langs->trans('ProductStatusNotOnBuyShort'));
3694  if ($status == 1) return img_picto($langs->trans('ProductStatusOnSell'),'statut4', 'class="pictostatus"').' '.($type==0 ? $langs->trans('ProductStatusOnSellShort'):$langs->trans('ProductStatusOnBuyShort'));
3695  }
3696  if ($mode == 3)
3697  {
3698  if ($status == 0) return img_picto(($type==0 ? $langs->trans('ProductStatusNotOnSell') : $langs->trans('ProductStatusNotOnBuy')),'statut5', 'class="pictostatus"');
3699  if ($status == 1) return img_picto(($type==0 ? $langs->trans('ProductStatusOnSell') : $langs->trans('ProductStatusOnBuy')),'statut4', 'class="pictostatus"');
3700  }
3701  if ($mode == 4)
3702  {
3703  if ($status == 0) return img_picto($langs->trans('ProductStatusNotOnSell'),'statut5', 'class="pictostatus"').' '.($type==0 ? $langs->trans('ProductStatusNotOnSell'):$langs->trans('ProductStatusNotOnBuy'));
3704  if ($status == 1) return img_picto($langs->trans('ProductStatusOnSell'),'statut4', 'class="pictostatus"').' '.($type==0 ? $langs->trans('ProductStatusOnSell'):$langs->trans('ProductStatusOnBuy'));
3705  }
3706  if ($mode == 5)
3707  {
3708  if ($status == 0) return ($type==0 ? $langs->trans('ProductStatusNotOnSellShort'):$langs->trans('ProductStatusNotOnBuyShort')).' '.img_picto(($type==0 ? $langs->trans('ProductStatusNotOnSell'):$langs->trans('ProductStatusNotOnBuy')), 'statut5', 'class="pictostatus"');
3709  if ($status == 1) return ($type==0 ? $langs->trans('ProductStatusOnSellShort'):$langs->trans('ProductStatusOnBuyShort')).' '.img_picto(($type==0 ? $langs->trans('ProductStatusOnSell'):$langs->trans('ProductStatusOnBuy')),'statut4', 'class="pictostatus"');
3710  }
3711  if ($mode == 6)
3712  {
3713  if ($status == 0) return ($type==0 ? $langs->trans('ProductStatusNotOnSellShort'):$langs->trans('ProductStatusNotOnBuyShort')).' '.img_picto(($type==0 ? $langs->trans('ProductStatusNotOnSell'):$langs->trans('ProductStatusNotOnBuy')), 'statut5', 'class="pictostatus"');
3714  if ($status == 1) return ($type==0 ? $langs->trans('ProductStatusOnSellShort'):$langs->trans('ProductStatusOnBuyShort')).' '.img_picto(($type==0 ? $langs->trans('ProductStatusOnSell'):$langs->trans('ProductStatusOnBuy')),'statut4', 'class="pictostatus"');
3715  }
3716  return $langs->trans('Unknown');
3717  }
3718 
3719 
3725  function getLibFinished()
3726  {
3727  global $langs;
3728  $langs->load('products');
3729 
3730  if ($this->finished == '0') return $langs->trans("RowMaterial");
3731  if ($this->finished == '1') return $langs->trans("Finished");
3732  return '';
3733  }
3734 
3735 
3750  function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $inventorycode='', $origin_element='', $origin_id=null)
3751  {
3752  if ($id_entrepot)
3753  {
3754  $this->db->begin();
3755 
3756  require_once DOL_DOCUMENT_ROOT .'/product/stock/class/mouvementstock.class.php';
3757 
3758  $op[0] = "+".trim($nbpiece);
3759  $op[1] = "-".trim($nbpiece);
3760 
3761  $movementstock=new MouvementStock($this->db);
3762  $movementstock->setOrigin($origin_element, $origin_id);
3763  $result=$movementstock->_create($user,$this->id,$id_entrepot,$op[$movement],$movement,$price,$label,$inventorycode);
3764 
3765  if ($result >= 0)
3766  {
3767  $this->db->commit();
3768  return 1;
3769  }
3770  else
3771  {
3772  $this->error=$movementstock->error;
3773  $this->errors=$movementstock->errors;
3774 
3775  $this->db->rollback();
3776  return -1;
3777  }
3778  }
3779  }
3780 
3798  function correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $dlc='', $dluo='',$lot='', $inventorycode='', $origin_element='', $origin_id=null)
3799  {
3800  if ($id_entrepot)
3801  {
3802  $this->db->begin();
3803 
3804  require_once DOL_DOCUMENT_ROOT .'/product/stock/class/mouvementstock.class.php';
3805 
3806  $op[0] = "+".trim($nbpiece);
3807  $op[1] = "-".trim($nbpiece);
3808 
3809  $movementstock=new MouvementStock($this->db);
3810  $movementstock->setOrigin($origin_element, $origin_id);
3811  $result=$movementstock->_create($user,$this->id,$id_entrepot,$op[$movement],$movement,$price,$label,$inventorycode,'',$dlc,$dluo,$lot);
3812 
3813  if ($result >= 0)
3814  {
3815  $this->db->commit();
3816  return 1;
3817  }
3818  else
3819  {
3820  $this->error=$movementstock->error;
3821  $this->errors=$movementstock->errors;
3822 
3823  $this->db->rollback();
3824  return -1;
3825  }
3826  }
3827  }
3828 
3843  function load_stock($option='')
3844  {
3845  global $conf;
3846 
3847  $this->stock_reel = 0;
3848  $this->stock_warehouse = array();
3849  $this->stock_theorique = 0;
3850 
3851  $warehouseStatus = array();
3852 
3853  if (preg_match('/warehouseclosed/', $option))
3854  {
3855  $warehouseStatus[] = Entrepot::STATUS_CLOSED;
3856  }
3857  if (preg_match('/warehouseopen/', $option))
3858  {
3859  $warehouseStatus[] = Entrepot::STATUS_OPEN_ALL;
3860  }
3861  if (preg_match('/warehouseinternal/', $option))
3862  {
3863  $warehouseStatus[] = Entrepot::STATUS_OPEN_INTERNAL;
3864  }
3865 
3866  $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
3867  $sql.= " FROM ".MAIN_DB_PREFIX."product_stock as ps";
3868  $sql.= ", ".MAIN_DB_PREFIX."entrepot as w";
3869  $sql.= " WHERE w.entity IN (".getEntity('stock').")";
3870  $sql.= " AND w.rowid = ps.fk_entrepot";
3871  $sql.= " AND ps.fk_product = ".$this->id;
3872  if ($conf->global->ENTREPOT_EXTRA_STATUS && count($warehouseStatus)) $sql.= " AND w.statut IN (".$this->db->escape(implode(',',$warehouseStatus)).")";
3873 
3874  dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
3875  $result = $this->db->query($sql);
3876  if ($result)
3877  {
3878  $num = $this->db->num_rows($result);
3879  $i=0;
3880  if ($num > 0)
3881  {
3882  while ($i < $num)
3883  {
3884  $row = $this->db->fetch_object($result);
3885  $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
3886  $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
3887  $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
3888  if ((! preg_match('/nobatch/', $option)) && $this->hasbatch()) $this->stock_warehouse[$row->fk_entrepot]->detail_batch=Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
3889  $this->stock_reel+=$row->reel;
3890  $i++;
3891  }
3892  }
3893  $this->db->free($result);
3894 
3895  if (! preg_match('/novirtual/', $option))
3896  {
3897  $this->load_virtual_stock(); // This also load stats_commande_fournisseur, ...
3898  }
3899 
3900  return 1;
3901  }
3902  else
3903  {
3904  $this->error=$this->db->lasterror();
3905  return -1;
3906  }
3907  }
3908 
3917  {
3918  global $conf;
3919 
3920  $stock_commande_client=0;
3921  $stock_commande_fournisseur=0;
3922  $stock_sending_client=0;
3923  $stock_reception_fournisseur=0;
3924 
3925  if (! empty($conf->commande->enabled))
3926  {
3927  $result=$this->load_stats_commande(0,'1,2', 1);
3928  if ($result < 0) dol_print_error($this->db,$this->error);
3929  $stock_commande_client=$this->stats_commande['qty'];
3930  }
3931  if (! empty($conf->expedition->enabled))
3932  {
3933  $result=$this->load_stats_sending(0,'1,2', 1);
3934  if ($result < 0) dol_print_error($this->db,$this->error);
3935  $stock_sending_client=$this->stats_expedition['qty'];
3936  }
3937  if (! empty($conf->fournisseur->enabled))
3938  {
3939  $result=$this->load_stats_commande_fournisseur(0,'1,2,3,4', 1);
3940  if ($result < 0) dol_print_error($this->db,$this->error);
3941  $stock_commande_fournisseur=$this->stats_commande_fournisseur['qty'];
3942 
3943  $result=$this->load_stats_reception(0,'4', 1);
3944  if ($result < 0) dol_print_error($this->db,$this->error);
3945  $stock_reception_fournisseur=$this->stats_reception['qty'];
3946  }
3947 
3948  // Stock decrease mode
3949  if (! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || ! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
3950  $this->stock_theorique=$this->stock_reel-$stock_commande_client+$stock_sending_client;
3951  }
3952  if (! empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER)) {
3953  $this->stock_theorique=$this->stock_reel;
3954  }
3955  if (! empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
3956  $this->stock_theorique=$this->stock_reel-$stock_commande_client;
3957  }
3958  // Stock Increase mode
3959  if (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER)) {
3960  $this->stock_theorique+=$stock_commande_fournisseur-$stock_reception_fournisseur;
3961  }
3962  if (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER)) {
3963  $this->stock_theorique-=$stock_reception_fournisseur;
3964  }
3965  if (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_BILL)) {
3966  $this->stock_theorique+=$stock_commande_fournisseur-$stock_reception_fournisseur;
3967  }
3968  }
3969 
3970 
3978  function loadBatchInfo($batch)
3979  {
3980  $result=array();
3981 
3982  $sql = "SELECT pb.batch, pb.eatby, pb.sellby, SUM(pb.qty) FROM ".MAIN_DB_PREFIX."product_batch as pb, ".MAIN_DB_PREFIX."product_stock as ps";
3983  $sql.= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".$this->id." AND pb.batch = '".$this->db->escape($batch)."'";
3984  $sql.= " GROUP BY pb.batch, pb.eatby, pb.sellby";
3985  dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
3986  $resql = $this->db->query($sql);
3987  if ($resql)
3988  {
3989  $num = $this->db->num_rows($resql);
3990  $i=0;
3991  while ($i < $num)
3992  {
3993  $obj = $this->db->fetch_object($resql);
3994  $result[]=array('batch'=>$batch, 'eatby'=>$this->db->jdate($obj->eatby), 'sellby'=>$this->db->jdate($obj->sellby), 'qty'=>$obj->qty);
3995  $i++;
3996  }
3997  return $result;
3998  }
3999  else
4000  {
4001  dol_print_error($this->db);
4002  $this->db->rollback();
4003  return array();
4004  }
4005  }
4006 
4007 
4015  function add_photo($sdir, $file)
4016  {
4017  global $conf;
4018 
4019  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4020 
4021  $result = 0;
4022 
4023  $dir = $sdir;
4024  if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) $dir .= '/'. get_exdir($this->id,2,0,0,$this,'product') . $this->id ."/photos";
4025  else $dir .= '/'.get_exdir(0,0,0,0,$this,'product').dol_sanitizeFileName($this->ref);
4026 
4027  dol_mkdir($dir);
4028 
4029  $dir_osencoded=$dir;
4030 
4031  if (is_dir($dir_osencoded))
4032  {
4033  $originImage = $dir . '/' . $file['name'];
4034 
4035  // Cree fichier en taille origine
4036  $result=dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
4037 
4038  if (file_exists(dol_osencode($originImage)))
4039  {
4040  // Create thumbs
4041  $this->addThumbs($originImage);
4042  }
4043  }
4044 
4045  if (is_numeric($result) && $result > 0) return 1;
4046  else return -1;
4047  }
4048 
4055  function is_photo_available($sdir)
4056  {
4057  include_once DOL_DOCUMENT_ROOT .'/core/lib/files.lib.php';
4058  include_once DOL_DOCUMENT_ROOT .'/core/lib/images.lib.php';
4059 
4060  global $conf;
4061 
4062  $dir = $sdir;
4063  if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) $dir .= '/'. get_exdir($this->id,2,0,0,$this,'product') . $this->id ."/photos/";
4064  else $dir .= '/'.get_exdir(0,0,0,0,$this,'product').dol_sanitizeFileName($this->ref).'/';
4065 
4066  $nbphoto=0;
4067 
4068  $dir_osencoded=dol_osencode($dir);
4069  if (file_exists($dir_osencoded))
4070  {
4071  $handle=opendir($dir_osencoded);
4072  if (is_resource($handle))
4073  {
4074  while (($file = readdir($handle)) !== false)
4075  {
4076  if (! utf8_check($file)) $file=utf8_encode($file); // To be sure data is stored in UTF8 in memory
4077  if (dol_is_file($dir.$file) && image_format_supported($file) > 0) return true;
4078  }
4079  }
4080  }
4081  return false;
4082  }
4083 
4084 
4100  function show_photos($sdir,$size=0,$nbmax=0,$nbbyrow=5,$showfilename=0,$showaction=0,$maxHeight=120,$maxWidth=160,$nolink=0)
4101  {
4102  global $conf,$user,$langs;
4103 
4104  include_once DOL_DOCUMENT_ROOT .'/core/lib/files.lib.php';
4105  include_once DOL_DOCUMENT_ROOT .'/core/lib/images.lib.php';
4106 
4107  $sortfield='position_name';
4108  $sortorder='asc';
4109 
4110  $dir = $sdir . '/';
4111  $pdir = '/';
4112  $dir .= get_exdir(0,0,0,0,$this,'product').$this->ref.'/';
4113  $pdir .= get_exdir(0,0,0,0,$this,'product').$this->ref.'/';
4114 
4115  if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO))
4116  {
4117  $dir = $sdir . '/'. get_exdir($this->id,2,0,0,$this,'product') . $this->id ."/photos/";
4118  $pdir = '/' . get_exdir($this->id,2,0,0,$this,'product') . $this->id ."/photos/";
4119  }
4120 
4121  // Defined relative dir to DOL_DATA_ROOT
4122  $relativedir = '';
4123  if ($dir)
4124  {
4125  $relativedir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT,'/').'/', '', $dir);
4126  $relativedir = preg_replace('/^[\\/]/','',$relativedir);
4127  $relativedir = preg_replace('/[\\/]$/','',$relativedir);
4128  }
4129 
4130  $dirthumb = $dir.'thumbs/';
4131  $pdirthumb = $pdir.'thumbs/';
4132 
4133  $return ='<!-- Photo -->'."\n";
4134  $nbphoto=0;
4135 
4136  $filearray=dol_dir_list($dir,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
4137 
4138  /*if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) // For backward compatiblity, we scan also old dirs
4139  {
4140  $filearrayold=dol_dir_list($dirold,"files",0,'','(\.meta|_preview.*\.png)$',$sortfield,(strtolower($sortorder)=='desc'?SORT_DESC:SORT_ASC),1);
4141  $filearray=array_merge($filearray, $filearrayold);
4142  }*/
4143 
4144  completeFileArrayWithDatabaseInfo($filearray, $relativedir);
4145 
4146  if (count($filearray))
4147  {
4148  if ($sortfield && $sortorder)
4149  {
4150  $filearray=dol_sort_array($filearray, $sortfield, $sortorder);
4151  }
4152 
4153  foreach($filearray as $key => $val)
4154  {
4155  $photo='';
4156  $file = $val['name'];
4157 
4158  //if (! utf8_check($file)) $file=utf8_encode($file); // To be sure file is stored in UTF8 in memory
4159 
4160  //if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
4161  if (image_format_supported($file) >= 0)
4162  {
4163  $nbphoto++;
4164  $photo = $file;
4165  $viewfilename = $file;
4166 
4167  if ($size == 1 || $size == 'small') { // Format vignette
4168 
4169  // Find name of thumb file
4170  $photo_vignette=basename(getImageFileNameForSize($dir.$file, '_small'));
4171  if (! dol_is_file($dirthumb.$photo_vignette)) $photo_vignette='';
4172 
4173  // Get filesize of original file
4174  $imgarray=dol_getImageSize($dir.$photo);
4175 
4176  if ($nbbyrow > 0)
4177  {
4178  if ($nbphoto == 1) $return.= '<table width="100%" valign="top" align="center" border="0" cellpadding="2" cellspacing="2">';
4179 
4180  if ($nbphoto % $nbbyrow == 1) $return.= '<tr align=center valign=middle border=1>';
4181  $return.= '<td width="'.ceil(100/$nbbyrow).'%" class="photo">';
4182  }
4183  else if ($nbbyrow < 0) $return .= '<div class="inline-block">';
4184 
4185  $return.= "\n";
4186 
4187  $relativefile=preg_replace('/^\//', '', $pdir.$photo);
4188  if (empty($nolink))
4189  {
4190  $urladvanced=getAdvancedPreviewUrl('product', $relativefile, 0, 'entity='.$this->entity);
4191  if ($urladvanced) $return.='<a href="'.$urladvanced.'">';
4192  else $return.= '<a href="'.DOL_URL_ROOT.'/viewimage.php?modulepart=product&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" class="aphoto" target="_blank">';
4193  }
4194 
4195  // Show image (width height=$maxHeight)
4196  // Si fichier vignette disponible et image source trop grande, on utilise la vignette, sinon on utilise photo origine
4197  $alt=$langs->transnoentitiesnoconv('File').': '.$relativefile;
4198  $alt.=' - '.$langs->transnoentitiesnoconv('Size').': '.$imgarray['width'].'x'.$imgarray['height'];
4199 
4200  if (empty($maxHeight) || $photo_vignette && $imgarray['height'] > $maxHeight)
4201  {
4202  $return.= '<!-- Show thumb -->';
4203  $return.= '<img class="photo photowithmargin" border="0" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart=product&entity='.$this->entity.'&file='.urlencode($pdirthumb.$photo_vignette).'" title="'.dol_escape_htmltag($alt).'">';
4204  }
4205  else {
4206  $return.= '<!-- Show original file -->';
4207  $return.= '<img class="photo photowithmargin" border="0" height="'.$maxHeight.'" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart=product&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'" title="'.dol_escape_htmltag($alt).'">';
4208  }
4209 
4210  if (empty($nolink)) $return.= '</a>';
4211  $return.="\n";
4212 
4213  if ($showfilename) $return.= '<br>'.$viewfilename;
4214  if ($showaction)
4215  {
4216  $return.= '<br>';
4217  // On propose la generation de la vignette si elle n'existe pas et si la taille est superieure aux limites
4218  if ($photo_vignette && (image_format_supported($photo) > 0) && ($this->imgWidth > $maxWidth || $this->imgHeight > $maxHeight))
4219  {
4220  $return.= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&amp;action=addthumb&amp;file='.urlencode($pdir.$viewfilename).'">'.img_picto($langs->trans('GenerateThumb'),'refresh').'&nbsp;&nbsp;</a>';
4221  }
4222  if ($user->rights->produit->creer || $user->rights->service->creer)
4223  {
4224  // Link to resize
4225  $return.= '<a href="'.DOL_URL_ROOT.'/core/photos_resize.php?modulepart='.urlencode('produit|service').'&id='.$this->id.'&amp;file='.urlencode($pdir.$viewfilename).'" title="'.dol_escape_htmltag($langs->trans("Resize")).'">'.img_picto($langs->trans("Resize"),DOL_URL_ROOT.'/theme/common/transform-crop-and-resize','',1).'</a> &nbsp; ';
4226 
4227  // Link to delete
4228  $return.= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&amp;action=delete&amp;file='.urlencode($pdir.$viewfilename).'">';
4229  $return.= img_delete().'</a>';
4230  }
4231  }
4232  $return.= "\n";
4233 
4234  if ($nbbyrow > 0)
4235  {
4236  $return.= '</td>';
4237  if (($nbphoto % $nbbyrow) == 0) $return.= '</tr>';
4238  }
4239  else if ($nbbyrow < 0) $return.='</div>';
4240  }
4241 
4242  if (empty($size)) { // Format origine
4243  $return.= '<img class="photo photowithmargin" border="0" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart=product&entity='.$this->entity.'&file='.urlencode($pdir.$photo).'">';
4244 
4245  if ($showfilename) $return.= '<br>'.$viewfilename;
4246  if ($showaction)
4247  {
4248  if ($user->rights->produit->creer || $user->rights->service->creer)
4249  {
4250  // Link to resize
4251  $return.= '<a href="'.DOL_URL_ROOT.'/core/photos_resize.php?modulepart='.urlencode('produit|service').'&id='.$this->id.'&amp;file='.urlencode($pdir.$viewfilename).'" title="'.dol_escape_htmltag($langs->trans("Resize")).'">'.img_picto($langs->trans("Resize"),DOL_URL_ROOT.'/theme/common/transform-crop-and-resize','',1).'</a> &nbsp; ';
4252 
4253  // Link to delete
4254  $return.= '<a href="'.$_SERVER["PHP_SELF"].'?id='.$this->id.'&amp;action=delete&amp;file='.urlencode($pdir.$viewfilename).'">';
4255  $return.= img_delete().'</a>';
4256  }
4257  }
4258  }
4259 
4260  // On continue ou on arrete de boucler ?
4261  if ($nbmax && $nbphoto >= $nbmax) break;
4262  }
4263  }
4264 
4265  if ($size==1 || $size=='small')
4266  {
4267  if ($nbbyrow > 0)
4268  {
4269  // Ferme tableau
4270  while ($nbphoto % $nbbyrow)
4271  {
4272  $return.= '<td width="'.ceil(100/$nbbyrow).'%">&nbsp;</td>';
4273  $nbphoto++;
4274  }
4275 
4276  if ($nbphoto) $return.= '</table>';
4277  }
4278  }
4279  }
4280 
4281  $this->nbphoto = $nbphoto;
4282 
4283  return $return;
4284  }
4285 
4286 
4294  function liste_photos($dir,$nbmax=0)
4295  {
4296  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4297  include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
4298 
4299  $nbphoto=0;
4300  $tabobj=array();
4301 
4302  $dir_osencoded=dol_osencode($dir);
4303  $handle=@opendir($dir_osencoded);
4304  if (is_resource($handle))
4305  {
4306  while (($file = readdir($handle)) !== false)
4307  {
4308  if (! utf8_check($file)) $file=utf8_encode($file); // readdir returns ISO
4309  if (dol_is_file($dir.$file) && image_format_supported($file) >= 0)
4310  {
4311  $nbphoto++;
4312 
4313  // On determine nom du fichier vignette
4314  $photo=$file;
4315  $photo_vignette='';
4316  if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs))
4317  {
4318  $photo_vignette=preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
4319  }
4320 
4321  $dirthumb = $dir.'thumbs/';
4322 
4323  // Objet
4324  $obj=array();
4325  $obj['photo']=$photo;
4326  if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) $obj['photo_vignette']='thumbs/' . $photo_vignette;
4327  else $obj['photo_vignette']="";
4328 
4329  $tabobj[$nbphoto-1]=$obj;
4330 
4331  // On continue ou on arrete de boucler ?
4332  if ($nbmax && $nbphoto >= $nbmax) break;
4333  }
4334  }
4335 
4336  closedir($handle);
4337  }
4338 
4339  return $tabobj;
4340  }
4341 
4348  function delete_photo($file)
4349  {
4350  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4351  require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
4352 
4353  $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
4354  $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
4355  $filename = preg_replace('/'.preg_quote($dir,'/').'/i','',$file); // Nom du fichier
4356 
4357  // On efface l'image d'origine
4358  dol_delete_file($file, 0, 0, 0, $this); // For triggers
4359 
4360  // Si elle existe, on efface la vignette
4361  if (preg_match('/('.$this->regeximgext.')$/i',$filename,$regs))
4362  {
4363  $photo_vignette=preg_replace('/'.$regs[0].'/i','',$filename).'_small'.$regs[0];
4364  if (file_exists(dol_osencode($dirthumb.$photo_vignette)))
4365  {
4366  dol_delete_file($dirthumb.$photo_vignette);
4367  }
4368 
4369  $photo_vignette=preg_replace('/'.$regs[0].'/i','',$filename).'_mini'.$regs[0];
4370  if (file_exists(dol_osencode($dirthumb.$photo_vignette)))
4371  {
4372  dol_delete_file($dirthumb.$photo_vignette);
4373  }
4374  }
4375  }
4376 
4383  function get_image_size($file)
4384  {
4385  $file_osencoded=dol_osencode($file);
4386  $infoImg = getimagesize($file_osencoded); // Get information on image
4387  $this->imgWidth = $infoImg[0]; // Largeur de l'image
4388  $this->imgHeight = $infoImg[1]; // Hauteur de l'image
4389  }
4390 
4396  function load_state_board()
4397  {
4398  global $conf, $user, $hookmanager;
4399 
4400  $this->nb=array();
4401 
4402  $sql = "SELECT count(p.rowid) as nb, fk_product_type";
4403  $sql.= " FROM ".MAIN_DB_PREFIX."product as p";
4404  $sql.= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
4405  // Add where from hooks
4406  if (is_object($hookmanager))
4407  {
4408  $parameters=array();
4409  $reshook=$hookmanager->executeHooks('printFieldListWhere',$parameters); // Note that $action and $object may have been modified by hook
4410  $sql.=$hookmanager->resPrint;
4411  }
4412  $sql.= ' GROUP BY fk_product_type';
4413 
4414  $resql=$this->db->query($sql);
4415  if ($resql)
4416  {
4417  while ($obj=$this->db->fetch_object($resql))
4418  {
4419  if ($obj->fk_product_type == 1) $this->nb["services"]=$obj->nb;
4420  else $this->nb["products"]=$obj->nb;
4421  }
4422  $this->db->free($resql);
4423  return 1;
4424  }
4425  else
4426  {
4427  dol_print_error($this->db);
4428  $this->error=$this->db->error();
4429  return -1;
4430  }
4431  }
4432 
4438  function isProduct()
4439  {
4440  return ($this->type == Product::TYPE_PRODUCT ? true : false);
4441  }
4442 
4448  function isService()
4449  {
4450  return ($this->type == Product::TYPE_SERVICE ? true : false);
4451  }
4452 
4461  function get_barcode($object,$type='')
4462  {
4463  global $conf;
4464 
4465  $result='';
4466  if (! empty($conf->global->BARCODE_PRODUCT_ADDON_NUM))
4467  {
4468  $dirsociete=array_merge(array('/core/modules/barcode/'),$conf->modules_parts['barcode']);
4469  foreach ($dirsociete as $dirroot)
4470  {
4471  $res=dol_include_once($dirroot.$conf->global->BARCODE_PRODUCT_ADDON_NUM.'.php');
4472  if ($res) break;
4473  }
4474  $var = $conf->global->BARCODE_PRODUCT_ADDON_NUM;
4475  $mod = new $var;
4476 
4477  $result=$mod->getNextValue($object,$type);
4478 
4479  dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
4480  }
4481  return $result;
4482  }
4483 
4491  function initAsSpecimen()
4492  {
4493  global $user,$langs,$conf,$mysoc;
4494 
4495  $now=dol_now();
4496 
4497  // Initialize parameters
4498  $this->specimen=1;
4499  $this->id=0;
4500  $this->ref = 'PRODUCT_SPEC';
4501  $this->label = 'PRODUCT SPECIMEN';
4502  $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now,'dayhourlog').'.';
4503  $this->specimen=1;
4504  $this->country_id=1;
4505  $this->tosell=1;
4506  $this->tobuy=1;
4507  $this->tobatch=0;
4508  $this->note='This is a comment (private)';
4509  $this->date_creation = $now;
4510  $this->date_modification = $now;
4511 
4512  $this->weight = 4;
4513  $this->weight_unit = 1;
4514 
4515  $this->length = 5;
4516  $this->length_unit = 1;
4517  $this->width = 6;
4518  $this->width_unit = 0;
4519  $this->height = null;
4520  $this->height_unit = null;
4521 
4522  $this->surface = 30;
4523  $this->surface_unit = 0;
4524  $this->volume = 300;
4525  $this->volume_unit = 0;
4526 
4527  $this->barcode=-1; // Create barcode automatically
4528  }
4529 
4536  function getLabelOfUnit($type='long')
4537  {
4538  global $langs;
4539 
4540  if (!$this->fk_unit) {
4541  return '';
4542  }
4543 
4544  $langs->load('products');
4545 
4546  $this->db->begin();
4547 
4548  $label_type = 'label';
4549 
4550  if ($type == 'short')
4551  {
4552  $label_type = 'short_label';
4553  }
4554 
4555  $sql = 'select '.$label_type.' from '.MAIN_DB_PREFIX.'c_units where rowid='.$this->fk_unit;
4556  $resql = $this->db->query($sql);
4557  if($resql && $this->db->num_rows($resql) > 0)
4558  {
4559  $res = $this->db->fetch_array($resql);
4560  $label = $res[$label_type];
4561  $this->db->free($resql);
4562  return $label;
4563  }
4564  else
4565  {
4566  $this->error=$this->db->error().' sql='.$sql;
4567  dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
4568  return -1;
4569  }
4570  }
4571 
4577  function hasbatch()
4578  {
4579  return ($this->status_batch == 1 ? true : false);
4580  }
4581 
4582 
4589  {
4590  global $conf;
4591 
4592  $maxpricesupplier=0;
4593 
4594  if (! empty($conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE))
4595  {
4596  require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
4597  $product_fourn = new ProductFournisseur($this->db);
4598  $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
4599 
4600  if (is_array($product_fourn_list) && count($product_fourn_list)>0)
4601  {
4602  foreach($product_fourn_list as $productfourn)
4603  {
4604  if ($productfourn->fourn_unitprice > $maxpricesupplier)
4605  {
4606  $maxpricesupplier = $productfourn->fourn_unitprice;
4607  }
4608  }
4609 
4610  $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
4611  }
4612  }
4613 
4614  return $maxpricesupplier;
4615  }
4616 
4617 
4627  public function setCategories($categories) {
4628  // Handle single category
4629  if (! is_array($categories)) {
4630  $categories = array($categories);
4631  }
4632 
4633  // Get current categories
4634  require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
4635  $c = new Categorie($this->db);
4636  $existing = $c->containing($this->id, Categorie::TYPE_PRODUCT, 'id');
4637 
4638  // Diff
4639  if (is_array($existing)) {
4640  $to_del = array_diff($existing, $categories);
4641  $to_add = array_diff($categories, $existing);
4642  } else {
4643  $to_del = array(); // Nothing to delete
4644  $to_add = $categories;
4645  }
4646 
4647  // Process
4648  foreach($to_del as $del) {
4649  if ($c->fetch($del) > 0) {
4650  $c->del_type($this, 'product');
4651  }
4652  }
4653  foreach ($to_add as $add) {
4654  if ($c->fetch($add) > 0) {
4655  $c->add_type($this, 'product');
4656  }
4657  }
4658 
4659  return;
4660  }
4661 
4670  public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
4671  {
4672  $tables = array(
4673  'product_customer_price',
4674  'product_customer_price_log'
4675  );
4676 
4677  return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
4678  }
4679 
4691  public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
4692  {
4693  global $conf, $db;
4694 
4695  $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".MAIN_DB_PREFIX."product_pricerules";
4696  $query = $db->query($sql);
4697 
4698  $rules = array();
4699 
4700  while ($result = $db->fetch_object($query)) {
4701  $rules[$result->level] = $result;
4702  }
4703 
4704  //Because prices can be based on other level's prices, we temporarily store them
4705  $prices = array(
4706  1 => $baseprice
4707  );
4708 
4709  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
4710 
4711  $price = $baseprice;
4712  $price_min = $baseprice;
4713 
4714  //We have to make sure it does exist and it is > 0
4715  //First price level only allows changing min_price
4716  if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
4717  $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent/100));
4718  }
4719 
4720  $prices[$i] = $price;
4721 
4722  //We have to make sure it does exist and it is > 0
4723  if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
4724  $price_min = $price * (1 - ($rules[$i]->var_min_percent/100));
4725  }
4726 
4727  //Little check to make sure the price is modified before triggering generation
4728  $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
4729  $check_type = ($baseprice == $this->multiprices_base_type[$i]);
4730 
4731  if ($check_amount && $check_type) {
4732  continue;
4733  }
4734 
4735  if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
4736  return -1;
4737  }
4738  }
4739 
4740  return 1;
4741  }
4742 
4747  public function getRights()
4748  {
4749  global $user;
4750 
4751  if ($this->isProduct()) {
4752  return $user->rights->produit;
4753  } else {
4754  return $user->rights->service;
4755  }
4756  }
4757 
4764  function info($id)
4765  {
4766  $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
4767  $sql.= " p.fk_user_author, p.fk_user_modif";
4768  $sql.= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
4769  $sql.= " WHERE p.rowid = ".$id;
4770 
4771  $result=$this->db->query($sql);
4772  if ($result)
4773  {
4774  if ($this->db->num_rows($result))
4775  {
4776  $obj = $this->db->fetch_object($result);
4777 
4778  $this->id = $obj->rowid;
4779 
4780  if ($obj->fk_user_author) {
4781  $cuser = new User($this->db);
4782  $cuser->fetch($obj->fk_user_author);
4783  $this->user_creation = $cuser;
4784  }
4785 
4786  if ($obj->fk_user_modif) {
4787  $muser = new User($this->db);
4788  $muser->fetch($obj->fk_user_modif);
4789  $this->user_modification = $muser;
4790  }
4791 
4792  $this->ref = $obj->ref;
4793  $this->date_creation = $this->db->jdate($obj->date_creation);
4794  $this->date_modification = $this->db->jdate($obj->date_modification);
4795  }
4796 
4797  $this->db->free($result);
4798 
4799  }
4800  else
4801  {
4802  dol_print_error($this->db);
4803  }
4804  }
4805 
4806 }
is_sousproduit($fk_parent, $fk_child)
Verifie si c'est un sous-produit.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
update_sousproduit($id_pere, $id_fils, $qty, $incdec=1)
Modify composed product.
image_format_supported($file)
Return if a filename is file name of a supported image format.
Definition: images.lib.php:38
$multiprices
Arrays for multiprices.
Class to manage stock movements.
getLibFinished()
Retourne le libelle du finished du produit.
load_stats_facture_fournisseur($socid=0)
Charge tableau des stats facture pour le produit/service.
img_picto($titlealt, $picto, $moreatt= '', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='')
Show picto whatever it's its name (generic function)
dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disablevirusscan=0, $uploaderrorcode=0, $nohook=0, $varfiles='addedfile')
Make control on an uploaded file from an GUI page and move it to final destination.
Definition: files.lib.php:962
getRights()
Returns the rights used for this class.
const STATUS_CLOSED
Warehouse closed, inactive.
create($user, $notrigger=0)
Insert product into database.
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
Class to parse product price expressions.
addThumbs($file)
Build thumb.
dol_getImageSize($file, $url=false)
Return size of image file on disk (Supported extensions are gif, jpg, png and bmp) ...
Definition: images.lib.php:75
getImageFileNameForSize($file, $extName, $extImgTarget='')
Return the filename of file to get the thumbs.
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.
setPriceExpression($expression_id)
Sets the supplier price expression.
dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
Advanced sort array by second index function, which produces ascending (default) or descending output...
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm=false, $check=1)
Return a timestamp date built from detailed informations (by default a local PHP server timestamp) Re...
load_stock($option='')
Load information about stock of a product into ->stock_reel, ->stock_warehouse[] (including stock_war...
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.
</td >< tdclass="liste_titre"align="right"></td ></tr >< trclass="liste_titre">< inputtype="checkbox"onClick="toggle(this)"/> Ref p ref Label p label Duration p duration warehouseinternal SELECT description FROM product_lang WHERE qty< br > qty qty qty StockTooLow img yes disabled img no img no< trclass="oddeven">< td >< inputtype="checkbox"class="check"name="'.$i.'"'.$disabled.'></td >< td >< inputtype="checkbox"class="check"name="choose'.$i.'"></td >< tdclass="nowrap"></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:554
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
$duration_unit
Exoiration unit.
loadBatchInfo($batch)
Load existing information about a serial.
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart)
Return a path to have a the directory according to object where files are stored. ...
$product_fourn_id
Id du fournisseur.
completeFileArrayWithDatabaseInfo(&$filearray, $relativedir)
Complete $filearray with data from database.
Definition: files.lib.php:297
Class to manage products or services.
getNomUrl($withpicto=0, $option='', $maxlength=0, $save_lastsearch_value=-1)
Return clicable link of object (with eventually picto)
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:39
load_stats_commande($socid=0, $filtrestatut='', $forVirtualStock=0)
Charge tableau des stats commande client pour le produit/service.
const TYPE_STOCKKIT
Advanced feature: stock kit.
Class to manage Dolibarr database access.
$weight
Unites de mesure.
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'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.
dol_print_error($db='', $error='', $errors=null)
Affiche message erreur system avec toutes les informations pour faciliter le diagnostic et la remonte...
getCountry($searchkey, $withcode='', $dbtouse=0, $outputlangs='', $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
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_buyprice($prodfournprice, $qty, $product_id=0, $fourn_ref='', $fk_soc=0)
Read price used by a provider.
fetch($id='', $ref='', $ref_ext='', $ignore_expression=0)
Load a product in memory from database.
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.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0)
Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields...
get_arbo_each_prod($multiply=1)
reconstruit l'arborescence des produits sous la forme d'un tableau
clone_fournisseurs($fromId, $toId)
Recopie les fournisseurs et prix fournisseurs d'un produit/service sur un autre.
dol_dir_list($path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="")
Scan a directory and return a list of files/directories.
Definition: files.lib.php:58
get_nb_achat($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or supplier invoices in which product is included.
$desiredstock
Ask for replenishment when $desiredstock < $stock_reel.
$localtax1_tx
Other local taxes.
load_stats_facture($socid=0)
Charge tableau des stats facture pour le produit/service.
$default_vat_code
Default VAT code for product (link to code into llx_c_tva but without foreign keys) ...
$price_by_qty
Price by quantity arrays.
initAsSpecimen()
Initialise an instance with random values.
Class to manage standard extra fields.
Class to manage hooks.
list_suppliers()
Renvoie la liste des fournisseurs du produit/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.
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 ...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='')
Write log message into outputs.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
check_barcode($valuetotest, $typefortest)
Check barcode.
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.
getAdvancedPreviewUrl($modulepart, $relativepath, $alldata=0, $param='')
Return URL we can use for advanced preview links.
correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $dlc='', $dluo='', $lot='', $inventorycode='', $origin_element='', $origin_id=null)
Adjust stock in a warehouse for product with batch number.
get_each_prod()
Renvoie tous les sousproduits dans le tableau res, chaque ligne de res contient : id -> qty...
delete_photo($file)
Efface la photo du produit et sa vignette.
measuring_units_squared($unit)
Transform a given unit 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.
load_stats_sending($socid=0, $filtrestatut='', $forVirtualStock=0)
Charge tableau des stats expedition client pour le produit/service.
get_image_size($file)
Load size of image file.
load_virtual_stock()
Load value ->stock_theorique of a product.
_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:1234
fetch_prods($prod)
fonction recursive uniquement utilisee par get_each_prod, ajoute chaque sousproduits dans le tableau ...
load_stats_commande_fournisseur($socid=0, $filtrestatut='', $forVirtualStock=0)
Charge tableau des stats commande fournisseur pour le produit/service.
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.
update($id, $user, $notrigger=false, $action='update')
Update a record into database.
deleteExtraFields()
Delete all extra fields values for the current object.
get_nb_order($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or orders in which product is included.
get_nb_vente($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or customers invoices in which product is included.
_get_stats($sql, $mode, $year=0)
Return an array formated for showing graphs.
get_nb_ordersupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or orders in which product is included.
dol_now($mode='gmt')
Return date for now.
utf8_check($str)
Check if a string is in UTF8.
img_delete($titlealt= 'default', $other= 'class="pictodelete"')
Show delete logo.
Class ProductCombination Used to represent a product combination.
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
isService()
Return if object is a product.
dol_is_file($pathoffile)
Return if path is a file.
Definition: files.lib.php:427
add_photo($sdir, $file)
Move an uploaded file described into $file array into target directory $sdir.
fetch_prod_arbo($prod, $compl_path="", $multiply=1, $level=1, $id_parent=0)
Fonction recursive uniquement utilisee par get_arbo_each_prod, recompose l'arborescence des sousprodu...
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...
get_nb_propalsupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or proposals in which product is included.
dol_print_date($time, $format='', $tzoutput='tzserver', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
static findAll($db, $fk_product_stock, $with_qty=0, $fk_product=0)
Return all batch detail records for a given product and warehouse.
delMultiLangs($langtodelete, $user)
Delete a language for this product.
get_nb_propal($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or proposals in which product is included.
Manage record for batch number management.
$pmp
Average price value for product entry into stock (PMP)
get_barcode($object, $type='')
Get a barcode from the module to generate barcode values.
measuring_units_cubed($unit)
Transform a given unit into the cube of that unit, if known.
measuring_units_string($unit, $measuring_style='')
Return translation label of a unit key.
dol_mkdir($dir, $dataroot='', $newmask=null)
Creation of a directory (this can create recursive subdir)
if(!empty($conf->facture->enabled)&&$user->rights->facture->lire) if(!empty($conf->fournisseur->enabled)&&$user->rights->fournisseur->facture->lire) if(!empty($conf->don->enabled)&&$user->rights->societe->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)&&$user->rights->fournisseur->facture->lire) $resql
Social contributions to pay.
Definition: index.php:1013
if(!function_exists('dol_getprefix')) dol_include_once($relpath, $classname='')
Return a prefix to use for this Dolibarr instance, for session/cookie names or email id...
get_sousproduits_arbo()
Return tree of all subproducts for product.
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null)
Remove a file or several files with a mask.
Definition: files.lib.php:1103
$imgWidth
Taille de l'image.
correct_stock($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $inventorycode='', $origin_element='', $origin_id=null)
Adjust stock in a warehouse for product.
load_state_board()
Load indicators this->nb for the dashboard.
log_price_delete($user, $rowid)
Delete a price line.
hasFatherOrChild()
Return all parent products for current product (first level only)
clone_associations($fromId, $toId)
Clone links between products.
setCategories($categories)
Sets object to supplied categories.
updatePrice($newprice, $newpricebase, $user, $newvat='', $newminprice='', $level=0, $newnpr=0, $newpbq=0, $ignore_autogen=0, $localtaxes_array=array(), $newdefaultvatcode='')
Modify customer price of a product/Service.
call_trigger($trigger_name, $user)
Call trigger based on this instance.
getMultiLangs()
Load array this->multilangs.
load_stats_reception($socid=0, $filtrestatut='', $forVirtualStock=0)
Charge tableau des stats réception fournisseur pour le produit/service.
load_stats_contrat($socid=0)
Charge tableau des stats contrat pour le produit/service.
check()
Check that ref and label are ok.
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='')
Clean a string from all punctuation characters to use it as a ref or login.
type
Definition: viewcat.php:283
getLibStatut($mode=0, $type=0)
Return label of status of object.
price2num($amount, $rounding='', $alreadysqlnb=0)
Function that return a number with universal decimal format (decimal separator is '...
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
load_stats_proposal_supplier($socid=0)
Charge tableau des stats propale pour le produit/service.
generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
Generates prices for a product based on product multiprice generation rules.
Parent class of all other business classes (invoices, contracts, proposals, orders, ...)
Class to manage predefined suppliers products.
Class to manage triggers.
show_photos($sdir, $size=0, $nbmax=0, $nbbyrow=5, $showfilename=0, $showaction=0, $maxHeight=120, $maxWidth=160, $nolink=0)
Show photos of a product (nbmax maximum), into several columns TODO Move this into html...
getLabelOfUnit($type='long')
Returns the text label from units dictionary.
hasbatch()
Return if object has a sell-by date or eat-by date.
__construct($db)
Constructor.