dolibarr  9.0.0
product.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2001-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3  * Copyright (C) 2004-2014 Laurent Destailleur <eldy@users.sourceforge.net>
4  * Copyright (C) 2005-2015 Regis Houssin <regis.houssin@inodbox.com>
5  * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
6  * Copyright (C) 2007-2011 Jean Heimburger <jean@tiaris.info>
7  * Copyright (C) 2010-2018 Juanjo Menent <jmenent@2byte.es>
8  * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
9  * Copyright (C) 2013-2014 Cedric GROSS <c.gross@kreiz-it.fr>
10  * Copyright (C) 2013-2016 Marcos García <marcosgdf@gmail.com>
11  * Copyright (C) 2011-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-2018 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 {
49  public $element='product';
50 
54  public $table_element='product';
55 
59  public $fk_element='fk_product';
60 
61  protected $childtables=array('supplier_proposaldet', 'propaldet','commandedet','facturedet','contratdet','facture_fourn_det','commande_fournisseurdet'); // To test if we can delete object
62 
68  public $ismultientitymanaged = 1;
69 
73  protected $table_ref_field = 'ref';
74 
75  public $regeximgext='\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.xpm|\.xbm'; // See also into images.lib.php
76 
77  /*
78  * @deprecated
79  * @see label
80  */
81  public $libelle;
87  public $label;
88 
94  public $description;
95 
101  public $type = self::TYPE_PRODUCT;
102 
108  public $price; // Price net
109 
115  public $price_ttc;
116 
122  public $price_min;
123 
129  public $price_min_ttc;
130 
131  /*
132  * Base price ('TTC' for price including tax or 'HT' for net price)
133  * @var float
134  */
135  public $price_base_type;
136 
138  public $multiprices=array();
139  public $multiprices_ttc=array();
140  public $multiprices_base_type=array();
141  public $multiprices_min=array();
142  public $multiprices_min_ttc=array();
143  public $multiprices_tva_tx=array();
144  public $multiprices_recuperableonly=array();
145 
148  public $prices_by_qty=array();
149  public $prices_by_qty_id=array();
150  public $prices_by_qty_list=array();
151 
154 
156  public $tva_tx;
157 
159  public $tva_npr=0;
160 
163  public $localtax2_tx;
164  public $localtax1_type;
165  public $localtax2_type;
166 
172  public $stock_reel = 0;
173 
179  public $stock_theorique;
180 
186  public $cost_price;
187 
189  public $pmp;
190 
196  public $seuil_stock_alerte=0;
197 
201  public $desiredstock=0;
202 
203  /*
204  * Service expiration
205  */
206  public $duration_value;
207 
212 
218  public $status=0;
219 
225  public $status_buy=0;
226 
232  public $finished;
233 
239  public $status_batch=0;
240 
246  public $customcode;
247 
253  public $url;
254 
256  public $weight;
257  public $weight_units;
258  public $length;
259  public $length_units;
260  public $surface;
261  public $surface_units;
262  public $volume;
263  public $volume_units;
264 
265  public $accountancy_code_sell;
266  public $accountancy_code_sell_intra;
267  public $accountancy_code_sell_export;
268  public $accountancy_code_buy;
269 
276  public $barcode;
277 
283  public $barcodes_extra=array();
284 
285  public $stats_propale=array();
286  public $stats_commande=array();
287  public $stats_contrat=array();
288  public $stats_facture=array();
289  public $stats_commande_fournisseur=array();
290 
291  public $multilangs=array();
292 
294  public $imgWidth;
295  public $imgHeight;
296 
297  public $date_creation;
298  public $date_modification;
299 
302 
305 
306  public $nbphoto=0;
307 
309  public $stock_warehouse=array();
310 
311  public $oldcopy;
312 
316  public $fk_price_expression;
317 
318  /* To store supplier price found */
319  public $fourn_pu;
320  public $fourn_price_base_type;
321  public $fourn_socid;
322 
327  public $ref_fourn;
328  public $ref_supplier;
329 
335  public $fk_unit;
336 
342  public $price_autogen = 0;
343 
344 
345  public $fields = array(
346  'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'index'=>1, 'position'=>1, 'comment'=>'Id'),
347  'ref' =>array('type'=>'varchar(128)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>1, 'index'=>1, 'position'=>10, 'searchall'=>1, 'comment'=>'Reference of object'),
348  'entity' =>array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'default'=>1, 'notnull'=>1, 'index'=>1, 'position'=>20),
349  'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>61),
350  'note' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>62),
351  'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>500),
352  'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>501),
353  //'date_valid' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>502),
354  'fk_user_author'=>array('type'=>'integer', 'label'=>'UserAuthor', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>510, 'foreignkey'=>'llx_user.rowid'),
355  'fk_user_modif' =>array('type'=>'integer', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>511),
356  //'fk_user_valid' =>array('type'=>'integer', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>512),
357  'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'index'=>0, 'position'=>1000),
358  //'tosell' =>array('type'=>'integer', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>0, 'index'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Active', -1=>'Cancel')),
359  //'tobuy' =>array('type'=>'integer', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>0, 'index'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Active', -1=>'Cancel')),
360  );
361 
365  const TYPE_PRODUCT = 0;
369  const TYPE_SERVICE = 1;
373  const TYPE_ASSEMBLYKIT = 2;
377  const TYPE_STOCKKIT = 3;
378 
379 
385  function __construct($db)
386  {
387  $this->db = $db;
388  $this->canvas = '';
389  }
390 
396  function check()
397  {
398  $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
399 
400  $err = 0;
401  if (dol_strlen(trim($this->ref)) == 0) {
402  $err++;
403  }
404 
405  if (dol_strlen(trim($this->label)) == 0) {
406  $err++;
407  }
408 
409  if ($err > 0) {
410  return 0;
411  }
412  else
413  {
414  return 1;
415  }
416  }
417 
425  function create($user,$notrigger=0)
426  {
427  global $conf, $langs;
428 
429  $error=0;
430 
431  // Clean parameters
432  $this->ref = dol_string_nospecial(trim($this->ref));
433  $this->label = trim($this->label);
434  $this->price_ttc=price2num($this->price_ttc);
435  $this->price=price2num($this->price);
436  $this->price_min_ttc=price2num($this->price_min_ttc);
437  $this->price_min=price2num($this->price_min);
438  if (empty($this->tva_tx)) { $this->tva_tx = 0;
439  }
440  if (empty($this->tva_npr)) { $this->tva_npr = 0;
441  }
442  //Local taxes
443  if (empty($this->localtax1_tx)) { $this->localtax1_tx = 0;
444  }
445  if (empty($this->localtax2_tx)) { $this->localtax2_tx = 0;
446  }
447  if (empty($this->localtax1_type)) { $this->localtax1_type = '0';
448  }
449  if (empty($this->localtax2_type)) { $this->localtax2_type = '0';
450  }
451 
452  if (empty($this->price)) { $this->price = 0;
453  }
454  if (empty($this->price_min)) { $this->price_min = 0;
455  }
456 
457  // Price by quantity
458  if (empty($this->price_by_qty)) { $this->price_by_qty = 0;
459  }
460 
461  if (empty($this->status)) { $this->status = 0;
462  }
463  if (empty($this->status_buy)) { $this->status_buy = 0;
464  }
465 
466  $price_ht=0;
467  $price_ttc=0;
468  $price_min_ht=0;
469  $price_min_ttc=0;
470 
471  //
472  if ($this->price_base_type == 'TTC' && $this->price_ttc > 0) {
473  $price_ttc = price2num($this->price_ttc, 'MU');
474  $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)), 'MU');
475  }
476 
477  //
478  if ($this->price_base_type != 'TTC' && $this->price > 0) {
479  $price_ht = price2num($this->price, 'MU');
480  $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)), 'MU');
481  }
482 
483  //
484  if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC')) {
485  $price_min_ttc = price2num($this->price_min_ttc, 'MU');
486  $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)), 'MU');
487  }
488 
489  //
490  if (($this->price_min > 0) && ($this->price_base_type != 'TTC')) {
491  $price_min_ht = price2num($this->price_min, 'MU');
492  $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)), 'MU');
493  }
494 
495  $this->accountancy_code_buy = trim($this->accountancy_code_buy);
496  $this->accountancy_code_sell= trim($this->accountancy_code_sell);
497  $this->accountancy_code_sell_intra= trim($this->accountancy_code_sell_intra);
498  $this->accountancy_code_sell_export= trim($this->accountancy_code_sell_export);
499 
500  // Barcode value
501  $this->barcode=trim($this->barcode);
502 
503  // Check parameters
504  if (empty($this->label)) {
505  $this->error='ErrorMandatoryParametersNotProvided';
506  return -1;
507  }
508 
509  if (empty($this->ref)) {
510  // Load object modCodeProduct
511  $module=(! empty($conf->global->PRODUCT_CODEPRODUCT_ADDON)?$conf->global->PRODUCT_CODEPRODUCT_ADDON:'mod_codeproduct_leopard');
512  if ($module != 'mod_codeproduct_leopard') // Do not load module file for leopard
513  {
514  if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
515  $module = substr($module, 0, dol_strlen($module)-4);
516  }
517  dol_include_once('/core/modules/product/'.$module.'.php');
518  $modCodeProduct = new $module;
519  if (! empty($modCodeProduct->code_auto)) {
520  $this->ref = $modCodeProduct->getNextValue($this, $this->type);
521  }
522  unset($modCodeProduct);
523  }
524 
525  if (empty($this->ref)) {
526  $this->error='ProductModuleNotSetupForAutoRef';
527  return -2;
528  }
529  }
530 
531  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);
532 
533  $now=dol_now();
534 
535  $this->db->begin();
536 
537  // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
538  if ($this->barcode == -1) { $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
539  }
540 
541  // Check more parameters
542  // If error, this->errors[] is filled
543  $result = $this->verify();
544 
545  if ($result >= 0) {
546  $sql = "SELECT count(*) as nb";
547  $sql.= " FROM ".MAIN_DB_PREFIX."product";
548  $sql.= " WHERE entity IN (".getEntity('product').")";
549  $sql.= " AND ref = '" .$this->db->escape($this->ref)."'";
550 
551  $result = $this->db->query($sql);
552  if ($result) {
553  $obj = $this->db->fetch_object($result);
554  if ($obj->nb == 0) {
555  // Produit non deja existant
556  $sql = "INSERT INTO ".MAIN_DB_PREFIX."product (";
557  $sql.= "datec";
558  $sql.= ", entity";
559  $sql.= ", ref";
560  $sql.= ", ref_ext";
561  $sql.= ", price_min";
562  $sql.= ", price_min_ttc";
563  $sql.= ", label";
564  $sql.= ", fk_user_author";
565  $sql.= ", fk_product_type";
566  $sql.= ", price";
567  $sql.= ", price_ttc";
568  $sql.= ", price_base_type";
569  $sql.= ", tobuy";
570  $sql.= ", tosell";
571  $sql.= ", accountancy_code_buy";
572  $sql.= ", accountancy_code_sell";
573  $sql.= ", accountancy_code_sell_intra";
574  $sql.= ", accountancy_code_sell_export";
575  $sql.= ", canvas";
576  $sql.= ", finished";
577  $sql.= ", tobatch";
578  $sql.= ", fk_unit";
579  $sql.= ") VALUES (";
580  $sql.= "'".$this->db->idate($now)."'";
581  $sql.= ", ".$conf->entity;
582  $sql.= ", '".$this->db->escape($this->ref)."'";
583  $sql.= ", ".(! empty($this->ref_ext)?"'".$this->db->escape($this->ref_ext)."'":"null");
584  $sql.= ", ".price2num($price_min_ht);
585  $sql.= ", ".price2num($price_min_ttc);
586  $sql.= ", ".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null");
587  $sql.= ", ".$user->id;
588  $sql.= ", ".$this->type;
589  $sql.= ", ".price2num($price_ht);
590  $sql.= ", ".price2num($price_ttc);
591  $sql.= ", '".$this->db->escape($this->price_base_type)."'";
592  $sql.= ", ".$this->status;
593  $sql.= ", ".$this->status_buy;
594  $sql.= ", '".$this->db->escape($this->accountancy_code_buy)."'";
595  $sql.= ", '".$this->db->escape($this->accountancy_code_sell)."'";
596  $sql.= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
597  $sql.= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
598  $sql.= ", '".$this->db->escape($this->canvas)."'";
599  $sql.= ", ".((! isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'null' : (int) $this->finished);
600  $sql.= ", ".((empty($this->status_batch) || $this->status_batch < 0)? '0':$this->status_batch);
601  $sql.= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
602  $sql.= ")";
603 
604  dol_syslog(get_class($this)."::Create", LOG_DEBUG);
605  $result = $this->db->query($sql);
606  if ($result ) {
607  $id = $this->db->last_insert_id(MAIN_DB_PREFIX."product");
608 
609  if ($id > 0) {
610  $this->id = $id;
611  $this->price = $price_ht;
612  $this->price_ttc = $price_ttc;
613  $this->price_min = $price_min_ht;
614  $this->price_min_ttc = $price_min_ttc;
615 
616  $result = $this->_log_price($user);
617  if ($result > 0) {
618  if ($this->update($id, $user, true, 'add') <= 0) {
619  $error++;
620  }
621  }
622  else
623  {
624  $error++;
625  $this->error=$this->db->lasterror();
626  }
627  }
628  else
629  {
630  $error++;
631  $this->error='ErrorFailedToGetInsertedId';
632  }
633  }
634  else
635  {
636  $error++;
637  $this->error=$this->db->lasterror();
638  }
639  }
640  else
641  {
642  // Product already exists with this ref
643  $langs->load("products");
644  $error++;
645  $this->error = "ErrorProductAlreadyExists";
646  }
647  }
648  else
649  {
650  $error++;
651  $this->error=$this->db->lasterror();
652  }
653 
654  if (! $error && ! $notrigger) {
655  // Call trigger
656  $result=$this->call_trigger('PRODUCT_CREATE', $user);
657  if ($result < 0) { $error++;
658  }
659  // End call triggers
660  }
661 
662  if (! $error) {
663  $this->db->commit();
664  return $this->id;
665  }
666  else
667  {
668  $this->db->rollback();
669  return -$error;
670  }
671  }
672  else
673  {
674  $this->db->rollback();
675  dol_syslog(get_class($this)."::Create fails verify ".join(',', $this->errors), LOG_WARNING);
676  return -3;
677  }
678  }
679 
680 
687  function verify()
688  {
689  $this->errors=array();
690 
691  $result = 0;
692  $this->ref = trim($this->ref);
693 
694  if (! $this->ref) {
695  $this->errors[] = 'ErrorBadRef';
696  $result = -2;
697  }
698 
699  $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
700  if ($rescode) {
701  if ($rescode == -1) {
702  $this->errors[] = 'ErrorBadBarCodeSyntax';
703  }
704  elseif ($rescode == -2) {
705  $this->errors[] = 'ErrorBarCodeRequired';
706  }
707  elseif ($rescode == -3) {
708  // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
709  $this->errors[] = 'ErrorBarCodeAlreadyUsed';
710  }
711 
712  $result = -3;
713  }
714 
715  return $result;
716  }
717 
718  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
729  function check_barcode($valuetotest,$typefortest)
730  {
731  // phpcs:enable
732  global $conf;
733  if (! empty($conf->barcode->enabled) && ! empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
734  $module=strtolower($conf->global->BARCODE_PRODUCT_ADDON_NUM);
735 
736  $dirsociete=array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
737  foreach ($dirsociete as $dirroot)
738  {
739  $res=dol_include_once($dirroot.$module.'.php');
740  if ($res) { break;
741  }
742  }
743 
744  $mod = new $module();
745 
746  dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
747  $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
748  return $result;
749  }
750  else
751  {
752  return 0;
753  }
754  }
755 
766  function update($id, $user, $notrigger=false, $action='update')
767  {
768  global $langs, $conf, $hookmanager;
769 
770  $error=0;
771 
772  // Check parameters
773  if (! $this->label) { $this->label = 'MISSING LABEL';
774  }
775 
776  // Clean parameters
777  $this->ref = dol_string_nospecial(trim($this->ref));
778  $this->label = trim($this->label);
779  $this->description = trim($this->description);
780  $this->note = (isset($this->note) ? trim($this->note) : null);
781  $this->weight = price2num($this->weight);
782  $this->weight_units = trim($this->weight_units);
783  $this->length = price2num($this->length);
784  $this->length_units = trim($this->length_units);
785  $this->width = price2num($this->width);
786  $this->width_units = trim($this->width_units);
787  $this->height = price2num($this->height);
788  $this->height_units = trim($this->height_units);
789  // set unit not defined
790  if ($this->length_units) { $this->width_units = $this->length_units; // Not used yet
791  }
792  if ($this->length_units) { $this->height_units = $this->length_units; // Not used yet
793  }
794  // Automated compute surface and volume if not filled
795  if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
796  $this->surface = $this->length * $this->width;
797  $this->surface_units = measuring_units_squared($this->length_units);
798  }
799  if (empty($this->volume) && !empty($this->surface_units) && !empty($this->height) && $this->length_units == $this->height_units) {
800  $this->volume = $this->surface * $this->height;
801  $this->volume_units = measuring_units_cubed($this->height_units);
802  }
803 
804  $this->surface = price2num($this->surface);
805  $this->surface_units = trim($this->surface_units);
806  $this->volume = price2num($this->volume);
807  $this->volume_units = trim($this->volume_units);
808  if (empty($this->tva_tx)) { $this->tva_tx = 0;
809  }
810  if (empty($this->tva_npr)) { $this->tva_npr = 0;
811  }
812  if (empty($this->localtax1_tx)) { $this->localtax1_tx = 0;
813  }
814  if (empty($this->localtax2_tx)) { $this->localtax2_tx = 0;
815  }
816  if (empty($this->localtax1_type)) { $this->localtax1_type = '0';
817  }
818  if (empty($this->localtax2_type)) { $this->localtax2_type = '0';
819  }
820  if (empty($this->status)) { $this->status = 0;
821  }
822  if (empty($this->status_buy)) { $this->status_buy = 0;
823  }
824 
825  if (empty($this->country_id)) { $this->country_id = 0;
826  }
827 
828  // Barcode value
829  $this->barcode=trim($this->barcode);
830 
831  $this->accountancy_code_buy = trim($this->accountancy_code_buy);
832  $this->accountancy_code_sell= trim($this->accountancy_code_sell);
833  $this->accountancy_code_sell_intra= trim($this->accountancy_code_sell_intra);
834  $this->accountancy_code_sell_export= trim($this->accountancy_code_sell_export);
835 
836 
837  $this->db->begin();
838 
839  // Check name is required and codes are ok or unique.
840  // If error, this->errors[] is filled
841  if ($action != 'add') {
842  $result = $this->verify(); // We don't check when update called during a create because verify was already done
843  }
844 
845  if ($result >= 0) {
846  if (empty($this->oldcopy)) {
847  $org=new self($this->db);
848  $org->fetch($this->id);
849  $this->oldcopy=$org;
850  }
851 
852  // Test if batch management is activated on existing product
853  // If yes, we create missing entries into product_batch
854  if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
855  //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
856  $valueforundefinedlot = '000000';
857 
858  dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
859 
860  $this->load_stock();
861  foreach ($this->stock_warehouse as $idW => $ObjW) // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
862  {
863  $qty_batch = 0;
864  foreach ($ObjW->detail_batch as $detail) // Each lines of detail in product_batch of the current $ObjW = product_stock
865  {
866  if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
867  // We discard this line, we will create it later
868  $sqlclean="DELETE FROM ".MAIN_DB_PREFIX."product_batch WHERE batch in('Undefined', '".$valueforundefinedlot."') AND fk_product_stock = ".$ObjW->id;
869  $result = $this->db->query($sqlclean);
870  if (! $result) {
871  dol_print_error($this->db);
872  exit;
873  }
874  continue;
875  }
876 
877  $qty_batch += $detail->qty;
878  }
879  // Quantities in batch details are not same as stock quantity,
880  // so we add a default batch record to complete and get same qty in parent and child table
881  if ($ObjW->real <> $qty_batch) {
882  $ObjBatch = new Productbatch($this->db);
883  $ObjBatch->batch = $valueforundefinedlot;
884  $ObjBatch->qty = ($ObjW->real - $qty_batch);
885  $ObjBatch->fk_product_stock = $ObjW->id;
886 
887  if ($ObjBatch->create($user, 1) < 0) {
888  $error++;
889  $this->errors=$ObjBatch->errors;
890  }
891  }
892  }
893  }
894 
895  // For automatic creation
896  if ($this->barcode == -1) { $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
897  }
898 
899  $sql = "UPDATE ".MAIN_DB_PREFIX."product";
900  $sql.= " SET label = '" . $this->db->escape($this->label) ."'";
901  $sql.= ", ref = '" . $this->db->escape($this->ref) ."'";
902  $sql.= ", ref_ext = ".(! empty($this->ref_ext)?"'".$this->db->escape($this->ref_ext)."'":"null");
903  $sql.= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
904  $sql.= ", tva_tx = " . $this->tva_tx;
905  $sql.= ", recuperableonly = " . $this->tva_npr;
906  $sql.= ", localtax1_tx = " . $this->localtax1_tx;
907  $sql.= ", localtax2_tx = " . $this->localtax2_tx;
908  $sql.= ", localtax1_type = " . ($this->localtax1_type!=''?"'".$this->db->escape($this->localtax1_type)."'":"'0'");
909  $sql.= ", localtax2_type = " . ($this->localtax2_type!=''?"'".$this->db->escape($this->localtax2_type)."'":"'0'");
910 
911  $sql.= ", barcode = ". (empty($this->barcode)?"null":"'".$this->db->escape($this->barcode)."'");
912  $sql.= ", fk_barcode_type = ". (empty($this->barcode_type)?"null":$this->db->escape($this->barcode_type));
913 
914  $sql.= ", tosell = " . (int) $this->status;
915  $sql.= ", tobuy = " . (int) $this->status_buy;
916  $sql.= ", tobatch = " . ((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
917  $sql.= ", finished = " . ((! isset($this->finished) || $this->finished < 0) ? "null" : (int) $this->finished);
918  $sql.= ", weight = " . ($this->weight!='' ? "'".$this->db->escape($this->weight)."'" : 'null');
919  $sql.= ", weight_units = " . ($this->weight_units!='' ? "'".$this->db->escape($this->weight_units)."'": 'null');
920  $sql.= ", length = " . ($this->length!='' ? "'".$this->db->escape($this->length)."'" : 'null');
921  $sql.= ", length_units = " . ($this->length_units!='' ? "'".$this->db->escape($this->length_units)."'" : 'null');
922  $sql.= ", width= " . ($this->width!='' ? "'".$this->db->escape($this->width)."'" : 'null');
923  $sql.= ", width_units = " . ($this->width_units!='' ? "'".$this->db->escape($this->width_units)."'" : 'null');
924  $sql.= ", height = " . ($this->height!='' ? "'".$this->db->escape($this->height)."'" : 'null');
925  $sql.= ", height_units = " . ($this->height_units!='' ? "'".$this->db->escape($this->height_units)."'" : 'null');
926  $sql.= ", surface = " . ($this->surface!='' ? "'".$this->db->escape($this->surface)."'" : 'null');
927  $sql.= ", surface_units = " . ($this->surface_units!='' ? "'".$this->db->escape($this->surface_units)."'" : 'null');
928  $sql.= ", volume = " . ($this->volume!='' ? "'".$this->db->escape($this->volume)."'" : 'null');
929  $sql.= ", volume_units = " . ($this->volume_units!='' ? "'".$this->db->escape($this->volume_units)."'" : 'null');
930  $sql.= ", fk_default_warehouse = " . ($this->fk_default_warehouse > 0 ? $this->db->escape($this->fk_default_warehouse) : 'null');
931  $sql.= ", seuil_stock_alerte = " . ((isset($this->seuil_stock_alerte) && $this->seuil_stock_alerte != '') ? "'".$this->db->escape($this->seuil_stock_alerte)."'" : "null");
932  $sql.= ", description = '" . $this->db->escape($this->description) ."'";
933  $sql.= ", url = " . ($this->url?"'".$this->db->escape($this->url)."'":'null');
934  $sql.= ", customcode = '" . $this->db->escape($this->customcode) ."'";
935  $sql.= ", fk_country = " . ($this->country_id > 0 ? (int) $this->country_id : 'null');
936  $sql.= ", note = ".(isset($this->note) ? "'" .$this->db->escape($this->note)."'" : 'null');
937  $sql.= ", duration = '" . $this->db->escape($this->duration_value . $this->duration_unit) ."'";
938  $sql.= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy)."'";
939  $sql.= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell)."'";
940  $sql.= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra)."'";
941  $sql.= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export)."'";
942  $sql.= ", desiredstock = " . ((isset($this->desiredstock) && $this->desiredstock != '') ? (int) $this->desiredstock : "null");
943  $sql.= ", cost_price = " . ($this->cost_price != '' ? $this->db->escape($this->cost_price) : 'null');
944  $sql.= ", fk_unit= " . (!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
945  $sql.= ", price_autogen = " . (!$this->price_autogen ? 0 : 1);
946  $sql.= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
947  $sql.= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL');
948  // stock field is not here because it is a denormalized value from product_stock.
949  $sql.= " WHERE rowid = " . $id;
950 
951  dol_syslog(get_class($this)."::update", LOG_DEBUG);
952 
953  $resql=$this->db->query($sql);
954  if ($resql) {
955  $this->id = $id;
956 
957  // Multilangs
958  if (! empty($conf->global->MAIN_MULTILANGS)) {
959  if ($this->setMultiLangs($user) < 0) {
960  $this->error=$langs->trans("Error")." : ".$this->db->error()." - ".$sql;
961  return -2;
962  }
963  }
964 
965  $action='update';
966 
967  // Actions on extra fields
968  if (! $error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) {
969  $result=$this->insertExtraFields();
970  if ($result < 0) {
971  $error++;
972  }
973  }
974 
975  if (! $error && ! $notrigger) {
976  // Call trigger
977  $result=$this->call_trigger('PRODUCT_MODIFY', $user);
978  if ($result < 0) { $error++;
979  }
980  // End call triggers
981  }
982 
983  if (! $error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
984  // We remove directory
985  if ($conf->product->dir_output) {
986  $olddir = $conf->product->dir_output . "/" . dol_sanitizeFileName($this->oldcopy->ref);
987  $newdir = $conf->product->dir_output . "/" . dol_sanitizeFileName($this->ref);
988  if (file_exists($olddir)) {
989  //include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
990  //$res = dol_move($olddir, $newdir);
991  // do not use dol_move with directory
992  $res = @rename($olddir, $newdir);
993  if (! $res) {
994  $langs->load("errors");
995  $this->error=$langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
996  $error++;
997  }
998  }
999  }
1000  }
1001 
1002  if (! $error) {
1003  if ($conf->variants->enabled) {
1004 
1005  include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1006 
1007  $comb = new ProductCombination($this->db);
1008 
1009  foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1010  $currcomb->updateProperties($this);
1011  }
1012  }
1013 
1014  $this->db->commit();
1015  return 1;
1016  }
1017  else
1018  {
1019  $this->db->rollback();
1020  return -$error;
1021  }
1022  }
1023  else
1024  {
1025  if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1026  $langs->load("errors");
1027  if (empty($conf->barcode->enabled) || empty($this->barcode)) { $this->error=$langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1028  } else { $this->error=$langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1029  }
1030  $this->errors[]=$this->error;
1031  $this->db->rollback();
1032  return -1;
1033  }
1034  else
1035  {
1036  $this->error=$langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1037  $this->errors[]=$this->error;
1038  $this->db->rollback();
1039  return -2;
1040  }
1041  }
1042  }
1043  else
1044  {
1045  $this->db->rollback();
1046  dol_syslog(get_class($this)."::Update fails verify ".join(',', $this->errors), LOG_WARNING);
1047  return -3;
1048  }
1049  }
1050 
1058  function delete(User $user, $notrigger=0)
1059  {
1060  // Deprecation warning
1061  if ($id > 0) {
1062  dol_syslog(__METHOD__ . " with parameter is deprecated", LOG_WARNING);
1063  }
1064 
1065  global $conf, $langs;
1066  include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1067 
1068  $error=0;
1069 
1070  // Clean parameters
1071  if (empty($id)) { $id=$this->id;
1072  } else { $this->fetch($id);
1073  }
1074 
1075  // Check parameters
1076  if (empty($id)) {
1077  $this->error = "Object must be fetched before calling delete";
1078  return -1;
1079  }
1080  if (($this->type == Product::TYPE_PRODUCT && empty($user->rights->produit->supprimer)) || ($this->type == Product::TYPE_SERVICE && empty($user->rights->service->supprimer))) {
1081  $this->error = "ErrorForbidden";
1082  return 0;
1083  }
1084 
1085  $objectisused = $this->isObjectUsed($id);
1086  if (empty($objectisused)) {
1087  $this->db->begin();
1088 
1089  if (! $error && empty($notrigger)) {
1090  // Call trigger
1091  $result=$this->call_trigger('PRODUCT_DELETE', $user);
1092  if ($result < 0) { $error++;
1093  }
1094  // End call triggers
1095  }
1096 
1097  // Delete from product_batch on product delete
1098  if (! $error) {
1099  $sql = "DELETE FROM ".MAIN_DB_PREFIX.'product_batch';
1100  $sql.= " WHERE fk_product_stock IN (";
1101  $sql.= "SELECT rowid FROM ".MAIN_DB_PREFIX.'product_stock';
1102  $sql.= " WHERE fk_product = ".$id.")";
1103 
1104  $result = $this->db->query($sql);
1105  if (! $result) {
1106  $error++;
1107  $this->errors[] = $this->db->lasterror();
1108  }
1109  }
1110 
1111  // Delete all child tables
1112  if (! $error) {
1113  $elements = array('product_fournisseur_price','product_price','product_lang','categorie_product','product_stock','product_customer_price','product_lot'); // product_batch is done before
1114  foreach($elements as $table)
1115  {
1116  if (! $error) {
1117  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$table;
1118  $sql.= " WHERE fk_product = ".$id;
1119 
1120  $result = $this->db->query($sql);
1121  if (! $result) {
1122  $error++;
1123  $this->errors[] = $this->db->lasterror();
1124  }
1125  }
1126  }
1127  }
1128 
1129  if (!$error) {
1130 
1131  include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1132  include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1133 
1134  //If it is a parent product, then we remove the association with child products
1135  $prodcomb = new ProductCombination($this->db);
1136 
1137  if ($prodcomb->deleteByFkProductParent($user, $id) < 0) {
1138  $error++;
1139  $this->errors[] = 'Error deleting combinations';
1140  }
1141 
1142  //We also check if it is a child product
1143  if (!$error && ($prodcomb->fetchByFkProductChild($id) > 0) && ($prodcomb->delete($user) < 0)) {
1144  $error++;
1145  $this->errors[] = 'Error deleting child combination';
1146  }
1147  }
1148 
1149  // Delete from product_association
1150  if (!$error) {
1151  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_association";
1152  $sql.= " WHERE fk_product_pere = ".$id." OR fk_product_fils = ".$id;
1153 
1154  $result = $this->db->query($sql);
1155  if (! $result) {
1156  $error++;
1157  $this->errors[] = $this->db->lasterror();
1158  }
1159  }
1160 
1161  // Delete product
1162  if (! $error) {
1163  $sqlz = "DELETE FROM ".MAIN_DB_PREFIX."product";
1164  $sqlz.= " WHERE rowid = ".$id;
1165 
1166  $resultz = $this->db->query($sqlz);
1167  if (! $resultz ) {
1168  $error++;
1169  $this->errors[] = $this->db->lasterror();
1170  }
1171  }
1172 
1173  if (! $error) {
1174  // We remove directory
1175  $ref = dol_sanitizeFileName($this->ref);
1176  if ($conf->product->dir_output) {
1177  $dir = $conf->product->dir_output . "/" . $ref;
1178  if (file_exists($dir)) {
1179  $res=@dol_delete_dir_recursive($dir);
1180  if (! $res) {
1181  $this->errors[] = 'ErrorFailToDeleteDir';
1182  $error++;
1183  }
1184  }
1185  }
1186  }
1187 
1188  // Remove extrafields
1189  if ((! $error) && (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED))) // For avoid conflicts if trigger used
1190  {
1191  $result=$this->deleteExtraFields();
1192  if ($result < 0) {
1193  $error++;
1194  dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1195  }
1196  }
1197 
1198  if (! $error) {
1199  $this->db->commit();
1200  return 1;
1201  }
1202  else
1203  {
1204  foreach($this->errors as $errmsg)
1205  {
1206  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1207  $this->error.=($this->error?', '.$errmsg:$errmsg);
1208  }
1209  $this->db->rollback();
1210  return -$error;
1211  }
1212  }
1213  else
1214  {
1215  $this->error = "ErrorRecordIsUsedCantDelete";
1216  return 0;
1217  }
1218  }
1219 
1226  function setMultiLangs($user)
1227  {
1228  global $conf, $langs;
1229 
1230  $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1231  $current_lang = $langs->getDefaultLang();
1232 
1233  foreach ($langs_available as $key => $value)
1234  {
1235  if ($key == $current_lang) {
1236  $sql = "SELECT rowid";
1237  $sql.= " FROM ".MAIN_DB_PREFIX."product_lang";
1238  $sql.= " WHERE fk_product=".$this->id;
1239  $sql.= " AND lang='".$key."'";
1240 
1241  $result = $this->db->query($sql);
1242 
1243  if ($this->db->num_rows($result)) // if there is already a description line for this language
1244  {
1245  $sql2 = "UPDATE ".MAIN_DB_PREFIX."product_lang";
1246  $sql2.= " SET ";
1247  $sql2.= " label='".$this->db->escape($this->label)."',";
1248  $sql2.= " description='".$this->db->escape($this->description)."'";
1249  if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2.= ", note='".$this->db->escape($this->other)."'";
1250  }
1251  $sql2.= " WHERE fk_product=".$this->id." AND lang='".$this->db->escape($key)."'";
1252  }
1253  else
1254  {
1255  $sql2 = "INSERT INTO ".MAIN_DB_PREFIX."product_lang (fk_product, lang, label, description";
1256  if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2.=", note";
1257  }
1258  $sql2.= ")";
1259  $sql2.= " VALUES(".$this->id.",'".$this->db->escape($key)."','". $this->db->escape($this->label)."',";
1260  $sql2.= " '".$this->db->escape($this->description)."'";
1261  if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2.= ", '".$this->db->escape($this->other)."'";
1262  }
1263  $sql2.= ")";
1264  }
1265  dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
1266  if (! $this->db->query($sql2)) {
1267  $this->error=$this->db->lasterror();
1268  return -1;
1269  }
1270  }
1271  else if (isset($this->multilangs[$key])) {
1272  $sql = "SELECT rowid";
1273  $sql.= " FROM ".MAIN_DB_PREFIX."product_lang";
1274  $sql.= " WHERE fk_product=".$this->id;
1275  $sql.= " AND lang='".$key."'";
1276 
1277  $result = $this->db->query($sql);
1278 
1279  if ($this->db->num_rows($result)) // if there is already a description line for this language
1280  {
1281  $sql2 = "UPDATE ".MAIN_DB_PREFIX."product_lang";
1282  $sql2.= " SET ";
1283  $sql2.= " label='".$this->db->escape($this->multilangs["$key"]["label"])."',";
1284  $sql2.= " description='".$this->db->escape($this->multilangs["$key"]["description"])."'";
1285  if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2.= ", note='".$this->db->escape($this->multilangs["$key"]["other"])."'";
1286  }
1287  $sql2.= " WHERE fk_product=".$this->id." AND lang='".$this->db->escape($key)."'";
1288  }
1289  else
1290  {
1291  $sql2 = "INSERT INTO ".MAIN_DB_PREFIX."product_lang (fk_product, lang, label, description";
1292  if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2.=", note";
1293  }
1294  $sql2.= ")";
1295  $sql2.= " VALUES(".$this->id.",'".$this->db->escape($key)."','". $this->db->escape($this->multilangs["$key"]["label"])."',";
1296  $sql2.= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1297  if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2.= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1298  }
1299  $sql2.= ")";
1300  }
1301 
1302  // We do not save if main fields are empty
1303  if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
1304  if (! $this->db->query($sql2)) {
1305  $this->error=$this->db->lasterror();
1306  return -1;
1307  }
1308  }
1309  }
1310  else
1311  {
1312  // language is not current language and we didn't provide a multilang description for this language
1313  }
1314  }
1315 
1316  // Call trigger
1317  $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
1318  if ($result < 0) {
1319  $this->error = $this->db->lasterror();
1320  return -1;
1321  }
1322  // End call triggers
1323 
1324  return 1;
1325  }
1326 
1335  function delMultiLangs($langtodelete, $user)
1336  {
1337  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_lang";
1338  $sql.= " WHERE fk_product=".$this->id." AND lang='".$this->db->escape($langtodelete)."'";
1339 
1340  dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
1341  $result = $this->db->query($sql);
1342  if ($result) {
1343  // Call trigger
1344  $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
1345  if ($result < 0) {
1346  $this->error = $this->db->lasterror();
1347  dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1348  return -1;
1349  }
1350  // End call triggers
1351  return 1;
1352  }
1353  else
1354  {
1355  $this->error=$this->db->lasterror();
1356  dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1357  return -1;
1358  }
1359  }
1360 
1361  /*
1362  * Sets an accountancy code for a product.
1363  * Also calls PRODUCT_MODIFY trigger when modified
1364  *
1365  * @param string $type It can be 'buy', 'sell', 'sell_intra' or 'sell_export'
1366  * @param string $value Accountancy code
1367  * @return int <0 KO >0 OK
1368  */
1369  public function setAccountancyCode($type, $value)
1370  {
1371  global $user, $langs, $conf;
1372 
1373  $this->db->begin();
1374 
1375  if ($type == 'buy') {
1376  $field = 'accountancy_code_buy';
1377  } elseif ($type == 'sell') {
1378  $field = 'accountancy_code_sell';
1379  } elseif ($type == 'sell_intra') {
1380  $field = 'accountancy_code_sell_intra';
1381  } elseif ($type == 'sell_export') {
1382  $field = 'accountancy_code_sell_export';
1383  } else {
1384  return -1;
1385  }
1386 
1387  $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET ";
1388  $sql.= "$field = '".$this->db->escape($value)."'";
1389  $sql.= " WHERE rowid = ".$this->id;
1390 
1391  dol_syslog(get_class($this)."::".__FUNCTION__." sql=".$sql, LOG_DEBUG);
1392  $resql = $this->db->query($sql);
1393 
1394  if ($resql) {
1395  // Call triggers
1396  include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
1397  $interface=new Interfaces($this->db);
1398  $result=$interface->run_triggers('PRODUCT_MODIFY', $this, $user, $langs, $conf);
1399  if ($result < 0) {
1400  $this->errors=$interface->errors;
1401  $this->db->rollback();
1402  return -1;
1403  }
1404  // End call triggers
1405 
1406  $this->$field = $value;
1407 
1408  $this->db->commit();
1409  return 1;
1410  }
1411  else
1412  {
1413  $this->error=$this->db->lasterror();
1414  $this->db->rollback();
1415  return -1;
1416  }
1417  }
1418 
1424  function getMultiLangs()
1425  {
1426  global $langs;
1427 
1428  $current_lang = $langs->getDefaultLang();
1429 
1430  $sql = "SELECT lang, label, description, note as other";
1431  $sql.= " FROM ".MAIN_DB_PREFIX."product_lang";
1432  $sql.= " WHERE fk_product=".$this->id;
1433 
1434  $result = $this->db->query($sql);
1435  if ($result) {
1436  while ($obj = $this->db->fetch_object($result))
1437  {
1438  //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
1439  if ($obj->lang == $current_lang) // si on a les traduct. dans la langue courante on les charge en infos principales.
1440  {
1441  $this->label = $obj->label;
1442  $this->description = $obj->description;
1443  $this->other = $obj->other;
1444  }
1445  $this->multilangs["$obj->lang"]["label"] = $obj->label;
1446  $this->multilangs["$obj->lang"]["description"] = $obj->description;
1447  $this->multilangs["$obj->lang"]["other"] = $obj->other;
1448  }
1449  return 1;
1450  }
1451  else
1452  {
1453  $this->error="Error: ".$this->db->lasterror()." - ".$sql;
1454  return -1;
1455  }
1456  }
1457 
1458 
1459 
1460  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1468  function _log_price($user,$level=0)
1469  {
1470  // phpcs:enable
1471  global $conf;
1472 
1473  $now=dol_now();
1474 
1475  // Clean parameters
1476  if (empty($this->price_by_qty)) { $this->price_by_qty=0;
1477  }
1478 
1479  // Add new price
1480  $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,";
1481  $sql.= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
1482  $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.",";
1483  $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');
1484  $sql.= ")";
1485 
1486  dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
1487  $resql=$this->db->query($sql);
1488  if(! $resql) {
1489  $this->error=$this->db->lasterror();
1490  dol_print_error($this->db);
1491  return -1;
1492  }
1493  else
1494  {
1495  return 1;
1496  }
1497  }
1498 
1499 
1500  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1508  function log_price_delete($user, $rowid)
1509  {
1510  // phpcs:enable
1511  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_price_by_qty";
1512  $sql.= " WHERE fk_product_price=".$rowid;
1513  $resql=$this->db->query($sql);
1514 
1515  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_price";
1516  $sql.= " WHERE rowid=".$rowid;
1517  $resql=$this->db->query($sql);
1518  if ($resql) {
1519  return 1;
1520  }
1521  else
1522  {
1523  $this->error=$this->db->lasterror();
1524  return -1;
1525  }
1526  }
1527 
1528 
1529  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1542  function get_buyprice($prodfournprice, $qty, $product_id=0, $fourn_ref='', $fk_soc=0)
1543  {
1544  // phpcs:enable
1545  global $conf;
1546  $result = 0;
1547 
1548  // 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)
1549  $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent,";
1550  $sql.= " pfp.fk_product, pfp.ref_fourn, pfp.desc_fourn, pfp.fk_soc, pfp.tva_tx, pfp.fk_supplier_price_expression";
1551  $sql.= " ,pfp.default_vat_code";
1552  $sql.= " ,pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code";
1553  $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
1554  $sql.= " WHERE pfp.rowid = ".$prodfournprice;
1555  if ($qty > 0) { $sql.= " AND pfp.quantity <= ".$qty;
1556  }
1557  $sql.= " ORDER BY pfp.quantity DESC";
1558 
1559  dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
1560  $resql = $this->db->query($sql);
1561  if ($resql) {
1562  $obj = $this->db->fetch_object($resql);
1563  if ($obj && $obj->quantity > 0) // If we found a supplier prices from the id of supplier price
1564  {
1565  if (!empty($conf->dynamicprices->enabled) && !empty($obj->fk_supplier_price_expression)) {
1566  include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
1567  $prod_supplier = new ProductFournisseur($this->db);
1568  $prod_supplier->product_fourn_price_id = $obj->rowid;
1569  $prod_supplier->id = $obj->fk_product;
1570  $prod_supplier->fourn_qty = $obj->quantity;
1571  $prod_supplier->fourn_tva_tx = $obj->tva_tx;
1572  $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
1573  $priceparser = new PriceParser($this->db);
1574  $price_result = $priceparser->parseProductSupplier($prod_supplier);
1575  if ($price_result >= 0) {
1576  $obj->price = $price_result;
1577  }
1578  }
1579  $this->product_fourn_price_id = $obj->rowid;
1580  $this->buyprice = $obj->price; // deprecated
1581  $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
1582  $this->fourn_price_base_type = 'HT'; // Price base type
1583  $this->fourn_socid = $obj->fk_soc; // Company that offer this price
1584  $this->ref_fourn = $obj->ref_fourn; // deprecated
1585  $this->ref_supplier = $obj->ref_fourn; // Ref supplier
1586  $this->desc_supplier = $obj->desc_fourn; // desc supplier
1587  $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
1588  $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
1589  $this->default_vat_code = $obj->default_vat_code; // Vat code supplier
1590  $this->fourn_multicurrency_price = $obj->multicurrency_price;
1591  $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
1592  $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
1593  $this->fourn_multicurrency_id = $obj->fk_multicurrency;
1594  $this->fourn_multicurrency_code = $obj->multicurrency_code;
1595  $result=$obj->fk_product;
1596  return $result;
1597  }
1598  else // If not found
1599  {
1600  // 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.
1601  $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.fk_soc,";
1602  $sql.= " pfp.fk_product, pfp.ref_fourn as ref_supplier, pfp.desc_fourn as desc_supplier, pfp.tva_tx, pfp.fk_supplier_price_expression";
1603  $sql.= " ,pfp.default_vat_code";
1604  $sql.= " ,pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code";
1605  $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
1606  $sql.= " WHERE pfp.fk_product = ".$product_id;
1607  if ($fourn_ref != 'none') { $sql.= " AND pfp.ref_fourn = '".$fourn_ref."'";
1608  }
1609  if ($fk_soc > 0) { $sql.= " AND pfp.fk_soc = ".$fk_soc;
1610  }
1611  if ($qty > 0) { $sql.= " AND pfp.quantity <= ".$qty;
1612  }
1613  $sql.= " ORDER BY pfp.quantity DESC";
1614  $sql.= " LIMIT 1";
1615 
1616  dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
1617  $resql = $this->db->query($sql);
1618  if ($resql) {
1619  $obj = $this->db->fetch_object($resql);
1620  if ($obj && $obj->quantity > 0) // If found
1621  {
1622  if (!empty($conf->dynamicprices->enabled) && !empty($obj->fk_supplier_price_expression)) {
1623  include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
1624  $prod_supplier = new ProductFournisseur($this->db);
1625  $prod_supplier->product_fourn_price_id = $obj->rowid;
1626  $prod_supplier->id = $obj->fk_product;
1627  $prod_supplier->fourn_qty = $obj->quantity;
1628  $prod_supplier->fourn_tva_tx = $obj->tva_tx;
1629  $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
1630  $priceparser = new PriceParser($this->db);
1631  $price_result = $priceparser->parseProductSupplier($prod_supplier);
1632  if ($result >= 0) {
1633  $obj->price = $price_result;
1634  }
1635  }
1636  $this->product_fourn_price_id = $obj->rowid;
1637  $this->buyprice = $obj->price; // deprecated
1638  $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
1639  $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
1640  $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
1641  $this->fourn_socid = $obj->fk_soc; // Company that offer this price
1642  $this->ref_fourn = $obj->ref_supplier; // deprecated
1643  $this->ref_supplier = $obj->ref_supplier; // Ref supplier
1644  $this->desc_supplier = $obj->desc_supplier; // desc supplier
1645  $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
1646  $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
1647  $this->default_vat_code = $obj->default_vat_code; // Vat code supplier
1648  $this->fourn_multicurrency_price = $obj->multicurrency_price;
1649  $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
1650  $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
1651  $this->fourn_multicurrency_id = $obj->fk_multicurrency;
1652  $this->fourn_multicurrency_code = $obj->multicurrency_code;
1653  $result=$obj->fk_product;
1654  return $result;
1655  }
1656  else
1657  {
1658  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é.
1659  }
1660  }
1661  else
1662  {
1663  $this->error=$this->db->lasterror();
1664  return -3;
1665  }
1666  }
1667  }
1668  else
1669  {
1670  $this->error=$this->db->lasterror();
1671  return -2;
1672  }
1673  }
1674 
1675 
1692  function updatePrice($newprice, $newpricebase, $user, $newvat='',$newminprice=0, $level=0, $newnpr=0, $newpbq=0, $ignore_autogen=0, $localtaxes_array=array(), $newdefaultvatcode='')
1693  {
1694  global $conf,$langs;
1695 
1696  $id=$this->id;
1697 
1698  dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
1699 
1700  // Clean parameters
1701  if (empty($this->tva_tx)) { $this->tva_tx=0;
1702  }
1703  if (empty($newnpr)) { $newnpr=0;
1704  }
1705 
1706  // Check parameters
1707  if ($newvat == '') { $newvat=$this->tva_tx;
1708  }
1709 
1710  // If multiprices are enabled, then we check if the current product is subject to price autogeneration
1711  // Price will be modified ONLY when the first one is the one that is being modified
1712  if ((!empty($conf->global->PRODUIT_MULTIPRICES) || ! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
1713  return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
1714  }
1715 
1716  if (! empty($newminprice) && ($newminprice > $newprice)) {
1717  $this->error='ErrorPriceCantBeLowerThanMinPrice';
1718  return -1;
1719  }
1720 
1721  if ($newprice !== '' || $newprice === 0) {
1722  if ($newpricebase == 'TTC') {
1723  $price_ttc = price2num($newprice, 'MU');
1724  $price = price2num($newprice) / (1 + ($newvat / 100));
1725  $price = price2num($price, 'MU');
1726 
1727  if ($newminprice != '' || $newminprice == 0) {
1728  $price_min_ttc = price2num($newminprice, 'MU');
1729  $price_min = price2num($newminprice) / (1 + ($newvat / 100));
1730  $price_min = price2num($price_min, 'MU');
1731  }
1732  else
1733  {
1734  $price_min=0;
1735  $price_min_ttc=0;
1736  }
1737  }
1738  else
1739  {
1740  $price = price2num($newprice, 'MU');
1741  $price_ttc = ( $newnpr != 1 ) ? price2num($newprice) * (1 + ($newvat / 100)) : $price;
1742  $price_ttc = price2num($price_ttc, 'MU');
1743 
1744  if ($newminprice !== '' || $newminprice === 0) {
1745  $price_min = price2num($newminprice, 'MU');
1746  $price_min_ttc = price2num($newminprice) * (1 + ($newvat / 100));
1747  $price_min_ttc = price2num($price_min_ttc, 'MU');
1748  //print 'X'.$newminprice.'-'.$price_min;
1749  }
1750  else
1751  {
1752  $price_min=0;
1753  $price_min_ttc=0;
1754  }
1755  }
1756  //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
1757 
1758  if (count($localtaxes_array) > 0) {
1759  $localtaxtype1=$localtaxes_array['0'];
1760  $localtax1=$localtaxes_array['1'];
1761  $localtaxtype2=$localtaxes_array['2'];
1762  $localtax2=$localtaxes_array['3'];
1763  }
1764  else // old method. deprecated because ot can't retreive type
1765  {
1766  $localtaxtype1='0';
1767  $localtax1=get_localtax($newvat, 1);
1768  $localtaxtype2='0';
1769  $localtax2=get_localtax($newvat, 2);
1770  }
1771  if (empty($localtax1)) { $localtax1=0; // If = '' then = 0
1772  }
1773  if (empty($localtax2)) { $localtax2=0; // If = '' then = 0
1774  }
1775 
1776  $this->db->begin();
1777 
1778  // Ne pas mettre de quote sur les numeriques decimaux.
1779  // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
1780  $sql = "UPDATE ".MAIN_DB_PREFIX."product SET";
1781  $sql.= " price_base_type='".$newpricebase."',";
1782  $sql.= " price=".$price.",";
1783  $sql.= " price_ttc=".$price_ttc.",";
1784  $sql.= " price_min=".$price_min.",";
1785  $sql.= " price_min_ttc=".$price_min_ttc.",";
1786  $sql.= " localtax1_tx=".($localtax1>=0?$localtax1:'NULL').",";
1787  $sql.= " localtax2_tx=".($localtax2>=0?$localtax2:'NULL').",";
1788  $sql.= " localtax1_type=".($localtaxtype1!=''?"'".$localtaxtype1."'":"'0'").",";
1789  $sql.= " localtax2_type=".($localtaxtype2!=''?"'".$localtaxtype2."'":"'0'").",";
1790  $sql.= " default_vat_code=".($newdefaultvatcode?"'".$this->db->escape($newdefaultvatcode)."'":"null").",";
1791  $sql.= " tva_tx='".price2num($newvat)."',";
1792  $sql.= " recuperableonly='".$newnpr."'";
1793  $sql.= " WHERE rowid = ".$id;
1794 
1795  dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
1796  $resql=$this->db->query($sql);
1797  if ($resql) {
1798  $this->multiprices[$level] = $price;
1799  $this->multiprices_ttc[$level] = $price_ttc;
1800  $this->multiprices_min[$level]= $price_min;
1801  $this->multiprices_min_ttc[$level]= $price_min_ttc;
1802  $this->multiprices_base_type[$level]= $newpricebase;
1803  $this->multiprices_default_vat_code[$level]= $newdefaultvatcode;
1804  $this->multiprices_tva_tx[$level]= $newvat;
1805  $this->multiprices_recuperableonly[$level]= $newnpr;
1806 
1807  $this->price = $price;
1808  $this->price_ttc = $price_ttc;
1809  $this->price_min = $price_min;
1810  $this->price_min_ttc = $price_min_ttc;
1811  $this->price_base_type = $newpricebase;
1812  $this->default_vat_code = $newdefaultvatcode;
1813  $this->tva_tx = $newvat;
1814  $this->tva_npr = $newnpr;
1815  //Local taxes
1816  $this->localtax1_tx = $localtax1;
1817  $this->localtax2_tx = $localtax2;
1818  $this->localtax1_type = $localtaxtype1;
1819  $this->localtax2_type = $localtaxtype2;
1820 
1821  // Price by quantity
1822  $this->price_by_qty = $newpbq;
1823 
1824  $this->_log_price($user, $level); // Save price for level into table product_price
1825 
1826  $this->level = $level; // Store level of price edited for trigger
1827 
1828  // Call trigger
1829  $result=$this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
1830  if ($result < 0) {
1831  $this->db->rollback();
1832  return -1;
1833  }
1834  // End call triggers
1835 
1836  $this->db->commit();
1837  }
1838  else
1839  {
1840  $this->db->rollback();
1841  dol_print_error($this->db);
1842  }
1843  }
1844 
1845  return 1;
1846  }
1847 
1855  function setPriceExpression($expression_id)
1856  {
1857  global $user;
1858 
1859  $this->fk_price_expression = $expression_id;
1860 
1861  return $this->update($this->id, $user);
1862  }
1863 
1874  function fetch($id='', $ref='', $ref_ext='', $barcode='', $ignore_expression=0)
1875  {
1876  include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
1877 
1878  global $langs, $conf;
1879 
1880  dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
1881 
1882  // Check parameters
1883  if (! $id && ! $ref && ! $ref_ext && ! $barcode) {
1884  $this->error='ErrorWrongParameters';
1885  dol_syslog(get_class($this)."::fetch ".$this->error);
1886  return -1;
1887  }
1888 
1889  $sql = "SELECT rowid, ref, ref_ext, label, description, url, note as note_private, customcode, fk_country, price, price_ttc,";
1890  $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,";
1891  $sql.= " tobuy, fk_product_type, duration, fk_default_warehouse, seuil_stock_alerte, canvas, weight, weight_units,";
1892  $sql.= " length, length_units, width, width_units, height, height_units,";
1893  $sql.= " surface, surface_units, volume, volume_units, barcode, fk_barcode_type, finished,";
1894  $sql.= " accountancy_code_buy, accountancy_code_sell, accountancy_code_sell_intra, accountancy_code_sell_export, stock, pmp,";
1895  $sql.= " datec, tms, import_key, entity, desiredstock, tobatch, fk_unit,";
1896  $sql.= " fk_price_expression, price_autogen";
1897  $sql.= " FROM ".MAIN_DB_PREFIX."product";
1898  if ($id) { $sql.= " WHERE rowid = ".$this->db->escape($id);
1899  } else
1900  {
1901  $sql.= " WHERE entity IN (".getEntity($this->element).")";
1902  if ($ref) { $sql.= " AND ref = '".$this->db->escape($ref)."'";
1903  } else if ($ref_ext) { $sql.= " AND ref_ext = '".$this->db->escape($ref_ext)."'";
1904  } else if ($barcode) { $sql.= " AND barcode = '".$this->db->escape($barcode)."'";
1905  }
1906  }
1907 
1908  $resql = $this->db->query($sql);
1909  if ($resql ) {
1910  if ($this->db->num_rows($resql) > 0) {
1911  $obj = $this->db->fetch_object($resql);
1912 
1913  $this->id = $obj->rowid;
1914  $this->ref = $obj->ref;
1915  $this->ref_ext = $obj->ref_ext;
1916  $this->label = $obj->label;
1917  $this->description = $obj->description;
1918  $this->url = $obj->url;
1919  $this->note_private = $obj->note_private;
1920  $this->note = $obj->note_private; // deprecated
1921 
1922  $this->type = $obj->fk_product_type;
1923  $this->status = $obj->tosell;
1924  $this->status_buy = $obj->tobuy;
1925  $this->status_batch = $obj->tobatch;
1926 
1927  $this->customcode = $obj->customcode;
1928  $this->country_id = $obj->fk_country;
1929  $this->country_code = getCountry($this->country_id, 2, $this->db);
1930  $this->price = $obj->price;
1931  $this->price_ttc = $obj->price_ttc;
1932  $this->price_min = $obj->price_min;
1933  $this->price_min_ttc = $obj->price_min_ttc;
1934  $this->price_base_type = $obj->price_base_type;
1935  $this->cost_price = $obj->cost_price;
1936  $this->default_vat_code = $obj->default_vat_code;
1937  $this->tva_tx = $obj->tva_tx;
1939  $this->tva_npr = $obj->tva_npr;
1940  $this->recuperableonly = $obj->tva_npr; // For backward compatibility
1942  $this->localtax1_tx = $obj->localtax1_tx;
1943  $this->localtax2_tx = $obj->localtax2_tx;
1944  $this->localtax1_type = $obj->localtax1_type;
1945  $this->localtax2_type = $obj->localtax2_type;
1946 
1947  $this->finished = $obj->finished;
1948  $this->duration = $obj->duration;
1949  $this->duration_value = substr($obj->duration, 0, dol_strlen($obj->duration)-1);
1950  $this->duration_unit = substr($obj->duration, -1);
1951  $this->canvas = $obj->canvas;
1952  $this->weight = $obj->weight;
1953  $this->weight_units = $obj->weight_units;
1954  $this->length = $obj->length;
1955  $this->length_units = $obj->length_units;
1956  $this->width = $obj->width;
1957  $this->width_units = $obj->width_units;
1958  $this->height = $obj->height;
1959  $this->height_units = $obj->height_units;
1960 
1961  $this->surface = $obj->surface;
1962  $this->surface_units = $obj->surface_units;
1963  $this->volume = $obj->volume;
1964  $this->volume_units = $obj->volume_units;
1965  $this->barcode = $obj->barcode;
1966  $this->barcode_type = $obj->fk_barcode_type;
1967 
1968  $this->accountancy_code_buy = $obj->accountancy_code_buy;
1969  $this->accountancy_code_sell = $obj->accountancy_code_sell;
1970  $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
1971  $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
1972 
1973  $this->fk_default_warehouse = $obj->fk_default_warehouse;
1974  $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
1975  $this->desiredstock = $obj->desiredstock;
1976  $this->stock_reel = $obj->stock;
1977  $this->pmp = $obj->pmp;
1978 
1979  $this->date_creation = $obj->datec;
1980  $this->date_modification = $obj->tms;
1981  $this->import_key = $obj->import_key;
1982  $this->entity = $obj->entity;
1983 
1984  $this->ref_ext = $obj->ref_ext;
1985  $this->fk_price_expression = $obj->fk_price_expression;
1986  $this->fk_unit = $obj->fk_unit;
1987  $this->price_autogen = $obj->price_autogen;
1988 
1989  $this->db->free($resql);
1990 
1991  // Retreive all extrafield
1992  // fetch optionals attributes and labels
1993  $this->fetch_optionals();
1994 
1995  // multilangs
1996  if (! empty($conf->global->MAIN_MULTILANGS)) { $this->getMultiLangs();
1997  }
1998 
1999  // Load multiprices array
2000  if (! empty($conf->global->PRODUIT_MULTIPRICES)) // prices per segment
2001  {
2002  for ($i=1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++)
2003  {
2004  $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2005  $sql.= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2006  $sql.= " FROM ".MAIN_DB_PREFIX."product_price";
2007  $sql.= " WHERE entity IN (".getEntity('productprice').")";
2008  $sql.= " AND price_level=".$i;
2009  $sql.= " AND fk_product = ".$this->id;
2010  $sql.= " ORDER BY date_price DESC, rowid DESC";
2011  $sql.= " LIMIT 1";
2012  $resql = $this->db->query($sql);
2013  if ($resql) {
2014  $result = $this->db->fetch_array($resql);
2015 
2016  $this->multiprices[$i]=$result["price"];
2017  $this->multiprices_ttc[$i]=$result["price_ttc"];
2018  $this->multiprices_min[$i]=$result["price_min"];
2019  $this->multiprices_min_ttc[$i]=$result["price_min_ttc"];
2020  $this->multiprices_base_type[$i]=$result["price_base_type"];
2021  // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2022  $this->multiprices_tva_tx[$i]=$result["tva_tx"]; // TODO Add ' ('.$result['default_vat_code'].')'
2023  $this->multiprices_recuperableonly[$i]=$result["recuperableonly"];
2024 
2025  // Price by quantity
2026  /*
2027  $this->prices_by_qty[$i]=$result["price_by_qty"];
2028  $this->prices_by_qty_id[$i]=$result["rowid"];
2029  // Récuperation de la liste des prix selon qty si flag positionné
2030  if ($this->prices_by_qty[$i] == 1)
2031  {
2032  $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2033  $sql.= " FROM ".MAIN_DB_PREFIX."product_price_by_qty";
2034  $sql.= " WHERE fk_product_price = ".$this->prices_by_qty_id[$i];
2035  $sql.= " ORDER BY quantity ASC";
2036  $resultat=array();
2037  $resql = $this->db->query($sql);
2038  if ($resql)
2039  {
2040  $ii=0;
2041  while ($result= $this->db->fetch_array($resql)) {
2042  $resultat[$ii]=array();
2043  $resultat[$ii]["rowid"]=$result["rowid"];
2044  $resultat[$ii]["price"]= $result["price"];
2045  $resultat[$ii]["unitprice"]= $result["unitprice"];
2046  $resultat[$ii]["quantity"]= $result["quantity"];
2047  $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2048  $resultat[$ii]["remise"]= $result["remise"]; // deprecated
2049  $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2050  $ii++;
2051  }
2052  $this->prices_by_qty_list[$i]=$resultat;
2053  }
2054  else
2055  {
2056  dol_print_error($this->db);
2057  return -1;
2058  }
2059  }*/
2060  }
2061  else
2062  {
2063  dol_print_error($this->db);
2064  return -1;
2065  }
2066  }
2067  }
2068  elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES)) // prices per customers
2069  {
2070  // Nothing loaded by default. List may be very long.
2071  }
2072  else if (! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY)) // prices per quantity
2073  {
2074  $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2075  $sql.= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
2076  $sql.= " FROM ".MAIN_DB_PREFIX."product_price";
2077  $sql.= " WHERE fk_product = ".$this->id;
2078  $sql.= " ORDER BY date_price DESC, rowid DESC";
2079  $sql.= " LIMIT 1";
2080  $resql = $this->db->query($sql);
2081  if ($resql) {
2082  $result = $this->db->fetch_array($resql);
2083 
2084  // Price by quantity
2085  $this->prices_by_qty[0]=$result["price_by_qty"];
2086  $this->prices_by_qty_id[0]=$result["rowid"];
2087  // Récuperation de la liste des prix selon qty si flag positionné
2088  if ($this->prices_by_qty[0] == 1) {
2089  $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
2090  $sql.= " FROM ".MAIN_DB_PREFIX."product_price_by_qty";
2091  $sql.= " WHERE fk_product_price = ".$this->prices_by_qty_id[0];
2092  $sql.= " ORDER BY quantity ASC";
2093  $resultat=array();
2094  $resql = $this->db->query($sql);
2095  if ($resql) {
2096  $ii=0;
2097  while ($result= $this->db->fetch_array($resql)) {
2098  $resultat[$ii]=array();
2099  $resultat[$ii]["rowid"]=$result["rowid"];
2100  $resultat[$ii]["price"]= $result["price"];
2101  $resultat[$ii]["unitprice"]= $result["unitprice"];
2102  $resultat[$ii]["quantity"]= $result["quantity"];
2103  $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2104  //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
2105  $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2106  $ii++;
2107  }
2108  $this->prices_by_qty_list[0]=$resultat;
2109  }
2110  else
2111  {
2112  dol_print_error($this->db);
2113  return -1;
2114  }
2115  }
2116  }
2117  else
2118  {
2119  dol_print_error($this->db);
2120  return -1;
2121  }
2122  }
2123  else if (! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) // prices per customer and quantity
2124  {
2125  for ($i=1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++)
2126  {
2127  $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2128  $sql.= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2129  $sql.= " FROM ".MAIN_DB_PREFIX."product_price";
2130  $sql.= " WHERE entity IN (".getEntity('productprice').")";
2131  $sql.= " AND price_level=".$i;
2132  $sql.= " AND fk_product = ".$this->id;
2133  $sql.= " ORDER BY date_price DESC, rowid DESC";
2134  $sql.= " LIMIT 1";
2135  $resql = $this->db->query($sql);
2136  if ($resql) {
2137  $result = $this->db->fetch_array($resql);
2138 
2139  $this->multiprices[$i]=$result["price"];
2140  $this->multiprices_ttc[$i]=$result["price_ttc"];
2141  $this->multiprices_min[$i]=$result["price_min"];
2142  $this->multiprices_min_ttc[$i]=$result["price_min_ttc"];
2143  $this->multiprices_base_type[$i]=$result["price_base_type"];
2144  // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2145  $this->multiprices_tva_tx[$i]=$result["tva_tx"]; // TODO Add ' ('.$result['default_vat_code'].')'
2146  $this->multiprices_recuperableonly[$i]=$result["recuperableonly"];
2147 
2148  // Price by quantity
2149  $this->prices_by_qty[$i]=$result["price_by_qty"];
2150  $this->prices_by_qty_id[$i]=$result["rowid"];
2151  // Récuperation de la liste des prix selon qty si flag positionné
2152  if ($this->prices_by_qty[$i] == 1) {
2153  $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2154  $sql.= " FROM ".MAIN_DB_PREFIX."product_price_by_qty";
2155  $sql.= " WHERE fk_product_price = ".$this->prices_by_qty_id[$i];
2156  $sql.= " ORDER BY quantity ASC";
2157  $resultat=array();
2158  $resql = $this->db->query($sql);
2159  if ($resql) {
2160  $ii=0;
2161  while ($result= $this->db->fetch_array($resql)) {
2162  $resultat[$ii]=array();
2163  $resultat[$ii]["rowid"]=$result["rowid"];
2164  $resultat[$ii]["price"]= $result["price"];
2165  $resultat[$ii]["unitprice"]= $result["unitprice"];
2166  $resultat[$ii]["quantity"]= $result["quantity"];
2167  $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2168  $resultat[$ii]["remise"]= $result["remise"]; // deprecated
2169  $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2170  $ii++;
2171  }
2172  $this->prices_by_qty_list[$i]=$resultat;
2173  }
2174  else
2175  {
2176  dol_print_error($this->db);
2177  return -1;
2178  }
2179  }
2180  }
2181  else
2182  {
2183  dol_print_error($this->db);
2184  return -1;
2185  }
2186  }
2187  }
2188 
2189  if (!empty($conf->dynamicprices->enabled) && !empty($this->fk_price_expression) && empty($ignore_expression)) {
2190  include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2191  $priceparser = new PriceParser($this->db);
2192  $price_result = $priceparser->parseProduct($this);
2193  if ($price_result >= 0) {
2194  $this->price = $price_result;
2195  // Calculate the VAT
2196  $this->price_ttc = price2num($this->price) * (1 + ($this->tva_tx / 100));
2197  $this->price_ttc = price2num($this->price_ttc, 'MU');
2198  }
2199  }
2200 
2201  // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
2202  // Instead we just init the stock_warehouse array
2203  $this->stock_warehouse = array();
2204 
2205  return 1;
2206  }
2207  else
2208  {
2209  return 0;
2210  }
2211  }
2212  else
2213  {
2214  dol_print_error($this->db);
2215  return -1;
2216  }
2217  }
2218 
2219 
2220  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2227  function load_stats_propale($socid=0)
2228  {
2229  // phpcs:enable
2230  global $conf;
2231  global $user;
2232 
2233  $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
2234  $sql.= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
2235  $sql.= " FROM ".MAIN_DB_PREFIX."propaldet as pd";
2236  $sql.= ", ".MAIN_DB_PREFIX."propal as p";
2237  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2238  if (!$user->rights->societe->client->voir && !$socid) { $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2239  }
2240  $sql.= " WHERE p.rowid = pd.fk_propal";
2241  $sql.= " AND p.fk_soc = s.rowid";
2242  $sql.= " AND p.entity IN (".getEntity('propal').")";
2243  $sql.= " AND pd.fk_product = ".$this->id;
2244  if (!$user->rights->societe->client->voir && !$socid) { $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2245  }
2246  //$sql.= " AND pr.fk_statut != 0";
2247  if ($socid > 0) { $sql.= " AND p.fk_soc = ".$socid;
2248  }
2249 
2250  $result = $this->db->query($sql);
2251  if ($result ) {
2252  $obj=$this->db->fetch_object($result);
2253  $this->stats_propale['customers']=$obj->nb_customers;
2254  $this->stats_propale['nb']=$obj->nb;
2255  $this->stats_propale['rows']=$obj->nb_rows;
2256  $this->stats_propale['qty']=$obj->qty?$obj->qty:0;
2257  return 1;
2258  }
2259  else
2260  {
2261  $this->error=$this->db->error();
2262  return -1;
2263  }
2264  }
2265 
2266 
2267  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2274  function load_stats_proposal_supplier($socid=0)
2275  {
2276  // phpcs:enable
2277  global $conf;
2278  global $user;
2279 
2280  $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
2281  $sql.= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
2282  $sql.= " FROM ".MAIN_DB_PREFIX."supplier_proposaldet as pd";
2283  $sql.= ", ".MAIN_DB_PREFIX."supplier_proposal as p";
2284  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2285  if (!$user->rights->societe->client->voir && !$socid) { $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2286  }
2287  $sql.= " WHERE p.rowid = pd.fk_supplier_proposal";
2288  $sql.= " AND p.fk_soc = s.rowid";
2289  $sql.= " AND p.entity IN (".getEntity('supplier_proposal').")";
2290  $sql.= " AND pd.fk_product = ".$this->id;
2291  if (!$user->rights->societe->client->voir && !$socid) { $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2292  }
2293  //$sql.= " AND pr.fk_statut != 0";
2294  if ($socid > 0) { $sql.= " AND p.fk_soc = ".$socid;
2295  }
2296 
2297  $result = $this->db->query($sql);
2298  if ($result ) {
2299  $obj=$this->db->fetch_object($result);
2300  $this->stats_proposal_supplier['suppliers']=$obj->nb_suppliers;
2301  $this->stats_proposal_supplier['nb']=$obj->nb;
2302  $this->stats_proposal_supplier['rows']=$obj->nb_rows;
2303  $this->stats_proposal_supplier['qty']=$obj->qty?$obj->qty:0;
2304  return 1;
2305  }
2306  else
2307  {
2308  $this->error=$this->db->error();
2309  return -1;
2310  }
2311  }
2312 
2313 
2314  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2323  function load_stats_commande($socid=0,$filtrestatut='', $forVirtualStock = 0)
2324  {
2325  // phpcs:enable
2326  global $conf,$user;
2327 
2328  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2329  $sql.= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2330  $sql.= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
2331  $sql.= ", ".MAIN_DB_PREFIX."commande as c";
2332  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2333  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) { $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2334  }
2335  $sql.= " WHERE c.rowid = cd.fk_commande";
2336  $sql.= " AND c.fk_soc = s.rowid";
2337  $sql.= " AND c.entity IN (".getEntity('commande').")";
2338  $sql.= " AND cd.fk_product = ".$this->id;
2339  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) { $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2340  }
2341  if ($socid > 0) { $sql.= " AND c.fk_soc = ".$socid;
2342  }
2343  if ($filtrestatut <> '') { $sql.= " AND c.fk_statut in (".$filtrestatut.")";
2344  }
2345 
2346  $result = $this->db->query($sql);
2347  if ($result ) {
2348  $obj=$this->db->fetch_object($result);
2349  $this->stats_commande['customers']=$obj->nb_customers;
2350  $this->stats_commande['nb']=$obj->nb;
2351  $this->stats_commande['rows']=$obj->nb_rows;
2352  $this->stats_commande['qty']=$obj->qty?$obj->qty:0;
2353 
2354  // if it's a virtual product, maybe it is in order by extension
2355  if (! empty($conf->global->ORDER_ADD_ORDERS_WITH_PARENT_PROD_IF_INCDEC)) {
2356  $TFather = $this->getFather();
2357  if (is_array($TFather) && !empty($TFather)) {
2358  foreach($TFather as &$fatherData) {
2359  $pFather = new Product($this->db);
2360  $pFather->id = $fatherData['id'];
2361  $qtyCoef = $fatherData['qty'];
2362 
2363  if ($fatherData['incdec']) {
2364  $pFather->load_stats_commande($socid, $filtrestatut);
2365 
2366  $this->stats_commande['customers']+=$pFather->stats_commande['customers'];
2367  $this->stats_commande['nb']+=$pFather->stats_commande['nb'];
2368  $this->stats_commande['rows']+=$pFather->stats_commande['rows'];
2369  $this->stats_commande['qty']+=$pFather->stats_commande['qty'] * $qtyCoef;
2370  }
2371  }
2372  }
2373  }
2374 
2375  // If stock decrease is on invoice validation, the theorical stock continue to
2376  // count the orders to ship in theorical stock when some are already removed b invoice validation.
2377  // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation.
2378  if (! empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
2379  if (! empty($conf->global->DECREASE_ONLY_UNINVOICEDPRODUCTS)) {
2380  $adeduire = 0;
2381  $sql = "SELECT sum(fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet fd ";
2382  $sql .= " JOIN ".MAIN_DB_PREFIX."facture f ON fd.fk_facture = f.rowid ";
2383  $sql .= " JOIN ".MAIN_DB_PREFIX."element_element el ON el.fk_target = f.rowid and el.targettype = 'facture' and sourcetype = 'commande'";
2384  $sql .= " JOIN ".MAIN_DB_PREFIX."commande c ON el.fk_source = c.rowid ";
2385  $sql .= " WHERE c.fk_statut IN (".$filtrestatut.") AND c.facture = 0 AND fd.fk_product = ".$this->id;
2386  dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
2387 
2388  $resql = $this->db->query($sql);
2389  if ($resql ) {
2390  if ($this->db->num_rows($resql) > 0) {
2391  $obj = $this->db->fetch_object($resql);
2392  $adeduire += $obj->count;
2393  }
2394  }
2395 
2396  $this->stats_commande['qty'] -= $adeduire;
2397  }
2398  }
2399 
2400  return 1;
2401  }
2402  else
2403  {
2404  $this->error=$this->db->error();
2405  return -1;
2406  }
2407  }
2408 
2409  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2418  function load_stats_commande_fournisseur($socid=0,$filtrestatut='', $forVirtualStock = 0)
2419  {
2420  // phpcs:enable
2421  global $conf,$user;
2422 
2423  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
2424  $sql.= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2425  $sql.= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd";
2426  $sql.= ", ".MAIN_DB_PREFIX."commande_fournisseur as c";
2427  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2428  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) { $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2429  }
2430  $sql.= " WHERE c.rowid = cd.fk_commande";
2431  $sql.= " AND c.fk_soc = s.rowid";
2432  $sql.= " AND c.entity IN (".getEntity('supplier_order').")";
2433  $sql.= " AND cd.fk_product = ".$this->id;
2434  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) { $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2435  }
2436  if ($socid > 0) { $sql.= " AND c.fk_soc = ".$socid;
2437  }
2438  if ($filtrestatut != '') { $sql.= " AND c.fk_statut in (".$filtrestatut.")"; // Peut valoir 0
2439  }
2440 
2441  $result = $this->db->query($sql);
2442  if ($result ) {
2443  $obj=$this->db->fetch_object($result);
2444  $this->stats_commande_fournisseur['suppliers']=$obj->nb_suppliers;
2445  $this->stats_commande_fournisseur['nb']=$obj->nb;
2446  $this->stats_commande_fournisseur['rows']=$obj->nb_rows;
2447  $this->stats_commande_fournisseur['qty']=$obj->qty?$obj->qty:0;
2448  return 1;
2449  }
2450  else
2451  {
2452  $this->error=$this->db->error().' sql='.$sql;
2453  return -1;
2454  }
2455  }
2456 
2457  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2466  function load_stats_sending($socid=0,$filtrestatut='', $forVirtualStock = 0)
2467  {
2468  // phpcs:enable
2469  global $conf,$user;
2470 
2471  $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
2472  $sql.= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
2473  $sql.= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed";
2474  $sql.= ", ".MAIN_DB_PREFIX."commandedet as cd";
2475  $sql.= ", ".MAIN_DB_PREFIX."commande as c";
2476  $sql.= ", ".MAIN_DB_PREFIX."expedition as e";
2477  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2478  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) { $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2479  }
2480  $sql.= " WHERE e.rowid = ed.fk_expedition";
2481  $sql.= " AND c.rowid = cd.fk_commande";
2482  $sql.= " AND e.fk_soc = s.rowid";
2483  $sql.= " AND e.entity IN (".getEntity('expedition').")";
2484  $sql.= " AND ed.fk_origin_line = cd.rowid";
2485  $sql.= " AND cd.fk_product = ".$this->id;
2486  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) { $sql.= " AND e.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2487  }
2488  if ($socid > 0) { $sql.= " AND e.fk_soc = ".$socid;
2489  }
2490  if ($filtrestatut <> '') { $sql.= " AND c.fk_statut in (".$filtrestatut.")";
2491  }
2492 
2493  $result = $this->db->query($sql);
2494  if ($result ) {
2495  $obj=$this->db->fetch_object($result);
2496  $this->stats_expedition['customers']=$obj->nb_customers;
2497  $this->stats_expedition['nb']=$obj->nb;
2498  $this->stats_expedition['rows']=$obj->nb_rows;
2499  $this->stats_expedition['qty']=$obj->qty?$obj->qty:0;
2500  return 1;
2501  }
2502  else
2503  {
2504  $this->error=$this->db->error();
2505  return -1;
2506  }
2507  }
2508 
2509  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2518  function load_stats_reception($socid=0,$filtrestatut='', $forVirtualStock = 0)
2519  {
2520  // phpcs:enable
2521  global $conf,$user;
2522 
2523  $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_customers, COUNT(DISTINCT cf.rowid) as nb,";
2524  $sql.= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
2525  $sql.= " FROM ".MAIN_DB_PREFIX."commande_fournisseur_dispatch as fd";
2526  $sql.= ", ".MAIN_DB_PREFIX."commande_fournisseur as cf";
2527  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2528  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) { $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2529  }
2530  $sql.= " WHERE cf.rowid = fd.fk_commande";
2531  $sql.= " AND cf.fk_soc = s.rowid";
2532  $sql.= " AND cf.entity IN (".getEntity('supplier_order').")";
2533  $sql.= " AND fd.fk_product = ".$this->id;
2534  if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) { $sql.= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2535  }
2536  if ($socid > 0) { $sql.= " AND cf.fk_soc = ".$socid;
2537  }
2538  if ($filtrestatut <> '') { $sql.= " AND cf.fk_statut in (".$filtrestatut.")";
2539  }
2540 
2541  $result = $this->db->query($sql);
2542  if ($result ) {
2543  $obj=$this->db->fetch_object($result);
2544  $this->stats_reception['suppliers']=$obj->nb_customers;
2545  $this->stats_reception['nb']=$obj->nb;
2546  $this->stats_reception['rows']=$obj->nb_rows;
2547  $this->stats_reception['qty']=$obj->qty?$obj->qty:0;
2548  return 1;
2549  }
2550  else
2551  {
2552  $this->error=$this->db->error();
2553  return -1;
2554  }
2555  }
2556 
2557  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2564  function load_stats_contrat($socid=0)
2565  {
2566  // phpcs:enable
2567  global $conf;
2568  global $user;
2569 
2570  $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2571  $sql.= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2572  $sql.= " FROM ".MAIN_DB_PREFIX."contratdet as cd";
2573  $sql.= ", ".MAIN_DB_PREFIX."contrat as c";
2574  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2575  if (!$user->rights->societe->client->voir && !$socid) { $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2576  }
2577  $sql.= " WHERE c.rowid = cd.fk_contrat";
2578  $sql.= " AND c.fk_soc = s.rowid";
2579  $sql.= " AND c.entity IN (".getEntity('contract').")";
2580  $sql.= " AND cd.fk_product = ".$this->id;
2581  if (!$user->rights->societe->client->voir && !$socid) { $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2582  }
2583  //$sql.= " AND c.statut != 0";
2584  if ($socid > 0) { $sql.= " AND c.fk_soc = ".$socid;
2585  }
2586 
2587  $result = $this->db->query($sql);
2588  if ($result ) {
2589  $obj=$this->db->fetch_object($result);
2590  $this->stats_contrat['customers']=$obj->nb_customers;
2591  $this->stats_contrat['nb']=$obj->nb;
2592  $this->stats_contrat['rows']=$obj->nb_rows;
2593  $this->stats_contrat['qty']=$obj->qty?$obj->qty:0;
2594  return 1;
2595  }
2596  else
2597  {
2598  $this->error=$this->db->error().' sql='.$sql;
2599  return -1;
2600  }
2601  }
2602 
2603  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2610  function load_stats_facture($socid=0)
2611  {
2612  // phpcs:enable
2613  global $conf;
2614  global $user;
2615 
2616  $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
2617  $sql.= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
2618  $sql.= " FROM ".MAIN_DB_PREFIX."facturedet as fd";
2619  $sql.= ", ".MAIN_DB_PREFIX."facture as f";
2620  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2621  if (!$user->rights->societe->client->voir && !$socid) { $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2622  }
2623  $sql.= " WHERE f.rowid = fd.fk_facture";
2624  $sql.= " AND f.fk_soc = s.rowid";
2625  $sql.= " AND f.entity IN (".getEntity('facture').")";
2626  $sql.= " AND fd.fk_product = ".$this->id;
2627  if (!$user->rights->societe->client->voir && !$socid) { $sql.= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2628  }
2629  //$sql.= " AND f.fk_statut != 0";
2630  if ($socid > 0) { $sql .= " AND f.fk_soc = ".$socid;
2631  }
2632 
2633  $result = $this->db->query($sql);
2634  if ($result ) {
2635  $obj=$this->db->fetch_object($result);
2636  $this->stats_facture['customers']=$obj->nb_customers;
2637  $this->stats_facture['nb']=$obj->nb;
2638  $this->stats_facture['rows']=$obj->nb_rows;
2639  $this->stats_facture['qty']=$obj->qty?$obj->qty:0;
2640  return 1;
2641  }
2642  else
2643  {
2644  $this->error=$this->db->error();
2645  return -1;
2646  }
2647  }
2648 
2649  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2657  {
2658  // phpcs:enable
2659  global $conf;
2660  global $user;
2661 
2662  $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
2663  $sql.= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
2664  $sql.= " FROM ".MAIN_DB_PREFIX."facture_fourn_det as fd";
2665  $sql.= ", ".MAIN_DB_PREFIX."facture_fourn as f";
2666  $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2667  if (!$user->rights->societe->client->voir && !$socid) { $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2668  }
2669  $sql.= " WHERE f.rowid = fd.fk_facture_fourn";
2670  $sql.= " AND f.fk_soc = s.rowid";
2671  $sql.= " AND f.entity IN (".getEntity('facture_fourn').")";
2672  $sql.= " AND fd.fk_product = ".$this->id;
2673  if (!$user->rights->societe->client->voir && !$socid) { $sql.= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2674  }
2675  //$sql.= " AND f.fk_statut != 0";
2676  if ($socid > 0) { $sql .= " AND f.fk_soc = ".$socid;
2677  }
2678 
2679  $result = $this->db->query($sql);
2680  if ($result ) {
2681  $obj=$this->db->fetch_object($result);
2682  $this->stats_facture_fournisseur['suppliers']=$obj->nb_suppliers;
2683  $this->stats_facture_fournisseur['nb']=$obj->nb;
2684  $this->stats_facture_fournisseur['rows']=$obj->nb_rows;
2685  $this->stats_facture_fournisseur['qty']=$obj->qty?$obj->qty:0;
2686  return 1;
2687  }
2688  else
2689  {
2690  $this->error=$this->db->error();
2691  return -1;
2692  }
2693  }
2694 
2695  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2704  function _get_stats($sql, $mode, $year=0)
2705  {
2706  // phpcs:enable
2707  $resql = $this->db->query($sql);
2708  if ($resql) {
2709  $num = $this->db->num_rows($resql);
2710  $i = 0;
2711  while ($i < $num)
2712  {
2713  $arr = $this->db->fetch_array($resql);
2714  if ($mode == 'byunit') { $tab[$arr[1]] = $arr[0]; // 1st field
2715  }
2716  if ($mode == 'bynumber') { $tab[$arr[1]] = $arr[2]; // 3rd field
2717  }
2718  $i++;
2719  }
2720  }
2721  else
2722  {
2723  $this->error=$this->db->error().' sql='.$sql;
2724  return -1;
2725  }
2726 
2727  if (empty($year)) {
2728  $year = strftime('%Y', time());
2729  $month = strftime('%m', time());
2730  }
2731  else
2732  {
2733  $month=12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
2734  }
2735  $result = array();
2736 
2737  for ($j = 0 ; $j < 12 ; $j++)
2738  {
2739  $idx=ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, $year), "%b"), 3, 'right', 'UTF-8', 1));
2740  $monthnum=sprintf("%02s", $month);
2741 
2742  $result[$j] = array($idx,isset($tab[$year.$month])?$tab[$year.$month]:0);
2743  // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
2744 
2745  $month = "0".($month - 1);
2746  if (dol_strlen($month) == 3) {
2747  $month = substr($month, 1);
2748  }
2749  if ($month == 0) {
2750  $month = 12;
2751  $year = $year - 1;
2752  }
2753  }
2754 
2755  return array_reverse($result);
2756  }
2757 
2758 
2759  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2770  function get_nb_vente($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
2771  {
2772  // phpcs:enable
2773  global $conf;
2774  global $user;
2775 
2776  $sql = "SELECT sum(d.qty), date_format(f.datef, '%Y%m')";
2777  if ($mode == 'bynumber') { $sql.= ", count(DISTINCT f.rowid)";
2778  }
2779  $sql.= " FROM ".MAIN_DB_PREFIX."facturedet as d, ".MAIN_DB_PREFIX."facture as f, ".MAIN_DB_PREFIX."societe as s";
2780  if ($filteronproducttype >= 0) { $sql.=", ".MAIN_DB_PREFIX."product as p";
2781  }
2782  if (!$user->rights->societe->client->voir && !$socid) { $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2783  }
2784  $sql.= " WHERE f.rowid = d.fk_facture";
2785  if ($this->id > 0) { $sql.= " AND d.fk_product =".$this->id;
2786  } else { $sql.=" AND d.fk_product > 0";
2787  }
2788  if ($filteronproducttype >= 0) { $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype;
2789  }
2790  $sql.= " AND f.fk_soc = s.rowid";
2791  $sql.= " AND f.entity IN (".getEntity('facture').")";
2792  if (!$user->rights->societe->client->voir && !$socid) { $sql.= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2793  }
2794  if ($socid > 0) { $sql.= " AND f.fk_soc = $socid";
2795  }
2796  $sql.=$morefilter;
2797  $sql.= " GROUP BY date_format(f.datef,'%Y%m')";
2798  $sql.= " ORDER BY date_format(f.datef,'%Y%m') DESC";
2799 
2800  return $this->_get_stats($sql, $mode, $year);
2801  }
2802 
2803 
2804  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2815  function get_nb_achat($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
2816  {
2817  // phpcs:enable
2818  global $conf;
2819  global $user;
2820 
2821  $sql = "SELECT sum(d.qty), date_format(f.datef, '%Y%m')";
2822  if ($mode == 'bynumber') { $sql.= ", count(DISTINCT f.rowid)";
2823  }
2824  $sql.= " FROM ".MAIN_DB_PREFIX."facture_fourn_det as d, ".MAIN_DB_PREFIX."facture_fourn as f, ".MAIN_DB_PREFIX."societe as s";
2825  if ($filteronproducttype >= 0) { $sql.=", ".MAIN_DB_PREFIX."product as p";
2826  }
2827  if (!$user->rights->societe->client->voir && !$socid) { $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2828  }
2829  $sql.= " WHERE f.rowid = d.fk_facture_fourn";
2830  if ($this->id > 0) { $sql.= " AND d.fk_product =".$this->id;
2831  } else { $sql.=" AND d.fk_product > 0";
2832  }
2833  if ($filteronproducttype >= 0) { $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype;
2834  }
2835  $sql.= " AND f.fk_soc = s.rowid";
2836  $sql.= " AND f.entity IN (".getEntity('facture_fourn').")";
2837  if (!$user->rights->societe->client->voir && !$socid) { $sql.= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2838  }
2839  if ($socid > 0) { $sql.= " AND f.fk_soc = $socid";
2840  }
2841  $sql.=$morefilter;
2842  $sql.= " GROUP BY date_format(f.datef,'%Y%m')";
2843  $sql.= " ORDER BY date_format(f.datef,'%Y%m') DESC";
2844 
2845  return $this->_get_stats($sql, $mode, $year);
2846  }
2847 
2848  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2859  function get_nb_propal($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
2860  {
2861  // phpcs:enable
2862  global $conf;
2863  global $user;
2864 
2865  $sql = "SELECT sum(d.qty), date_format(p.datep, '%Y%m')";
2866  if ($mode == 'bynumber') { $sql.= ", count(DISTINCT p.rowid)";
2867  }
2868  $sql.= " FROM ".MAIN_DB_PREFIX."propaldet as d, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."societe as s";
2869  if ($filteronproducttype >= 0) { $sql.=", ".MAIN_DB_PREFIX."product as prod";
2870  }
2871  if (!$user->rights->societe->client->voir && !$socid) { $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2872  }
2873  $sql.= " WHERE p.rowid = d.fk_propal";
2874  if ($this->id > 0) { $sql.= " AND d.fk_product =".$this->id;
2875  } else { $sql.=" AND d.fk_product > 0";
2876  }
2877  if ($filteronproducttype >= 0) { $sql.= " AND prod.rowid = d.fk_product AND prod.fk_product_type =".$filteronproducttype;
2878  }
2879  $sql.= " AND p.fk_soc = s.rowid";
2880  $sql.= " AND p.entity IN (".getEntity('propal').")";
2881  if (!$user->rights->societe->client->voir && !$socid) { $sql.= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2882  }
2883  if ($socid > 0) { $sql.= " AND p.fk_soc = ".$socid;
2884  }
2885  $sql.=$morefilter;
2886  $sql.= " GROUP BY date_format(p.datep,'%Y%m')";
2887  $sql.= " ORDER BY date_format(p.datep,'%Y%m') DESC";
2888 
2889  return $this->_get_stats($sql, $mode, $year);
2890  }
2891 
2892  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2903  function get_nb_propalsupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
2904  {
2905  // phpcs:enable
2906  global $conf;
2907  global $user;
2908 
2909  $sql = "SELECT sum(d.qty), date_format(p.date_valid, '%Y%m')";
2910  if ($mode == 'bynumber') { $sql.= ", count(DISTINCT p.rowid)";
2911  }
2912  $sql.= " FROM ".MAIN_DB_PREFIX."supplier_proposaldet as d, ".MAIN_DB_PREFIX."supplier_proposal as p, ".MAIN_DB_PREFIX."societe as s";
2913  if ($filteronproducttype >= 0) { $sql.=", ".MAIN_DB_PREFIX."product as prod";
2914  }
2915  if (!$user->rights->societe->client->voir && !$socid) { $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2916  }
2917  $sql.= " WHERE p.rowid = d.fk_supplier_proposal";
2918  if ($this->id > 0) { $sql.= " AND d.fk_product =".$this->id;
2919  } else { $sql.=" AND d.fk_product > 0";
2920  }
2921  if ($filteronproducttype >= 0) { $sql.= " AND prod.rowid = d.fk_product AND prod.fk_product_type =".$filteronproducttype;
2922  }
2923  $sql.= " AND p.fk_soc = s.rowid";
2924  $sql.= " AND p.entity IN (".getEntity('supplier_proposal').")";
2925  if (!$user->rights->societe->client->voir && !$socid) { $sql.= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2926  }
2927  if ($socid > 0) { $sql.= " AND p.fk_soc = ".$socid;
2928  }
2929  $sql.=$morefilter;
2930  $sql.= " GROUP BY date_format(p.date_valid,'%Y%m')";
2931  $sql.= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
2932 
2933  return $this->_get_stats($sql, $mode, $year);
2934  }
2935 
2936  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2947  function get_nb_order($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
2948  {
2949  // phpcs:enable
2950  global $conf, $user;
2951 
2952  $sql = "SELECT sum(d.qty), date_format(c.date_commande, '%Y%m')";
2953  if ($mode == 'bynumber') { $sql.= ", count(DISTINCT c.rowid)";
2954  }
2955  $sql.= " FROM ".MAIN_DB_PREFIX."commandedet as d, ".MAIN_DB_PREFIX."commande as c, ".MAIN_DB_PREFIX."societe as s";
2956  if ($filteronproducttype >= 0) { $sql.=", ".MAIN_DB_PREFIX."product as p";
2957  }
2958  if (!$user->rights->societe->client->voir && !$socid) { $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2959  }
2960  $sql.= " WHERE c.rowid = d.fk_commande";
2961  if ($this->id > 0) { $sql.= " AND d.fk_product =".$this->id;
2962  } else { $sql.=" AND d.fk_product > 0";
2963  }
2964  if ($filteronproducttype >= 0) { $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype;
2965  }
2966  $sql.= " AND c.fk_soc = s.rowid";
2967  $sql.= " AND c.entity IN (".getEntity('commande').")";
2968  if (!$user->rights->societe->client->voir && !$socid) { $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2969  }
2970  if ($socid > 0) { $sql.= " AND c.fk_soc = ".$socid;
2971  }
2972  $sql.=$morefilter;
2973  $sql.= " GROUP BY date_format(c.date_commande,'%Y%m')";
2974  $sql.= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
2975 
2976  return $this->_get_stats($sql, $mode, $year);
2977  }
2978 
2979  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2990  function get_nb_ordersupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
2991  {
2992  // phpcs:enable
2993  global $conf, $user;
2994 
2995  $sql = "SELECT sum(d.qty), date_format(c.date_commande, '%Y%m')";
2996  if ($mode == 'bynumber') { $sql.= ", count(DISTINCT c.rowid)";
2997  }
2998  $sql.= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as d, ".MAIN_DB_PREFIX."commande_fournisseur as c, ".MAIN_DB_PREFIX."societe as s";
2999  if ($filteronproducttype >= 0) { $sql.=", ".MAIN_DB_PREFIX."product as p";
3000  }
3001  if (!$user->rights->societe->client->voir && !$socid) { $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3002  }
3003  $sql.= " WHERE c.rowid = d.fk_commande";
3004  if ($this->id > 0) { $sql.= " AND d.fk_product =".$this->id;
3005  } else { $sql.=" AND d.fk_product > 0";
3006  }
3007  if ($filteronproducttype >= 0) { $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype;
3008  }
3009  $sql.= " AND c.fk_soc = s.rowid";
3010  $sql.= " AND c.entity IN (".getEntity('supplier_order').")";
3011  if (!$user->rights->societe->client->voir && !$socid) { $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
3012  }
3013  if ($socid > 0) { $sql.= " AND c.fk_soc = ".$socid;
3014  }
3015  $sql.=$morefilter;
3016  $sql.= " GROUP BY date_format(c.date_commande,'%Y%m')";
3017  $sql.= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
3018 
3019  return $this->_get_stats($sql, $mode, $year);
3020  }
3021 
3022  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3032  function add_sousproduit($id_pere, $id_fils, $qty, $incdec=1)
3033  {
3034  // phpcs:enable
3035  // Clean parameters
3036  if (! is_numeric($id_pere)) { $id_pere=0;
3037  }
3038  if (! is_numeric($id_fils)) { $id_fils=0;
3039  }
3040  if (! is_numeric($incdec)) { $incdec=0;
3041  }
3042 
3043  $result=$this->del_sousproduit($id_pere, $id_fils);
3044  if ($result < 0) { return $result;
3045  }
3046 
3047  // Check not already father of id_pere (to avoid father -> child -> father links)
3048  $sql = 'SELECT fk_product_pere from '.MAIN_DB_PREFIX.'product_association';
3049  $sql .= ' WHERE fk_product_pere = '.$id_fils.' AND fk_product_fils = '.$id_pere;
3050  if (! $this->db->query($sql)) {
3051  dol_print_error($this->db);
3052  return -1;
3053  }
3054  else
3055  {
3056  $result = $this->db->query($sql);
3057  if ($result) {
3058  $num = $this->db->num_rows($result);
3059  if($num > 0) {
3060  $this->error="isFatherOfThis";
3061  return -1;
3062  }
3063  else
3064  {
3065  $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'product_association(fk_product_pere,fk_product_fils,qty,incdec)';
3066  $sql .= ' VALUES ('.$id_pere.', '.$id_fils.', '.$qty.', '.$incdec.')';
3067  if (! $this->db->query($sql)) {
3068  dol_print_error($this->db);
3069  return -1;
3070  }
3071  else
3072  {
3073  return 1;
3074  }
3075  }
3076  }
3077  }
3078  }
3079 
3080  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3090  function update_sousproduit($id_pere, $id_fils, $qty, $incdec=1)
3091  {
3092  // phpcs:enable
3093  // Clean parameters
3094  if (! is_numeric($id_pere)) { $id_pere=0;
3095  }
3096  if (! is_numeric($id_fils)) { $id_fils=0;
3097  }
3098  if (! is_numeric($incdec)) { $incdec=1;
3099  }
3100  if (! is_numeric($qty)) { $qty=1;
3101  }
3102 
3103  $sql = 'UPDATE '.MAIN_DB_PREFIX.'product_association SET ';
3104  $sql.= 'qty='.$qty;
3105  $sql.= ',incdec='.$incdec;
3106  $sql .= ' WHERE fk_product_pere='.$id_pere.' AND fk_product_fils='.$id_fils;
3107 
3108  if (!$this->db->query($sql)) {
3109  dol_print_error($this->db);
3110  return -1;
3111  }
3112  else
3113  {
3114  return 1;
3115  }
3116  }
3117 
3118  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3126  function del_sousproduit($fk_parent, $fk_child)
3127  {
3128  // phpcs:enable
3129  if (! is_numeric($fk_parent)) { $fk_parent=0;
3130  }
3131  if (! is_numeric($fk_child)) { $fk_child=0;
3132  }
3133 
3134  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_association";
3135  $sql.= " WHERE fk_product_pere = ".$fk_parent;
3136  $sql.= " AND fk_product_fils = ".$fk_child;
3137 
3138  dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
3139  if (! $this->db->query($sql)) {
3140  dol_print_error($this->db);
3141  return -1;
3142  }
3143 
3144  return 1;
3145  }
3146 
3147  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3155  function is_sousproduit($fk_parent, $fk_child)
3156  {
3157  // phpcs:enable
3158  $sql = "SELECT fk_product_pere, qty, incdec";
3159  $sql.= " FROM ".MAIN_DB_PREFIX."product_association";
3160  $sql.= " WHERE fk_product_pere = '".$fk_parent."'";
3161  $sql.= " AND fk_product_fils = '".$fk_child."'";
3162 
3163  $result = $this->db->query($sql);
3164  if ($result) {
3165  $num = $this->db->num_rows($result);
3166 
3167  if($num > 0) {
3168  $obj = $this->db->fetch_object($result);
3169  $this->is_sousproduit_qty = $obj->qty;
3170  $this->is_sousproduit_incdec = $obj->incdec;
3171 
3172  return true;
3173  }
3174  else
3175  {
3176  return false;
3177  }
3178  }
3179  else
3180  {
3181  dol_print_error($this->db);
3182  return -1;
3183  }
3184  }
3185 
3186 
3187  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3198  function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
3199  {
3200  // phpcs:enable
3201  global $conf;
3202 
3203  $now=dol_now();
3204 
3205  dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
3206 
3207  if ($ref_fourn) {
3208  $sql = "SELECT rowid, fk_product";
3209  $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
3210  $sql.= " WHERE fk_soc = ".$id_fourn;
3211  $sql.= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
3212  $sql.= " AND fk_product != ".$this->id;
3213  $sql.= " AND entity IN (".getEntity('productsupplierprice').")";
3214 
3215  $resql=$this->db->query($sql);
3216  if ($resql) {
3217  $obj = $this->db->fetch_object($resql);
3218  if ($obj) {
3219  // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
3220  $this->product_id_already_linked = $obj->fk_product;
3221  return -3;
3222  }
3223  $this->db->free($resql);
3224  }
3225  }
3226 
3227  $sql = "SELECT rowid";
3228  $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
3229  $sql.= " WHERE fk_soc = ".$id_fourn;
3230  if ($ref_fourn) { $sql.= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
3231  } else { $sql.= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
3232  }
3233  $sql.= " AND quantity = '".$quantity."'";
3234  $sql.= " AND fk_product = ".$this->id;
3235  $sql.= " AND entity IN (".getEntity('productsupplierprice').")";
3236 
3237  $resql=$this->db->query($sql);
3238  if ($resql) {
3239  $obj = $this->db->fetch_object($resql);
3240 
3241  // The reference supplier does not exist, we create it for this product.
3242  if (! $obj) {
3243  $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_fournisseur_price(";
3244  $sql.= "datec";
3245  $sql.= ", entity";
3246  $sql.= ", fk_product";
3247  $sql.= ", fk_soc";
3248  $sql.= ", ref_fourn";
3249  $sql.= ", quantity";
3250  $sql.= ", fk_user";
3251  $sql.= ", tva_tx";
3252  $sql.= ") VALUES (";
3253  $sql.= "'".$this->db->idate($now)."'";
3254  $sql.= ", ".$conf->entity;
3255  $sql.= ", ".$this->id;
3256  $sql.= ", ".$id_fourn;
3257  $sql.= ", '".$this->db->escape($ref_fourn)."'";
3258  $sql.= ", ".$quantity;
3259  $sql.= ", ".$user->id;
3260  $sql.= ", 0";
3261  $sql.= ")";
3262 
3263  if ($this->db->query($sql)) {
3264  $this->product_fourn_price_id = $this->db->last_insert_id(MAIN_DB_PREFIX."product_fournisseur_price");
3265  return 1;
3266  }
3267  else
3268  {
3269  $this->error=$this->db->lasterror();
3270  return -1;
3271  }
3272  }
3273  // If the supplier price already exists for this product and quantity
3274  else
3275  {
3276  $this->product_fourn_price_id = $obj->rowid;
3277  return 0;
3278  }
3279  }
3280  else
3281  {
3282  $this->error=$this->db->lasterror();
3283  return -2;
3284  }
3285  }
3286 
3287 
3288  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3294  function list_suppliers()
3295  {
3296  // phpcs:enable
3297  global $conf;
3298 
3299  $list = array();
3300 
3301  $sql = "SELECT DISTINCT p.fk_soc";
3302  $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as p";
3303  $sql.= " WHERE p.fk_product = ".$this->id;
3304  $sql.= " AND p.entity = ".$conf->entity;
3305 
3306  $result = $this->db->query($sql);
3307  if ($result) {
3308  $num = $this->db->num_rows($result);
3309  $i=0;
3310  while ($i < $num)
3311  {
3312  $obj = $this->db->fetch_object($result);
3313  $list[$i] = $obj->fk_soc;
3314  $i++;
3315  }
3316  }
3317 
3318  return $list;
3319  }
3320 
3321  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3329  function clone_price($fromId, $toId)
3330  {
3331  // phpcs:enable
3332  $this->db->begin();
3333 
3334  // les prix
3335  $sql = "INSERT ".MAIN_DB_PREFIX."product_price (";
3336  $sql.= " fk_product, date_price, price, tva_tx, localtax1_tx, localtax2_tx, fk_user_author, tosell)";
3337  $sql.= " SELECT ".$toId . ", date_price, price, tva_tx, localtax1_tx, localtax2_tx, fk_user_author, tosell";
3338  $sql.= " FROM ".MAIN_DB_PREFIX."product_price ";
3339  $sql.= " WHERE fk_product = ". $fromId;
3340 
3341  dol_syslog(get_class($this).'::clone_price', LOG_DEBUG);
3342  if (! $this->db->query($sql)) {
3343  $this->db->rollback();
3344  return -1;
3345  }
3346  $this->db->commit();
3347  return 1;
3348  }
3349 
3350  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3358  function clone_associations($fromId, $toId)
3359  {
3360  // phpcs:enable
3361  $this->db->begin();
3362 
3363  $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'product_association (fk_product_pere, fk_product_fils, qty)';
3364  $sql.= " SELECT ".$toId.", fk_product_fils, qty FROM ".MAIN_DB_PREFIX."product_association";
3365  $sql.= " WHERE fk_product_pere = ".$fromId;
3366 
3367  dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
3368  if (! $this->db->query($sql)) {
3369  $this->db->rollback();
3370  return -1;
3371  }
3372 
3373  $this->db->commit();
3374  return 1;
3375  }
3376 
3377  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3385  function clone_fournisseurs($fromId, $toId)
3386  {
3387  // phpcs:enable
3388  $this->db->begin();
3389 
3390  $now=dol_now();
3391 
3392  // les fournisseurs
3393  /*$sql = "INSERT ".MAIN_DB_PREFIX."product_fournisseur ("
3394  . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
3395  . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
3396  . " FROM ".MAIN_DB_PREFIX."product_fournisseur"
3397  . " WHERE fk_product = ".$fromId;
3398 
3399  if ( ! $this->db->query($sql ) )
3400  {
3401  $this->db->rollback();
3402  return -1;
3403  }*/
3404 
3405  // les prix de fournisseurs.
3406  $sql = "INSERT ".MAIN_DB_PREFIX."product_fournisseur_price (";
3407  $sql.= " datec, fk_product, fk_soc, price, quantity, fk_user)";
3408  $sql.= " SELECT '".$this->db->idate($now)."', ".$toId. ", fk_soc, price, quantity, fk_user";
3409  $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
3410  $sql.= " WHERE fk_product = ".$fromId;
3411 
3412  dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
3413  $resql=$this->db->query($sql);
3414  if (! $resql) {
3415  $this->db->rollback();
3416  return -1;
3417  }
3418  else
3419  {
3420  $this->db->commit();
3421  return 1;
3422  }
3423  }
3424 
3425  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3437  function fetch_prod_arbo($prod, $compl_path="", $multiply=1, $level=1, $id_parent=0)
3438  {
3439  // phpcs:enable
3440  global $conf,$langs;
3441 
3442  $product = new Product($this->db);
3443  //var_dump($prod);
3444  foreach($prod as $id_product => $desc_pere) // $id_product is 0 (first call starting with root top) or an id of a sub_product
3445  {
3446  if (is_array($desc_pere)) // If desc_pere is an array, this means it's a child
3447  {
3448  $id=(! empty($desc_pere[0]) ? $desc_pere[0] :'');
3449  $nb=(! empty($desc_pere[1]) ? $desc_pere[1] :'');
3450  $type=(! empty($desc_pere[2]) ? $desc_pere[2] :'');
3451  $label=(! empty($desc_pere[3]) ? $desc_pere[3] :'');
3452  $incdec=!empty($desc_pere[4]) ? $desc_pere[4] : 0;
3453 
3454  if ($multiply < 1) { $multiply=1;
3455  }
3456 
3457  //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
3458  $this->fetch($id); // Load product
3459  $this->load_stock('nobatch,novirtual'); // Load stock to get true this->stock_reel
3460  $this->res[]= array(
3461  'id'=>$id, // Id product
3462  'id_parent'=>$id_parent,
3463  'ref'=>$this->ref, // Ref product
3464  'nb'=>$nb, // Nb of units that compose parent product
3465  'nb_total'=>$nb*$multiply, // Nb of units for all nb of product
3466  'stock'=>$this->stock_reel, // Stock
3467  'stock_alert'=>$this->seuil_stock_alerte, // Stock alert
3468  'label'=>$label,
3469  'fullpath'=>$compl_path.$label, // Label
3470  'type'=>$type, // Nb of units that compose parent product
3471  'desiredstock'=>$this->desiredstock,
3472  'level'=>$level,
3473  'incdec'=>$incdec,
3474  'entity'=>$this->entity
3475  );
3476 
3477  // Recursive call if there is childs to child
3478  if (is_array($desc_pere['childs'])) {
3479  //print 'YYY We go down for '.$desc_pere[3]." -> \n";
3480  $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1]*$multiply, $level+1, $id);
3481  }
3482  }
3483  }
3484  }
3485 
3486  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3494  function get_arbo_each_prod($multiply=1)
3495  {
3496  // phpcs:enable
3497  $this->res = array();
3498  if (isset($this->sousprods) && is_array($this->sousprods)) {
3499  foreach($this->sousprods as $prod_name => $desc_product)
3500  {
3501  if (is_array($desc_product)) { $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id);
3502  }
3503  }
3504  }
3505  //var_dump($this->res);
3506  return $this->res;
3507  }
3508 
3514  public function hasFatherOrChild()
3515  {
3516  $nb = 0;
3517 
3518  $sql = "SELECT COUNT(pa.rowid) as nb";
3519  $sql.= " FROM ".MAIN_DB_PREFIX."product_association as pa";
3520  $sql.= " WHERE pa.fk_product_fils = ".$this->id." OR pa.fk_product_pere = ".$this->id;
3521  $resql = $this->db->query($sql);
3522  if ($resql) {
3523  $obj = $this->db->fetch_object($resql);
3524  if ($obj) { $nb = $obj->nb;
3525  }
3526  }
3527  else
3528  {
3529  return -1;
3530  }
3531 
3532  return $nb;
3533  }
3534 
3540  public function hasVariants()
3541  {
3542  $nb = 0;
3543  $sql = "SELECT count(rowid) as nb FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_parent = ".$this->id;
3544  $sql.= " AND entity IN (".getEntity('product').")";
3545 
3546  $resql = $this->db->query($sql);
3547  if ($resql) {
3548  $obj = $this->db->fetch_object($resql);
3549  if ($obj) { $nb = $obj->nb;
3550  }
3551  }
3552 
3553  return $nb;
3554  }
3555 
3556 
3562  public function isVariant()
3563  {
3564  global $conf;
3565  if (!empty($conf->variants->enabled)) {
3566  $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "product_attribute_combination WHERE fk_product_child = " . $this->id . " AND entity IN (" . getEntity('product') . ")";
3567 
3568  $query = $this->db->query($sql);
3569 
3570  if ($query) {
3571  if (!$this->db->num_rows($query)) {
3572  return false;
3573  }
3574  return true;
3575  } else {
3576  dol_print_error($this->db);
3577  return -1;
3578  }
3579  } else {
3580  return false;
3581  }
3582  }
3583 
3589  public function getFather()
3590  {
3591  $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";
3592  $sql.= " FROM ".MAIN_DB_PREFIX."product_association as pa,";
3593  $sql.= " ".MAIN_DB_PREFIX."product as p";
3594  $sql.= " WHERE p.rowid = pa.fk_product_pere";
3595  $sql.= " AND pa.fk_product_fils = ".$this->id;
3596 
3597  $res = $this->db->query($sql);
3598  if ($res) {
3599  $prods = array ();
3600  while ($record = $this->db->fetch_array($res))
3601  {
3602  // $record['id'] = $record['rowid'] = id of father
3603  $prods[$record['id']]['id'] = $record['rowid'];
3604  $prods[$record['id']]['ref'] = $record['ref'];
3605  $prods[$record['id']]['label'] = $record['label'];
3606  $prods[$record['id']]['qty'] = $record['qty'];
3607  $prods[$record['id']]['incdec'] = $record['incdec'];
3608  $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
3609  $prods[$record['id']]['entity'] = $record['entity'];
3610  }
3611  return $prods;
3612  }
3613  else
3614  {
3615  dol_print_error($this->db);
3616  return -1;
3617  }
3618  }
3619 
3620 
3629  public function getChildsArbo($id, $firstlevelonly=0, $level=1)
3630  {
3631  global $alreadyfound;
3632 
3633  $sql = "SELECT p.rowid, p.label as label, pa.qty as qty, pa.fk_product_fils as id, p.fk_product_type, pa.incdec";
3634  $sql.= " FROM ".MAIN_DB_PREFIX."product as p";
3635  $sql.= ", ".MAIN_DB_PREFIX."product_association as pa";
3636  $sql.= " WHERE p.rowid = pa.fk_product_fils";
3637  $sql.= " AND pa.fk_product_pere = ".$id;
3638  $sql.= " AND pa.fk_product_fils != ".$id; // This should not happens, it is to avoid infinite loop if it happens
3639 
3640  dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level, LOG_DEBUG);
3641 
3642  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
3643  }
3644  // Protection against infinite loop
3645  if ($level > 30) { return array();
3646  }
3647 
3648  $res = $this->db->query($sql);
3649  if ($res) {
3650  $prods = array();
3651  while ($rec = $this->db->fetch_array($res))
3652  {
3653  if (! empty($alreadyfound[$rec['rowid']])) {
3654  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);
3655  continue;
3656  }
3657  $alreadyfound[$rec['rowid']]=1;
3658  $prods[$rec['rowid']]= array(
3659  0=>$rec['rowid'],
3660  1=>$rec['qty'],
3661  2=>$rec['fk_product_type'],
3662  3=>$this->db->escape($rec['label']),
3663  4=>$rec['incdec']
3664  );
3665  //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
3666  //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
3667  if (empty($firstlevelonly)) {
3668  $listofchilds=$this->getChildsArbo($rec['rowid'], 0, $level + 1);
3669  foreach($listofchilds as $keyChild => $valueChild)
3670  {
3671  $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
3672  }
3673  }
3674  }
3675 
3676  return $prods;
3677  }
3678  else
3679  {
3680  dol_print_error($this->db);
3681  return -1;
3682  }
3683  }
3684 
3685  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3693  {
3694  // phpcs:enable
3695  $parent=array();
3696 
3697  foreach($this->getChildsArbo($this->id) as $keyChild => $valueChild) // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
3698  {
3699  $parent[$this->label][$keyChild] = $valueChild;
3700  }
3701  foreach($parent as $key => $value) // key=label, value is array of childs
3702  {
3703  $this->sousprods[$key] = $value;
3704  }
3705  }
3706 
3716  public function getNomUrl($withpicto=0, $option='', $maxlength=0, $save_lastsearch_value=-1)
3717  {
3718  global $conf, $langs, $hookmanager;
3719  include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
3720 
3721  $result='';
3722  $newref=$this->ref;
3723  if ($maxlength) { $newref=dol_trunc($newref, $maxlength, 'middle');
3724  }
3725 
3726  if ($this->type == Product::TYPE_PRODUCT) { $label = '<u>' . $langs->trans("ShowProduct") . '</u>';
3727  }
3728  if ($this->type == Product::TYPE_SERVICE) { $label = '<u>' . $langs->trans("ShowService") . '</u>';
3729  }
3730  if (! empty($this->ref)) {
3731  $label .= '<br><b>' . $langs->trans('ProductRef') . ':</b> ' . $this->ref;
3732  }
3733  if (! empty($this->label)) {
3734  $label .= '<br><b>' . $langs->trans('ProductLabel') . ':</b> ' . $this->label;
3735  }
3736 
3737  if ($this->type == Product::TYPE_PRODUCT) {
3738  if ($this->weight) { $label.="<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuring_units_string($this->weight_units, "weight");
3739  }
3740  if ($this->length) { $label.="<br><b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuring_units_string($this->length_units, 'length');
3741  }
3742  if ($this->surface) { $label.="<br><b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuring_units_string($this->surface_units, 'surface');
3743  }
3744  if ($this->volume) { $label.="<br><b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuring_units_string($this->volume_units, 'volume');
3745  }
3746  }
3747 
3748  if ($this->type == Product::TYPE_PRODUCT || ! empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
3749  if (! empty($conf->productbatch->enabled)) {
3750  $langs->load("productbatch");
3751  $label.="<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
3752  }
3753  }
3754  //if ($this->type == Product::TYPE_SERVICE)
3755  //{
3756  //
3757  //}
3758  if (! empty($conf->accounting->enabled) && $this->status) {
3759  include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
3760  $label.= '<br><b>' . $langs->trans('ProductAccountancySellCode') . ':</b> '. length_accountg($this->accountancy_code_sell);
3761  $label.= '<br><b>' . $langs->trans('ProductAccountancySellIntraCode') . ':</b> '. length_accountg($this->accountancy_code_sell_export);
3762  $label.= '<br><b>' . $langs->trans('ProductAccountancySellExportCode') . ':</b> '. length_accountg($this->accountancy_code_sell_intra);
3763  }
3764  if (! empty($conf->accounting->enabled) && $this->status_buy) {
3765  include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
3766  $label.= '<br><b>' . $langs->trans('ProductAccountancyBuyCode') . ':</b> '. length_accountg($this->accountancy_code_buy);
3767  }
3768  if (! empty($this->entity)) {
3769  $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80);
3770  if ($this->nbphoto > 0) { $label .= '<br>' . $tmpphoto;
3771  }
3772  }
3773 
3774  $linkclose='';
3775  if (empty($notooltip)) {
3776  if (! empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3777  $label=$langs->trans("ShowOrder");
3778  $linkclose.=' alt="'.dol_escape_htmltag($label, 1).'"';
3779  }
3780 
3781  $linkclose.= ' title="'.dol_escape_htmltag($label, 1, 1).'"';
3782  $linkclose.= ' class="classfortooltip"';
3783 
3784  /*
3785  $hookmanager->initHooks(array('productdao'));
3786  $parameters=array('id'=>$this->id);
3787  $reshook=$hookmanager->executeHooks('getnomurltooltip',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks
3788  if ($reshook > 0) $linkclose = $hookmanager->resPrint;
3789  */
3790  }
3791 
3792  if ($option == 'supplier' || $option == 'category') {
3793  $url = DOL_URL_ROOT.'/product/fournisseurs.php?id='.$this->id;
3794  } else if ($option == 'stock') {
3795  $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
3796  } else if ($option == 'composition') {
3797  $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
3798  } else {
3799  $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
3800  }
3801 
3802  if ($option !== 'nolink') {
3803  // Add param to save lastsearch_values or not
3804  $add_save_lastsearch_values=($save_lastsearch_value == 1 ? 1 : 0);
3805  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) { $add_save_lastsearch_values=1;
3806  }
3807  if ($add_save_lastsearch_values) { $url.='&save_lastsearch_values=1';
3808  }
3809  }
3810 
3811  $linkstart = '<a href="'.$url.'"';
3812  $linkstart.=$linkclose.'>';
3813  $linkend='</a>';
3814 
3815  $result.=$linkstart;
3816  if ($withpicto) {
3817  if ($this->type == Product::TYPE_PRODUCT) { $result.=(img_object(($notooltip?'':$label), 'product', ($notooltip?'class="paddingright"':'class="paddingright classfortooltip"'), 0, 0, $notooltip?0:1));
3818  }
3819  if ($this->type == Product::TYPE_SERVICE) { $result.=(img_object(($notooltip?'':$label), 'service', ($notooltip?'class="paddinright"':'class="paddingright classfortooltip"'), 0, 0, $notooltip?0:1));
3820  }
3821  }
3822  $result.= $newref;
3823  $result.= $linkend;
3824 
3825  global $action;
3826  $hookmanager->initHooks(array('productdao'));
3827  $parameters=array('id'=>$this->id, 'getnomurl'=>$result);
3828  $reshook=$hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3829  if ($reshook > 0) { $result = $hookmanager->resPrint;
3830  } else { $result .= $hookmanager->resPrint;
3831  }
3832 
3833  return $result;
3834  }
3835 
3836 
3847  public function generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0)
3848  {
3849  global $conf,$user,$langs;
3850 
3851  $langs->load("products");
3852 
3853  // Positionne le modele sur le nom du modele a utiliser
3854  if (! dol_strlen($modele)) {
3855  if (! empty($conf->global->PRODUCT_ADDON_PDF)) {
3856  $modele = $conf->global->PRODUCT_ADDON_PDF;
3857  }
3858  else
3859  {
3860  $modele = 'strato';
3861  }
3862  }
3863 
3864  $modelpath = "core/modules/product/doc/";
3865 
3866  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
3867  }
3868 
3876  public function getLibStatut($mode=0, $type=0)
3877  {
3878  switch ($type)
3879  {
3880  case 0:
3881  return $this->LibStatut($this->status, $mode, $type);
3882  case 1:
3883  return $this->LibStatut($this->status_buy, $mode, $type);
3884  case 2:
3885  return $this->LibStatut($this->status_batch, $mode, $type);
3886  default:
3887  //Simulate previous behavior but should return an error string
3888  return $this->LibStatut($this->status_buy, $mode, $type);
3889  }
3890  }
3891 
3892  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3901  function LibStatut($status,$mode=0,$type=0)
3902  {
3903  // phpcs:enable
3904  global $conf, $langs;
3905 
3906  $langs->load('products');
3907  if (! empty($conf->productbatch->enabled)) { $langs->load("productbatch");
3908  }
3909 
3910  if ($type == 2) {
3911  switch ($mode)
3912  {
3913  case 0:
3914  return ($status == 0 ? $langs->trans('ProductStatusNotOnBatch') : $langs->trans('ProductStatusOnBatch'));
3915  case 1:
3916  return ($status == 0 ? $langs->trans('ProductStatusNotOnBatchShort') : $langs->trans('ProductStatusOnBatchShort'));
3917  case 2:
3918  return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
3919  case 3:
3920  if ($status == 0) {
3921  return img_picto($langs->trans('ProductStatusNotOnBatch'), 'statut5');
3922  }
3923  return img_picto($langs->trans('ProductStatusOnBatch'), 'statut4');
3924  case 4:
3925  return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
3926  case 5:
3927  return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
3928  default:
3929  return $langs->trans('Unknown');
3930  }
3931  }
3932  if ($mode == 0) {
3933  if ($status == 0) { return ($type==0 ? $langs->trans('ProductStatusNotOnSellShort'):$langs->trans('ProductStatusNotOnBuyShort'));
3934  } elseif ($status == 1) { return ($type==0 ? $langs->trans('ProductStatusOnSellShort'):$langs->trans('ProductStatusOnBuyShort'));
3935  }
3936  }
3937  elseif ($mode == 1) {
3938  if ($status == 0) { return ($type==0 ? $langs->trans('ProductStatusNotOnSell'):$langs->trans('ProductStatusNotOnBuy'));
3939  } elseif ($status == 1) { return ($type==0 ? $langs->trans('ProductStatusOnSell'):$langs->trans('ProductStatusOnBuy'));
3940  }
3941  }
3942  elseif ($mode == 2) {
3943  if ($status == 0) { return img_picto($langs->trans('ProductStatusNotOnSell'), 'statut5', 'class="pictostatus"').' '.($type==0 ? $langs->trans('ProductStatusNotOnSellShort'):$langs->trans('ProductStatusNotOnBuyShort'));
3944  } elseif ($status == 1) { return img_picto($langs->trans('ProductStatusOnSell'), 'statut4', 'class="pictostatus"').' '.($type==0 ? $langs->trans('ProductStatusOnSellShort'):$langs->trans('ProductStatusOnBuyShort'));
3945  }
3946  }
3947  elseif ($mode == 3) {
3948  if ($status == 0) { return img_picto(($type==0 ? $langs->trans('ProductStatusNotOnSell') : $langs->trans('ProductStatusNotOnBuy')), 'statut5', 'class="pictostatus"');
3949  } elseif ($status == 1) { return img_picto(($type==0 ? $langs->trans('ProductStatusOnSell') : $langs->trans('ProductStatusOnBuy')), 'statut4', 'class="pictostatus"');
3950  }
3951  }
3952  elseif ($mode == 4) {
3953  if ($status == 0) { return img_picto($langs->trans('ProductStatusNotOnSell'), 'statut5', 'class="pictostatus"').' '.($type==0 ? $langs->trans('ProductStatusNotOnSell'):$langs->trans('ProductStatusNotOnBuy'));
3954  } elseif ($status == 1) { return img_picto($langs->trans('ProductStatusOnSell'), 'statut4', 'class="pictostatus"').' '.($type==0 ? $langs->trans('ProductStatusOnSell'):$langs->trans('ProductStatusOnBuy'));
3955  }
3956  }
3957  elseif ($mode == 5) {
3958  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"');
3959  } elseif ($status == 1) { return ($type==0 ? $langs->trans('ProductStatusOnSellShort'):$langs->trans('ProductStatusOnBuyShort')).' '.img_picto(($type==0 ? $langs->trans('ProductStatusOnSell'):$langs->trans('ProductStatusOnBuy')), 'statut4', 'class="pictostatus"');
3960  }
3961  }
3962  elseif ($mode == 6) {
3963  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"');
3964  } elseif ($status == 1) { return ($type==0 ? $langs->trans('ProductStatusOnSellShort'):$langs->trans('ProductStatusOnBuyShort')).' '.img_picto(($type==0 ? $langs->trans('ProductStatusOnSell'):$langs->trans('ProductStatusOnBuy')), 'statut4', 'class="pictostatus"');
3965  }
3966  }
3967  return $langs->trans('Unknown');
3968  }
3969 
3970 
3976  function getLibFinished()
3977  {
3978  global $langs;
3979  $langs->load('products');
3980 
3981  if ($this->finished == '0') { return $langs->trans("RowMaterial");
3982  }
3983  if ($this->finished == '1') { return $langs->trans("Finished");
3984  }
3985  return '';
3986  }
3987 
3988 
3989  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4004  function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $inventorycode='', $origin_element='', $origin_id=null)
4005  {
4006  // phpcs:enable
4007  if ($id_entrepot) {
4008  $this->db->begin();
4009 
4010  include_once DOL_DOCUMENT_ROOT .'/product/stock/class/mouvementstock.class.php';
4011 
4012  $op[0] = "+".trim($nbpiece);
4013  $op[1] = "-".trim($nbpiece);
4014 
4015  $movementstock=new MouvementStock($this->db);
4016  $movementstock->setOrigin($origin_element, $origin_id);
4017  $result=$movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode);
4018 
4019  if ($result >= 0) {
4020  $this->db->commit();
4021  return 1;
4022  }
4023  else
4024  {
4025  $this->error=$movementstock->error;
4026  $this->errors=$movementstock->errors;
4027 
4028  $this->db->rollback();
4029  return -1;
4030  }
4031  }
4032  }
4033 
4034  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4052  function correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $dlc='', $dluo='',$lot='', $inventorycode='', $origin_element='', $origin_id=null)
4053  {
4054  // phpcs:enable
4055  if ($id_entrepot) {
4056  $this->db->begin();
4057 
4058  include_once DOL_DOCUMENT_ROOT .'/product/stock/class/mouvementstock.class.php';
4059 
4060  $op[0] = "+".trim($nbpiece);
4061  $op[1] = "-".trim($nbpiece);
4062 
4063  $movementstock=new MouvementStock($this->db);
4064  $movementstock->setOrigin($origin_element, $origin_id);
4065  $result=$movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot);
4066 
4067  if ($result >= 0) {
4068  $this->db->commit();
4069  return 1;
4070  }
4071  else
4072  {
4073  $this->error=$movementstock->error;
4074  $this->errors=$movementstock->errors;
4075 
4076  $this->db->rollback();
4077  return -1;
4078  }
4079  }
4080  }
4081 
4082  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4092  function load_stock($option='')
4093  {
4094  // phpcs:enable
4095  global $conf;
4096 
4097  $this->stock_reel = 0;
4098  $this->stock_warehouse = array();
4099  $this->stock_theorique = 0;
4100 
4101  $warehouseStatus = array();
4102 
4103  if (preg_match('/warehouseclosed/', $option)) {
4104  $warehouseStatus[] = Entrepot::STATUS_CLOSED;
4105  }
4106  if (preg_match('/warehouseopen/', $option)) {
4107  $warehouseStatus[] = Entrepot::STATUS_OPEN_ALL;
4108  }
4109  if (preg_match('/warehouseinternal/', $option)) {
4110  $warehouseStatus[] = Entrepot::STATUS_OPEN_INTERNAL;
4111  }
4112 
4113  $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
4114  $sql.= " FROM ".MAIN_DB_PREFIX."product_stock as ps";
4115  $sql.= ", ".MAIN_DB_PREFIX."entrepot as w";
4116  $sql.= " WHERE w.entity IN (".getEntity('stock').")";
4117  $sql.= " AND w.rowid = ps.fk_entrepot";
4118  $sql.= " AND ps.fk_product = ".$this->id;
4119  if ($conf->global->ENTREPOT_EXTRA_STATUS && count($warehouseStatus)) { $sql.= " AND w.statut IN (".$this->db->escape(implode(',', $warehouseStatus)).")";
4120  }
4121 
4122  dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
4123  $result = $this->db->query($sql);
4124  if ($result) {
4125  $num = $this->db->num_rows($result);
4126  $i=0;
4127  if ($num > 0) {
4128  while ($i < $num)
4129  {
4130  $row = $this->db->fetch_object($result);
4131  $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
4132  $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
4133  $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
4134  if ((! preg_match('/nobatch/', $option)) && $this->hasbatch()) { $this->stock_warehouse[$row->fk_entrepot]->detail_batch=Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
4135  }
4136  $this->stock_reel+=$row->reel;
4137  $i++;
4138  }
4139  }
4140  $this->db->free($result);
4141 
4142  if (! preg_match('/novirtual/', $option)) {
4143  $this->load_virtual_stock(); // This also load stats_commande_fournisseur, ...
4144  }
4145 
4146  return 1;
4147  }
4148  else
4149  {
4150  $this->error=$this->db->lasterror();
4151  return -1;
4152  }
4153  }
4154 
4155  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4164  {
4165  // phpcs:enable
4166  global $conf, $hookmanager, $action;
4167 
4168  $stock_commande_client=0;
4169  $stock_commande_fournisseur=0;
4170  $stock_sending_client=0;
4171  $stock_reception_fournisseur=0;
4172 
4173  if (! empty($conf->commande->enabled)) {
4174  $result=$this->load_stats_commande(0, '1,2', 1);
4175  if ($result < 0) { dol_print_error($this->db, $this->error);
4176  }
4177  $stock_commande_client=$this->stats_commande['qty'];
4178  }
4179  if (! empty($conf->expedition->enabled)) {
4180  $result=$this->load_stats_sending(0, '1,2', 1);
4181  if ($result < 0) { dol_print_error($this->db, $this->error);
4182  }
4183  $stock_sending_client=$this->stats_expedition['qty'];
4184  }
4185  if (! empty($conf->fournisseur->enabled)) {
4186  $result=$this->load_stats_commande_fournisseur(0, '1,2,3,4', 1);
4187  if ($result < 0) { dol_print_error($this->db, $this->error);
4188  }
4189  $stock_commande_fournisseur=$this->stats_commande_fournisseur['qty'];
4190 
4191  $result=$this->load_stats_reception(0, '4', 1);
4192  if ($result < 0) { dol_print_error($this->db, $this->error);
4193  }
4194  $stock_reception_fournisseur=$this->stats_reception['qty'];
4195  }
4196 
4197  // Stock decrease mode
4198  if (! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || ! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
4199  $this->stock_theorique=$this->stock_reel-$stock_commande_client+$stock_sending_client;
4200  }
4201  if (! empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER)) {
4202  $this->stock_theorique=$this->stock_reel;
4203  }
4204  if (! empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
4205  $this->stock_theorique=$this->stock_reel-$stock_commande_client;
4206  }
4207  // Stock Increase mode
4208  if (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER)) {
4209  $this->stock_theorique+=$stock_commande_fournisseur-$stock_reception_fournisseur;
4210  }
4211  if (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER)) {
4212  $this->stock_theorique-=$stock_reception_fournisseur;
4213  }
4214  if (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_BILL)) {
4215  $this->stock_theorique+=$stock_commande_fournisseur-$stock_reception_fournisseur;
4216  }
4217 
4218  if (! is_object($hookmanager)) {
4219  include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
4220  $hookmanager=new HookManager($this->db);
4221  }
4222  $hookmanager->initHooks(array('productdao'));
4223  $parameters=array('id'=>$this->id);
4224  // Note that $action and $object may have been modified by some hooks
4225  $reshook=$hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
4226  if ($reshook > 0) { $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
4227  }
4228 
4229  return 1;
4230  }
4231 
4232 
4240  function loadBatchInfo($batch)
4241  {
4242  $result=array();
4243 
4244  $sql = "SELECT pb.batch, pb.eatby, pb.sellby, SUM(pb.qty) AS qty FROM ".MAIN_DB_PREFIX."product_batch as pb, ".MAIN_DB_PREFIX."product_stock as ps";
4245  $sql.= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".$this->id." AND pb.batch = '".$this->db->escape($batch)."'";
4246  $sql.= " GROUP BY pb.batch, pb.eatby, pb.sellby";
4247  dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
4248  $resql = $this->db->query($sql);
4249  if ($resql) {
4250  $num = $this->db->num_rows($resql);
4251  $i=0;
4252  while ($i < $num)
4253  {
4254  $obj = $this->db->fetch_object($resql);
4255  $result[]=array('batch'=>$batch, 'eatby'=>$this->db->jdate($obj->eatby), 'sellby'=>$this->db->jdate($obj->sellby), 'qty'=>$obj->qty);
4256  $i++;
4257  }
4258  return $result;
4259  }
4260  else
4261  {
4262  dol_print_error($this->db);
4263  $this->db->rollback();
4264  return array();
4265  }
4266  }
4267 
4268 
4269  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4277  function add_photo($sdir, $file)
4278  {
4279  // phpcs:enable
4280  global $conf;
4281 
4282  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4283 
4284  $result = 0;
4285 
4286  $dir = $sdir;
4287  if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) { $dir .= '/'. get_exdir($this->id, 2, 0, 0, $this, 'product') . $this->id ."/photos";
4288  } else { $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
4289  }
4290 
4291  dol_mkdir($dir);
4292 
4293  $dir_osencoded=$dir;
4294 
4295  if (is_dir($dir_osencoded)) {
4296  $originImage = $dir . '/' . $file['name'];
4297 
4298  // Cree fichier en taille origine
4299  $result=dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
4300 
4301  if (file_exists(dol_osencode($originImage))) {
4302  // Create thumbs
4303  $this->addThumbs($originImage);
4304  }
4305  }
4306 
4307  if (is_numeric($result) && $result > 0) { return 1;
4308  } else { return -1;
4309  }
4310  }
4311 
4312  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4319  function is_photo_available($sdir)
4320  {
4321  // phpcs:enable
4322  include_once DOL_DOCUMENT_ROOT .'/core/lib/files.lib.php';
4323  include_once DOL_DOCUMENT_ROOT .'/core/lib/images.lib.php';
4324 
4325  global $conf;
4326 
4327  $dir = $sdir;
4328  if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) { $dir .= '/'. get_exdir($this->id, 2, 0, 0, $this, 'product') . $this->id ."/photos/";
4329  } else { $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref).'/';
4330  }
4331 
4332  $nbphoto=0;
4333 
4334  $dir_osencoded=dol_osencode($dir);
4335  if (file_exists($dir_osencoded)) {
4336  $handle=opendir($dir_osencoded);
4337  if (is_resource($handle)) {
4338  while (($file = readdir($handle)) !== false)
4339  {
4340  if (! utf8_check($file)) { $file=utf8_encode($file); // To be sure data is stored in UTF8 in memory
4341  }
4342  if (dol_is_file($dir.$file) && image_format_supported($file) > 0) { return true;
4343  }
4344  }
4345  }
4346  }
4347  return false;
4348  }
4349 
4350 
4351  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4359  function liste_photos($dir,$nbmax=0)
4360  {
4361  // phpcs:enable
4362  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4363  include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
4364 
4365  $nbphoto=0;
4366  $tabobj=array();
4367 
4368  $dir_osencoded=dol_osencode($dir);
4369  $handle=@opendir($dir_osencoded);
4370  if (is_resource($handle)) {
4371  while (($file = readdir($handle)) !== false)
4372  {
4373  if (! utf8_check($file)) { $file=utf8_encode($file); // readdir returns ISO
4374  }
4375  if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
4376  $nbphoto++;
4377 
4378  // On determine nom du fichier vignette
4379  $photo=$file;
4380  $photo_vignette='';
4381  if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
4382  $photo_vignette=preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
4383  }
4384 
4385  $dirthumb = $dir.'thumbs/';
4386 
4387  // Objet
4388  $obj=array();
4389  $obj['photo']=$photo;
4390  if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) { $obj['photo_vignette']='thumbs/' . $photo_vignette;
4391  } else { $obj['photo_vignette']="";
4392  }
4393 
4394  $tabobj[$nbphoto-1]=$obj;
4395 
4396  // On continue ou on arrete de boucler ?
4397  if ($nbmax && $nbphoto >= $nbmax) { break;
4398  }
4399  }
4400  }
4401 
4402  closedir($handle);
4403  }
4404 
4405  return $tabobj;
4406  }
4407 
4408  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4415  function delete_photo($file)
4416  {
4417  // phpcs:enable
4418  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4419  include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
4420 
4421  $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
4422  $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
4423  $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
4424 
4425  // On efface l'image d'origine
4426  dol_delete_file($file, 0, 0, 0, $this); // For triggers
4427 
4428  // Si elle existe, on efface la vignette
4429  if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
4430  $photo_vignette=preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
4431  if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
4432  dol_delete_file($dirthumb.$photo_vignette);
4433  }
4434 
4435  $photo_vignette=preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
4436  if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
4437  dol_delete_file($dirthumb.$photo_vignette);
4438  }
4439  }
4440  }
4441 
4442  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4449  function get_image_size($file)
4450  {
4451  // phpcs:enable
4452  $file_osencoded=dol_osencode($file);
4453  $infoImg = getimagesize($file_osencoded); // Get information on image
4454  $this->imgWidth = $infoImg[0]; // Largeur de l'image
4455  $this->imgHeight = $infoImg[1]; // Hauteur de l'image
4456  }
4457 
4458  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4464  function load_state_board()
4465  {
4466  // phpcs:enable
4467  global $conf, $user, $hookmanager;
4468 
4469  $this->nb=array();
4470 
4471  $sql = "SELECT count(p.rowid) as nb, fk_product_type";
4472  $sql.= " FROM ".MAIN_DB_PREFIX."product as p";
4473  $sql.= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
4474  // Add where from hooks
4475  if (is_object($hookmanager)) {
4476  $parameters=array();
4477  $reshook=$hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook
4478  $sql.=$hookmanager->resPrint;
4479  }
4480  $sql.= ' GROUP BY fk_product_type';
4481 
4482  $resql=$this->db->query($sql);
4483  if ($resql) {
4484  while ($obj=$this->db->fetch_object($resql))
4485  {
4486  if ($obj->fk_product_type == 1) { $this->nb["services"]=$obj->nb;
4487  } else { $this->nb["products"]=$obj->nb;
4488  }
4489  }
4490  $this->db->free($resql);
4491  return 1;
4492  }
4493  else
4494  {
4495  dol_print_error($this->db);
4496  $this->error=$this->db->error();
4497  return -1;
4498  }
4499  }
4500 
4506  function isProduct()
4507  {
4508  return ($this->type == Product::TYPE_PRODUCT ? true : false);
4509  }
4510 
4516  function isService()
4517  {
4518  return ($this->type == Product::TYPE_SERVICE ? true : false);
4519  }
4520 
4521  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4530  function get_barcode($object,$type='')
4531  {
4532  // phpcs:enable
4533  global $conf;
4534 
4535  $result='';
4536  if (! empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
4537  $dirsociete=array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
4538  foreach ($dirsociete as $dirroot)
4539  {
4540  $res=dol_include_once($dirroot.$conf->global->BARCODE_PRODUCT_ADDON_NUM.'.php');
4541  if ($res) { break;
4542  }
4543  }
4544  $var = $conf->global->BARCODE_PRODUCT_ADDON_NUM;
4545  $mod = new $var;
4546 
4547  $result=$mod->getNextValue($object, $type);
4548 
4549  dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
4550  }
4551  return $result;
4552  }
4553 
4561  function initAsSpecimen()
4562  {
4563  global $user,$langs,$conf,$mysoc;
4564 
4565  $now=dol_now();
4566 
4567  // Initialize parameters
4568  $this->specimen=1;
4569  $this->id=0;
4570  $this->ref = 'PRODUCT_SPEC';
4571  $this->label = 'PRODUCT SPECIMEN';
4572  $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
4573  $this->specimen=1;
4574  $this->country_id=1;
4575  $this->tosell=1;
4576  $this->tobuy=1;
4577  $this->tobatch=0;
4578  $this->note='This is a comment (private)';
4579  $this->date_creation = $now;
4580  $this->date_modification = $now;
4581 
4582  $this->weight = 4;
4583  $this->weight_unit = 1;
4584 
4585  $this->length = 5;
4586  $this->length_unit = 1;
4587  $this->width = 6;
4588  $this->width_unit = 0;
4589  $this->height = null;
4590  $this->height_unit = null;
4591 
4592  $this->surface = 30;
4593  $this->surface_unit = 0;
4594  $this->volume = 300;
4595  $this->volume_unit = 0;
4596 
4597  $this->barcode=-1; // Create barcode automatically
4598  }
4599 
4606  function getLabelOfUnit($type='long')
4607  {
4608  global $langs;
4609 
4610  if (!$this->fk_unit) {
4611  return '';
4612  }
4613 
4614  $langs->load('products');
4615 
4616  $label_type = 'label';
4617 
4618  if ($type == 'short') {
4619  $label_type = 'short_label';
4620  }
4621 
4622  $sql = 'select '.$label_type.' from '.MAIN_DB_PREFIX.'c_units where rowid='.$this->fk_unit;
4623  $resql = $this->db->query($sql);
4624  if($resql && $this->db->num_rows($resql) > 0) {
4625  $res = $this->db->fetch_array($resql);
4626  $label = $res[$label_type];
4627  $this->db->free($resql);
4628  return $label;
4629  }
4630  else
4631  {
4632  $this->error=$this->db->error().' sql='.$sql;
4633  dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
4634  return -1;
4635  }
4636  }
4637 
4643  function hasbatch()
4644  {
4645  return ($this->status_batch == 1 ? true : false);
4646  }
4647 
4648 
4649  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4656  {
4657  // phpcs:enable
4658  global $conf;
4659 
4660  $maxpricesupplier=0;
4661 
4662  if (! empty($conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE)) {
4663  include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
4664  $product_fourn = new ProductFournisseur($this->db);
4665  $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
4666 
4667  if (is_array($product_fourn_list) && count($product_fourn_list)>0) {
4668  foreach($product_fourn_list as $productfourn)
4669  {
4670  if ($productfourn->fourn_unitprice > $maxpricesupplier) {
4671  $maxpricesupplier = $productfourn->fourn_unitprice;
4672  }
4673  }
4674 
4675  $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
4676  }
4677  }
4678 
4679  return $maxpricesupplier;
4680  }
4681 
4682 
4693  public function setCategories($categories)
4694  {
4695  // Handle single category
4696  if (! is_array($categories)) {
4697  $categories = array($categories);
4698  }
4699 
4700  // Get current categories
4701  include_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
4702  $c = new Categorie($this->db);
4703  $existing = $c->containing($this->id, Categorie::TYPE_PRODUCT, 'id');
4704 
4705  // Diff
4706  if (is_array($existing)) {
4707  $to_del = array_diff($existing, $categories);
4708  $to_add = array_diff($categories, $existing);
4709  } else {
4710  $to_del = array(); // Nothing to delete
4711  $to_add = $categories;
4712  }
4713 
4714  // Process
4715  foreach($to_del as $del) {
4716  if ($c->fetch($del) > 0) {
4717  $c->del_type($this, 'product');
4718  }
4719  }
4720  foreach ($to_add as $add) {
4721  if ($c->fetch($add) > 0) {
4722  $c->add_type($this, 'product');
4723  }
4724  }
4725 
4726  return;
4727  }
4728 
4737  public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
4738  {
4739  $tables = array(
4740  'product_customer_price',
4741  'product_customer_price_log'
4742  );
4743 
4744  return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
4745  }
4746 
4758  public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
4759  {
4760  global $conf, $db;
4761 
4762  $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".MAIN_DB_PREFIX."product_pricerules";
4763  $query = $db->query($sql);
4764 
4765  $rules = array();
4766 
4767  while ($result = $db->fetch_object($query)) {
4768  $rules[$result->level] = $result;
4769  }
4770 
4771  //Because prices can be based on other level's prices, we temporarily store them
4772  $prices = array(
4773  1 => $baseprice
4774  );
4775 
4776  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
4777 
4778  $price = $baseprice;
4779  $price_min = $baseprice;
4780 
4781  //We have to make sure it does exist and it is > 0
4782  //First price level only allows changing min_price
4783  if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
4784  $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent/100));
4785  }
4786 
4787  $prices[$i] = $price;
4788 
4789  //We have to make sure it does exist and it is > 0
4790  if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
4791  $price_min = $price * (1 - ($rules[$i]->var_min_percent/100));
4792  }
4793 
4794  //Little check to make sure the price is modified before triggering generation
4795  $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
4796  $check_type = ($baseprice == $this->multiprices_base_type[$i]);
4797 
4798  if ($check_amount && $check_type) {
4799  continue;
4800  }
4801 
4802  if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
4803  return -1;
4804  }
4805  }
4806 
4807  return 1;
4808  }
4809 
4815  public function getRights()
4816  {
4817  global $user;
4818 
4819  if ($this->isProduct()) {
4820  return $user->rights->produit;
4821  } else {
4822  return $user->rights->service;
4823  }
4824  }
4825 
4832  function info($id)
4833  {
4834  $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
4835  $sql.= " p.fk_user_author, p.fk_user_modif";
4836  $sql.= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
4837  $sql.= " WHERE p.rowid = ".$id;
4838 
4839  $result=$this->db->query($sql);
4840  if ($result) {
4841  if ($this->db->num_rows($result)) {
4842  $obj = $this->db->fetch_object($result);
4843 
4844  $this->id = $obj->rowid;
4845 
4846  if ($obj->fk_user_author) {
4847  $cuser = new User($this->db);
4848  $cuser->fetch($obj->fk_user_author);
4849  $this->user_creation = $cuser;
4850  }
4851 
4852  if ($obj->fk_user_modif) {
4853  $muser = new User($this->db);
4854  $muser->fetch($obj->fk_user_modif);
4855  $this->user_modification = $muser;
4856  }
4857 
4858  $this->ref = $obj->ref;
4859  $this->date_creation = $this->db->jdate($obj->date_creation);
4860  $this->date_modification = $this->db->jdate($obj->date_modification);
4861  }
4862 
4863  $this->db->free($result);
4864  }
4865  else
4866  {
4867  dol_print_error($this->db);
4868  }
4869  }
4870 }
print $object label
hash of file content (md5_file(dol_osencode($destfull))
Definition: edit.php:153
is_sousproduit($fk_parent, $fk_child)
Verifie si c&#39;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.
hasVariants()
Return if a product has variants or not.
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.
fetch($id='', $ref='', $ref_ext='', $barcode='', $ignore_expression=0)
Load a product in memory from database.
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:996
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 &#39;...&#39; if string larger than length.
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:1053
Class to parse product price expressions.
addThumbs($file)
Build thumb Move this into files.lib.php.
const STATUS_OPEN_ALL
Warehouse open and operations for customer shipping, supplier dispatch, internal stock transfers/corr...
static replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
isVariant()
Return if loaded product is a variant.
setPriceExpression($expression_id)
Sets the supplier price expression.
</td >< td class="liste_titre" align="right"></td ></tr >< tr class="liste_titre">< input type="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< tr class="oddeven">< td >< input type="checkbox" class="check" name="' . $i . '"' . $disabled . '></td >< td >< input type="checkbox" class="check" name="choose'.$i.'"></td >< td class="nowrap"></td >< td >< input type="hidden" name="desc' . $i . '" value="' . dol_escape_htmltag($objp-> description
Only used if Module[ID]Desc translation string is not found.
Definition: replenish.php:573
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.
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. ...
if(! empty($search_group)) natural_search(array("g.nom" g note
Definition: list.php:123
$product_fourn_id
Id du fournisseur.
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:41
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&#39;un produit/service sur un autre.
const TYPE_SERVICE
Service.
getFather()
Return all parent products for current product (first level only)
$product_id_already_linked
Product ID already linked to a reference supplier.
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.
liste_photos($dir, $nbmax=0)
Retourne tableau de toutes les photos du produit.
get_localtax($vatrate, $local, $thirdparty_buyer="", $thirdparty_seller="", $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller Note: This function applies same rules than get_default_tva.
del_sousproduit($fk_parent, $fk_child)
Retire le lien entre un sousproduit et un produit/service.
get_arbo_each_prod($multiply=1)
Build the tree of subproducts into an array this->sousprods is loaded by this->get_sousproduits_arbo(...
clone_fournisseurs($fromId, $toId)
Recopie les fournisseurs et prix fournisseurs d&#39;un produit/service sur un autre.
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 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.
type
Definition: viewcat.php:284
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
check_barcode($valuetotest, $typefortest)
Check barcode.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
Class to manage categories.
$tva_npr
French VAT NPR (0 or 1)
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0)
Create a document onto disk according to template module.
if(! function_exists('dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
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.
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.
updatePrice($newprice, $newpricebase, $user, $newvat='', $newminprice=0, $level=0, $newnpr=0, $newpbq=0, $ignore_autogen=0, $localtaxes_array=array(), $newdefaultvatcode='')
Modify customer price of a product/Service.
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:1273
load_stats_commande_fournisseur($socid=0, $filtrestatut='', $forVirtualStock=0)
Charge tableau des stats commande fournisseur pour le produit/service.
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1)
Remove a file or several files with a mask.
Definition: files.lib.php:1139
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.
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:451
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&#39;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)
get_sousproduits_arbo()
Return tree of all subproducts for product.
$imgWidth
Taille de l&#39;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.
show_photos($modulepart, $sdir, $size=0, $nbmax=0, $nbbyrow=5, $showfilename=0, $showaction=0, $maxHeight=120, $maxWidth=160, $nolink=0, $notitle=0, $usesharelink=0)
Show photos of an object (nbmax maximum), into several columns.
setCategories($categories)
Sets object to supplied categories.
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.
length_accountg($account)
Return General accounting account with defined length (used for product and miscellaneous) ...
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='')
Show picto whatever it&#39;s its name (generic function)
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='')
Clean a string from all punctuation characters to use it as a ref or login.
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 &#39;...
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.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
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.