dolibarr  19.0.0-dev
bom.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2019 Laurent Destailleur <eldy@users.sourceforge.net>
3  * Copyright (C) 2023 Benjamin Falière <benjamin.faliere@altairis.fr>
4  * Copyright (C) 2023 Charlene Benke <charlene@patas-monkey.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <https://www.gnu.org/licenses/>.
18  */
19 
26 // Put here all includes required by your class file
27 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
28 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
29 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
30 
31 if (isModEnabled('workstation')) {
32  require_once DOL_DOCUMENT_ROOT.'/workstation/class/workstation.class.php';
33 }
34 
35 //require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
36 //require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
37 
38 
42 class BOM extends CommonObject
43 {
44 
48  public $module = 'bom';
49 
53  public $element = 'bom';
54 
58  public $table_element = 'bom_bom';
59 
63  public $ismultientitymanaged = 1;
64 
68  public $isextrafieldmanaged = 1;
69 
73  public $picto = 'bom';
74 
75 
76  const STATUS_DRAFT = 0;
77  const STATUS_VALIDATED = 1;
78  const STATUS_CANCELED = 9;
79 
80 
107  // BEGIN MODULEBUILDER PROPERTIES
111  public $fields = array(
112  'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>"Id",),
113  'entity' => array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'notnull'=> 1, 'default'=>1, 'index'=>1, 'position'=>5),
114  'ref' => array('type'=>'varchar(128)', 'label'=>'Ref', 'enabled'=>1, 'noteditable'=>1, 'visible'=>4, 'position'=>10, 'notnull'=>1, 'default'=>'(PROV)', 'index'=>1, 'searchall'=>1, 'comment'=>"Reference of BOM", 'showoncombobox'=>'1', 'csslist'=>'nowraponall'),
115  'label' => array('type'=>'varchar(255)', 'label'=>'Label', 'enabled'=>1, 'visible'=>1, 'position'=>30, 'notnull'=>1, 'searchall'=>1, 'showoncombobox'=>'2', 'autofocusoncreate'=>1, 'css'=>'minwidth300 maxwidth400', 'csslist'=>'tdoverflowmax200'),
116  'bomtype' => array('type'=>'integer', 'label'=>'Type', 'enabled'=>1, 'visible'=>1, 'position'=>33, 'notnull'=>1, 'default'=>'0', 'arrayofkeyval'=>array(0=>'Manufacturing', 1=>'Disassemble'), 'css'=>'minwidth175', 'csslist'=>'minwidth175 center'),
117  //'bomtype' => array('type'=>'integer', 'label'=>'Type', 'enabled'=>1, 'visible'=>-1, 'position'=>32, 'notnull'=>1, 'default'=>'0', 'arrayofkeyval'=>array(0=>'Manufacturing')),
118  'fk_product' => array('type'=>'integer:Product:product/class/product.class.php:1:((finished:is:null) or (finished:!=:0))', 'label'=>'Product', 'picto'=>'product', 'enabled'=>1, 'visible'=>1, 'position'=>35, 'notnull'=>1, 'index'=>1, 'help'=>'ProductBOMHelp', 'css'=>'maxwidth500', 'csslist'=>'tdoverflowmax100'),
119  'description' => array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>-1, 'position'=>60, 'notnull'=>-1,),
120  'qty' => array('type'=>'real', 'label'=>'Quantity', 'enabled'=>1, 'visible'=>1, 'default'=>1, 'position'=>55, 'notnull'=>1, 'isameasure'=>'1', 'css'=>'maxwidth50imp right'),
121  //'efficiency' => array('type'=>'real', 'label'=>'ManufacturingEfficiency', 'enabled'=>1, 'visible'=>-1, 'default'=>1, 'position'=>100, 'notnull'=>0, 'css'=>'maxwidth50imp', 'help'=>'ValueOfMeansLossForProductProduced'),
122  'duration' => array('type'=>'duration', 'label'=>'EstimatedDuration', 'enabled'=>1, 'visible'=>-1, 'position'=>101, 'notnull'=>-1, 'css'=>'maxwidth50imp', 'help'=>'EstimatedDurationDesc'),
123  'fk_warehouse' => array('type'=>'integer:Entrepot:product/stock/class/entrepot.class.php:0', 'label'=>'WarehouseForProduction', 'picto'=>'stock', 'enabled'=>1, 'visible'=>-1, 'position'=>102, 'css'=>'maxwidth500', 'csslist'=>'tdoverflowmax100'),
124  'note_public' => array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>-2, 'position'=>161, 'notnull'=>-1,),
125  'note_private' => array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>-2, 'position'=>162, 'notnull'=>-1,),
126  'date_creation' => array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>300, 'notnull'=>1,),
127  'tms' => array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-2, 'position'=>501, 'notnull'=>1,),
128  'date_valid' => array('type'=>'datetime', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-2, 'position'=>502, 'notnull'=>0,),
129  'fk_user_creat' => array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserCreation', 'picto'=>'user', 'enabled'=>1, 'visible'=>-2, 'position'=>510, 'notnull'=>1, 'foreignkey'=>'user.rowid', 'csslist'=>'tdoverflowmax100'),
130  'fk_user_modif' => array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'picto'=>'user', 'enabled'=>1, 'visible'=>-2, 'position'=>511, 'notnull'=>-1, 'csslist'=>'tdoverflowmax100'),
131  'fk_user_valid' => array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserValidation', 'picto'=>'user', 'enabled'=>1, 'visible'=>-2, 'position'=>512, 'notnull'=>0, 'csslist'=>'tdoverflowmax100'),
132  'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>1000, 'notnull'=>-1,),
133  'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>1, 'visible'=>0, 'position'=>1010),
134  'status' => array('type'=>'integer', 'label'=>'Status', 'enabled'=>1, 'visible'=>2, 'position'=>1000, 'notnull'=>1, 'default'=>0, 'index'=>1, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Enabled', 9=>'Disabled')),
135  );
136 
140  public $rowid;
141 
145  public $ref;
146 
150  public $label;
151 
155  public $bomtype;
156 
160  public $description;
161 
165  public $date_creation;
166 
170  public $date_valid;
171 
172  public $tms;
173 
177  public $fk_user_creat;
178 
182  public $fk_user_modif;
183 
187  public $fk_user_valid;
188 
192  public $fk_warehouse;
193 
197  public $import_key;
198 
202  public $status;
203 
207  public $fk_product;
208  public $qty;
209  public $duration;
210  public $efficiency;
211  // END MODULEBUILDER PROPERTIES
212 
213 
214  // If this object has a subtable with lines
215 
219  public $table_element_line = 'bom_bomline';
220 
224  public $fk_element = 'fk_bom';
225 
229  public $class_element_line = 'BOMLine';
230 
231  // /**
232  // * @var array List of child tables. To test if we can delete object.
233  // */
234  // protected $childtables=array();
235 
239  protected $childtablesoncascade = array('bom_bomline');
240 
244  public $lines = array();
245 
249  public $total_cost = 0;
250 
254  public $unit_cost = 0;
255 
256 
262  public function __construct(DoliDB $db)
263  {
264  global $conf, $langs;
265 
266  $this->db = $db;
267 
268  if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && isset($this->fields['rowid'])) {
269  $this->fields['rowid']['visible'] = 0;
270  }
271  if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
272  $this->fields['entity']['enabled'] = 0;
273  }
274 
275  // Unset fields that are disabled
276  foreach ($this->fields as $key => $val) {
277  if (isset($val['enabled']) && empty($val['enabled'])) {
278  unset($this->fields[$key]);
279  }
280  }
281 
282  // Translate some data of arrayofkeyval
283  foreach ($this->fields as $key => $val) {
284  if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
285  foreach ($val['arrayofkeyval'] as $key2 => $val2) {
286  $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
287  }
288  }
289  }
290  }
291 
299  public function create(User $user, $notrigger = false)
300  {
301  if ($this->efficiency <= 0 || $this->efficiency > 1) {
302  $this->efficiency = 1;
303  }
304 
305  return $this->createCommon($user, $notrigger);
306  }
307 
315  public function createFromClone(User $user, $fromid)
316  {
317  global $langs, $hookmanager, $extrafields;
318  $error = 0;
319 
320  dol_syslog(__METHOD__, LOG_DEBUG);
321 
322  $object = new self($this->db);
323 
324  $this->db->begin();
325 
326  // Load source object
327  $result = $object->fetchCommon($fromid);
328  if ($result > 0 && !empty($object->table_element_line)) {
329  $object->fetchLines();
330  }
331 
332  // Get lines so they will be clone
333  //foreach ($object->lines as $line)
334  // $line->fetch_optionals();
335 
336  // Reset some properties
337  unset($object->id);
338  unset($object->fk_user_creat);
339  unset($object->import_key);
340 
341  // Clear fields
342  $object->ref = empty($this->fields['ref']['default']) ? $langs->trans("copy_of_").$object->ref : $this->fields['ref']['default'];
343  $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf")." ".$object->label : $this->fields['label']['default'];
344  $object->status = self::STATUS_DRAFT;
345  // ...
346  // Clear extrafields that are unique
347  if (is_array($object->array_options) && count($object->array_options) > 0) {
348  $extrafields->fetch_name_optionals_label($object->table_element);
349  foreach ($object->array_options as $key => $option) {
350  $shortkey = preg_replace('/options_/', '', $key);
351  if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) {
352  //var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
353  unset($object->array_options[$key]);
354  }
355  }
356  }
357 
358  // Create clone
359  $object->context['createfromclone'] = 'createfromclone';
360  $result = $object->createCommon($user);
361  if ($result < 0) {
362  $error++;
363  $this->error = $object->error;
364  $this->errors = $object->errors;
365  }
366 
367  if (!$error) {
368  // copy internal contacts
369  if ($this->copy_linked_contact($object, 'internal') < 0) {
370  $error++;
371  }
372  }
373 
374  if (!$error) {
375  // copy external contacts if same company
376  if (property_exists($this, 'socid') && $this->socid == $object->socid) {
377  if ($this->copy_linked_contact($object, 'external') < 0) {
378  $error++;
379  }
380  }
381  }
382 
383  // If there is lines, create lines too
384 
385 
386 
387  unset($object->context['createfromclone']);
388 
389  // End
390  if (!$error) {
391  $this->db->commit();
392  return $object;
393  } else {
394  $this->db->rollback();
395  return -1;
396  }
397  }
398 
406  public function fetch($id, $ref = null)
407  {
408  $result = $this->fetchCommon($id, $ref);
409 
410  if ($result > 0 && !empty($this->table_element_line)) {
411  $this->fetchLines();
412  }
413  //$this->calculateCosts(); // This consume a high number of subrequests. Do not call it into fetch but when you need it.
414 
415  return $result;
416  }
417 
423  public function fetchLines()
424  {
425  $this->lines = array();
426 
427  $result = $this->fetchLinesCommon();
428  return $result;
429  }
430 
438  public function fetchLinesbytypeproduct($typeproduct = 0)
439  {
440  $this->lines = array();
441 
442  $objectlineclassname = get_class($this).'Line';
443  if (!class_exists($objectlineclassname)) {
444  $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
445  return -1;
446  }
447 
448  $objectline = new $objectlineclassname($this->db);
449 
450  $sql = "SELECT ".$objectline->getFieldList('l');
451  $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
452  $sql .= " LEFT JOIN ".$this->db->prefix()."product as p ON p.rowid = l.fk_product";
453  $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
454  $sql .= " AND p.fk_product_type = ". ((int) $typeproduct);
455  if (isset($objectline->fields['position'])) {
456  $sql .= $this->db->order('position', 'ASC');
457  }
458 
459  $resql = $this->db->query($sql);
460  if ($resql) {
461  $num_rows = $this->db->num_rows($resql);
462  $i = 0;
463  while ($i < $num_rows) {
464  $obj = $this->db->fetch_object($resql);
465  if ($obj) {
466  $newline = new $objectlineclassname($this->db);
467  $newline->setVarsFromFetchObj($obj);
468 
469  $this->lines[] = $newline;
470  }
471  $i++;
472  }
473 
474  return $num_rows;
475  } else {
476  $this->error = $this->db->lasterror();
477  $this->errors[] = $this->error;
478  return -1;
479  }
480  }
481 
482 
494  public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
495  {
496  global $conf;
497 
498  dol_syslog(__METHOD__, LOG_DEBUG);
499 
500  $records = array();
501 
502  $sql = 'SELECT ';
503  $sql .= $this->getFieldList();
504  $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
505  if ($this->ismultientitymanaged) {
506  $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
507  } else {
508  $sql .= ' WHERE 1 = 1';
509  }
510  // Manage filter
511  $sqlwhere = array();
512  if (count($filter) > 0) {
513  foreach ($filter as $key => $value) {
514  if ($key == 't.rowid') {
515  $sqlwhere[] = $key." = ".((int) $value);
516  } elseif (strpos($key, 'date') !== false) {
517  $sqlwhere[] = $key." = '".$this->db->idate($value)."'";
518  } elseif ($key == 'customsql') {
519  $sqlwhere[] = $value;
520  } else {
521  $sqlwhere[] = $key." LIKE '%".$this->db->escape($value)."%'";
522  }
523  }
524  }
525  if (count($sqlwhere) > 0) {
526  $sql .= " AND (".implode(" ".$filtermode." ", $sqlwhere).")";
527  }
528 
529  if (!empty($sortfield)) {
530  $sql .= $this->db->order($sortfield, $sortorder);
531  }
532  if (!empty($limit)) {
533  $sql .= $this->db->plimit($limit, $offset);
534  }
535 
536  $resql = $this->db->query($sql);
537  if ($resql) {
538  $num = $this->db->num_rows($resql);
539 
540  while ($obj = $this->db->fetch_object($resql)) {
541  $record = new self($this->db);
542  $record->setVarsFromFetchObj($obj);
543 
544  $records[$record->id] = $record;
545  }
546  $this->db->free($resql);
547 
548  return $records;
549  } else {
550  $this->errors[] = 'Error '.$this->db->lasterror();
551  dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
552 
553  return -1;
554  }
555  }
556 
564  public function update(User $user, $notrigger = false)
565  {
566  if ($this->efficiency <= 0 || $this->efficiency > 1) {
567  $this->efficiency = 1;
568  }
569 
570  return $this->updateCommon($user, $notrigger);
571  }
572 
580  public function delete(User $user, $notrigger = false)
581  {
582  return $this->deleteCommon($user, $notrigger);
583  //return $this->deleteCommon($user, $notrigger, 1);
584  }
585 
602  public function addLine($fk_product, $qty, $qty_frozen = 0, $disable_stock_change = 0, $efficiency = 1.0, $position = -1, $fk_bom_child = null, $import_key = null, $fk_unit = '', $array_options = 0, $fk_default_workstation = null)
603  {
604  global $mysoc, $conf, $langs, $user;
605 
606  $logtext = "::addLine bomid=$this->id, qty=$qty, fk_product=$fk_product, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
607  $logtext .= ", fk_bom_child=$fk_bom_child, import_key=$import_key";
608  dol_syslog(get_class($this).$logtext, LOG_DEBUG);
609 
610  if ($this->statut == self::STATUS_DRAFT) {
611  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
612 
613  // Clean parameters
614  if (empty($qty)) {
615  $qty = 0;
616  }
617  if (empty($qty_frozen)) {
618  $qty_frozen = 0;
619  }
620  if (empty($disable_stock_change)) {
621  $disable_stock_change = 0;
622  }
623  if (empty($efficiency)) {
624  $efficiency = 1.0;
625  }
626  if (empty($fk_bom_child)) {
627  $fk_bom_child = null;
628  }
629  if (empty($import_key)) {
630  $import_key = null;
631  }
632  if (empty($position)) {
633  $position = -1;
634  }
635 
636  $qty = price2num($qty);
637  $efficiency = price2num($efficiency);
638  $position = price2num($position);
639 
640  $this->db->begin();
641 
642  // Rank to use
643  $rangMax = $this->line_max();
644  $rankToUse = $position;
645  if ($rankToUse <= 0 or $rankToUse > $rangMax) { // New line after existing lines
646  $rankToUse = $rangMax + 1;
647  } else { // New line between the existing lines
648  foreach ($this->lines as $bl) {
649  if ($bl->position >= $rankToUse) {
650  $bl->position++;
651  $bl->update($user);
652  }
653  }
654  }
655 
656  // Insert line
657  $line = new BOMLine($this->db);
658 
659  $line->context = $this->context;
660 
661  $line->fk_bom = $this->id;
662  $line->fk_product = $fk_product;
663  $line->qty = $qty;
664  $line->qty_frozen = $qty_frozen;
665  $line->disable_stock_change = $disable_stock_change;
666  $line->efficiency = $efficiency;
667  $line->fk_bom_child = $fk_bom_child;
668  $line->import_key = $import_key;
669  $line->position = $rankToUse;
670  $line->fk_unit = $fk_unit;
671  $line->fk_default_workstation = $fk_default_workstation;
672 
673  if (is_array($array_options) && count($array_options) > 0) {
674  $line->array_options = $array_options;
675  }
676 
677  $result = $line->create($user);
678 
679  if ($result > 0) {
680  $this->calculateCosts();
681  $this->db->commit();
682  return $result;
683  } else {
684  $this->setErrorsFromObject($line);
685  dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
686  $this->db->rollback();
687  return -2;
688  }
689  } else {
690  dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
691  return -3;
692  }
693  }
694 
709  public function updateLine($rowid, $qty, $qty_frozen = 0, $disable_stock_change = 0, $efficiency = 1.0, $position = -1, $import_key = null, $fk_unit = 0, $array_options = 0)
710  {
711  global $mysoc, $conf, $langs, $user;
712 
713  $logtext = "::updateLine bomid=$this->id, qty=$qty, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
714  $logtext .= ", import_key=$import_key";
715  dol_syslog(get_class($this).$logtext, LOG_DEBUG);
716 
717  if ($this->statut == self::STATUS_DRAFT) {
718  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
719 
720  // Clean parameters
721  if (empty($qty)) {
722  $qty = 0;
723  }
724  if (empty($qty_frozen)) {
725  $qty_frozen = 0;
726  }
727  if (empty($disable_stock_change)) {
728  $disable_stock_change = 0;
729  }
730  if (empty($efficiency)) {
731  $efficiency = 1.0;
732  }
733  if (empty($import_key)) {
734  $import_key = null;
735  }
736  if (empty($position)) {
737  $position = -1;
738  }
739 
740  $qty = price2num($qty);
741  $efficiency = price2num($efficiency);
742  $position = price2num($position);
743 
744  $this->db->begin();
745 
746  //Fetch current line from the database and then clone the object and set it in $oldline property
747  $line = new BOMLine($this->db);
748  $line->fetch($rowid);
749  $line->fetch_optionals();
750 
751  $staticLine = clone $line;
752  $line->oldcopy = $staticLine;
753  $line->context = $this->context;
754 
755  // Rank to use
756  $rankToUse = (int) $position;
757  if ($rankToUse != $line->oldcopy->position) { // check if position have a new value
758  foreach ($this->lines as $bl) {
759  if ($bl->position >= $rankToUse AND $bl->position < ($line->oldcopy->position + 1)) { // move rank up
760  $bl->position++;
761  $bl->update($user);
762  }
763  if ($bl->position <= $rankToUse AND $bl->position > ($line->oldcopy->position)) { // move rank down
764  $bl->position--;
765  $bl->update($user);
766  }
767  }
768  }
769 
770 
771  $line->fk_bom = $this->id;
772  $line->qty = $qty;
773  $line->qty_frozen = $qty_frozen;
774  $line->disable_stock_change = $disable_stock_change;
775  $line->efficiency = $efficiency;
776  $line->import_key = $import_key;
777  $line->position = $rankToUse;
778  if (!empty($fk_unit)) {
779  $line->fk_unit = $fk_unit;
780  }
781 
782  if (is_array($array_options) && count($array_options) > 0) {
783  // We replace values in this->line->array_options only for entries defined into $array_options
784  foreach ($array_options as $key => $value) {
785  $line->array_options[$key] = $array_options[$key];
786  }
787  }
788 
789  $result = $line->update($user);
790 
791  if ($result > 0) {
792  $this->calculateCosts();
793  $this->db->commit();
794  return $result;
795  } else {
796  $this->setErrorsFromObject($line);
797  dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
798  $this->db->rollback();
799  return -2;
800  }
801  } else {
802  dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
803  return -3;
804  }
805  }
806 
815  public function deleteLine(User $user, $idline, $notrigger = false)
816  {
817  if ($this->status < 0) {
818  $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
819  return -2;
820  }
821 
822  $this->db->begin();
823 
824  //Fetch current line from the database and then clone the object and set it in $oldline property
825  $line = new BOMLine($this->db);
826  $line->fetch($idline);
827  $line->fetch_optionals();
828 
829  $staticLine = clone $line;
830  $line->oldcopy = $staticLine;
831  $line->context = $this->context;
832 
833  $result = $line->delete($user, $notrigger);
834 
835  //Positions (rank) reordering
836  foreach ($this->lines as $bl) {
837  if ($bl->position > ($line->oldcopy->position)) { // move rank down
838  $bl->position--;
839  $bl->update($user);
840  }
841  }
842 
843  if ($result > 0) {
844  $this->calculateCosts();
845  $this->db->commit();
846  return $result;
847  } else {
848  $this->setErrorsFromObject($line);
849  dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
850  $this->db->rollback();
851  return -2;
852  }
853  }
854 
862  public function getNextNumRef($prod)
863  {
864  global $langs, $conf;
865  $langs->load("mrp");
866 
867  if (!empty($conf->global->BOM_ADDON)) {
868  $mybool = false;
869 
870  $file = $conf->global->BOM_ADDON.".php";
871  $classname = $conf->global->BOM_ADDON;
872 
873  // Include file with class
874  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
875  foreach ($dirmodels as $reldir) {
876  $dir = dol_buildpath($reldir."core/modules/bom/");
877 
878  // Load file with numbering class (if found)
879  $mybool |= @include_once $dir.$file;
880  }
881 
882  if ($mybool === false) {
883  dol_print_error('', "Failed to include file ".$file);
884  return '';
885  }
886 
887  $obj = new $classname();
888  $numref = $obj->getNextValue($prod, $this);
889 
890  if ($numref != "") {
891  return $numref;
892  } else {
893  $this->error = $obj->error;
894  //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
895  return "";
896  }
897  } else {
898  print $langs->trans("Error")." ".$langs->trans("Error_BOM_ADDON_NotDefined");
899  return "";
900  }
901  }
902 
910  public function validate($user, $notrigger = 0)
911  {
912  global $conf, $langs;
913 
914  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
915 
916  $error = 0;
917 
918  // Protection
919  if ($this->status == self::STATUS_VALIDATED) {
920  dol_syslog(get_class($this)."::validate action abandonned: already validated", LOG_WARNING);
921  return 0;
922  }
923 
924  /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->create))
925  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
926  {
927  $this->error='NotEnoughPermissions';
928  dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
929  return -1;
930  }*/
931 
932  $now = dol_now();
933 
934  $this->db->begin();
935 
936  // Define new ref
937  if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
938  $this->fetch_product();
939  $num = $this->getNextNumRef($this->product);
940  } else {
941  $num = $this->ref;
942  }
943  $this->newref = dol_sanitizeFileName($num);
944 
945  // Validate
946  $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
947  $sql .= " SET ref = '".$this->db->escape($num)."',";
948  $sql .= " status = ".self::STATUS_VALIDATED.",";
949  $sql .= " date_valid='".$this->db->idate($now)."',";
950  $sql .= " fk_user_valid = ".((int) $user->id);
951  $sql .= " WHERE rowid = ".((int) $this->id);
952 
953  dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
954  $resql = $this->db->query($sql);
955  if (!$resql) {
956  dol_print_error($this->db);
957  $this->error = $this->db->lasterror();
958  $error++;
959  }
960 
961  if (!$error && !$notrigger) {
962  // Call trigger
963  $result = $this->call_trigger('BOM_VALIDATE', $user);
964  if ($result < 0) {
965  $error++;
966  }
967  // End call triggers
968  }
969 
970  if (!$error) {
971  $this->oldref = $this->ref;
972 
973  // Rename directory if dir was a temporary ref
974  if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
975  // Now we rename also files into index
976  $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'bom/".$this->db->escape($this->newref)."'";
977  $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
978  $resql = $this->db->query($sql);
979  if (!$resql) {
980  $error++; $this->error = $this->db->lasterror();
981  }
982 
983  // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
984  $oldref = dol_sanitizeFileName($this->ref);
985  $newref = dol_sanitizeFileName($num);
986  $dirsource = $conf->bom->dir_output.'/'.$oldref;
987  $dirdest = $conf->bom->dir_output.'/'.$newref;
988  if (!$error && file_exists($dirsource)) {
989  dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
990 
991  if (@rename($dirsource, $dirdest)) {
992  dol_syslog("Rename ok");
993  // Rename docs starting with $oldref with $newref
994  $listoffiles = dol_dir_list($conf->bom->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
995  foreach ($listoffiles as $fileentry) {
996  $dirsource = $fileentry['name'];
997  $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
998  $dirsource = $fileentry['path'].'/'.$dirsource;
999  $dirdest = $fileentry['path'].'/'.$dirdest;
1000  @rename($dirsource, $dirdest);
1001  }
1002  }
1003  }
1004  }
1005  }
1006 
1007  // Set new ref and current status
1008  if (!$error) {
1009  $this->ref = $num;
1010  $this->status = self::STATUS_VALIDATED;
1011  }
1012 
1013  if (!$error) {
1014  $this->db->commit();
1015  return 1;
1016  } else {
1017  $this->db->rollback();
1018  return -1;
1019  }
1020  }
1021 
1029  public function setDraft($user, $notrigger = 0)
1030  {
1031  // Protection
1032  if ($this->status <= self::STATUS_DRAFT) {
1033  return 0;
1034  }
1035 
1036  /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->write))
1037  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
1038  {
1039  $this->error='Permission denied';
1040  return -1;
1041  }*/
1042 
1043  return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'BOM_UNVALIDATE');
1044  }
1045 
1053  public function cancel($user, $notrigger = 0)
1054  {
1055  // Protection
1056  if ($this->status != self::STATUS_VALIDATED) {
1057  return 0;
1058  }
1059 
1060  /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->write))
1061  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
1062  {
1063  $this->error='Permission denied';
1064  return -1;
1065  }*/
1066 
1067  return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'BOM_CLOSE');
1068  }
1069 
1077  public function reopen($user, $notrigger = 0)
1078  {
1079  // Protection
1080  if ($this->status != self::STATUS_CANCELED) {
1081  return 0;
1082  }
1083 
1084  /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->write))
1085  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
1086  {
1087  $this->error='Permission denied';
1088  return -1;
1089  }*/
1090 
1091  return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'BOM_REOPEN');
1092  }
1093 
1100  public function getTooltipContentArray($params)
1101  {
1102  global $conf, $langs, $user;
1103 
1104  $langs->loadLangs(['product', 'mrp']);
1105 
1106  $datas = [];
1107 
1108  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1109  return ['optimize' => $langs->trans("ShowBillOfMaterials")];
1110  }
1111  $picto = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("BillOfMaterials").'</u>';
1112  if (isset($this->status)) {
1113  $picto .= ' '.$this->getLibStatut(5);
1114  }
1115  $datas['picto'] = $picto;
1116  $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1117  if (isset($this->label)) {
1118  $datas['label'] = '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
1119  }
1120  if (!empty($this->fk_product) && $this->fk_product > 0) {
1121  include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1122  $product = new Product($this->db);
1123  $resultFetch = $product->fetch($this->fk_product);
1124  if ($resultFetch > 0) {
1125  $datas['product'] = "<br><b>".$langs->trans("Product").'</b>: '.$product->ref.' - '.$product->label;
1126  }
1127  }
1128 
1129  return $datas;
1130  }
1131 
1142  public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1143  {
1144  global $db, $conf, $langs, $hookmanager;
1145 
1146  if (!empty($conf->dol_no_mouse_hover)) {
1147  $notooltip = 1; // Force disable tooltips
1148  }
1149 
1150  $result = '';
1151  $params = [
1152  'id' => $this->id,
1153  'objecttype' => $this->element,
1154  'option' => $option,
1155  ];
1156  $classfortooltip = 'classfortooltip';
1157  $dataparams = '';
1158  if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1159  $classfortooltip = 'classforajaxtooltip';
1160  $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1161  $label = '';
1162  } else {
1163  $label = implode($this->getTooltipContentArray($params));
1164  }
1165 
1166  $url = DOL_URL_ROOT.'/bom/bom_card.php?id='.$this->id;
1167 
1168  if ($option != 'nolink') {
1169  // Add param to save lastsearch_values or not
1170  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1171  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1172  $add_save_lastsearch_values = 1;
1173  }
1174  if ($add_save_lastsearch_values) {
1175  $url .= '&save_lastsearch_values=1';
1176  }
1177  }
1178 
1179  $linkclose = '';
1180  if (empty($notooltip)) {
1181  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1182  $label = $langs->trans("ShowBillOfMaterials");
1183  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1184  }
1185  $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1186  $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1187  } else {
1188  $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1189  }
1190 
1191  $linkstart = '<a href="'.$url.'"';
1192  $linkstart .= $linkclose.'>';
1193  $linkend = '</a>';
1194 
1195  $result .= $linkstart;
1196  if ($withpicto) {
1197  $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1198  }
1199  if ($withpicto != 2) {
1200  $result .= $this->ref;
1201  }
1202  $result .= $linkend;
1203  //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1204 
1205  global $action, $hookmanager;
1206  $hookmanager->initHooks(array('bomdao'));
1207  $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
1208  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1209  if ($reshook > 0) {
1210  $result = $hookmanager->resPrint;
1211  } else {
1212  $result .= $hookmanager->resPrint;
1213  }
1214 
1215  return $result;
1216  }
1217 
1224  public function getLibStatut($mode = 0)
1225  {
1226  return $this->LibStatut($this->status, $mode);
1227  }
1228 
1229  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1237  public function LibStatut($status, $mode = 0)
1238  {
1239  // phpcs:enable
1240  if (empty($this->labelStatus)) {
1241  global $langs;
1242  //$langs->load("mrp");
1243  $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1244  $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Enabled');
1245  $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Disabled');
1246  }
1247 
1248  $statusType = 'status'.$status;
1249  if ($status == self::STATUS_VALIDATED) {
1250  $statusType = 'status4';
1251  }
1252  if ($status == self::STATUS_CANCELED) {
1253  $statusType = 'status6';
1254  }
1255 
1256  return dolGetStatus($this->labelStatus[$status], $this->labelStatus[$status], '', $statusType, $mode);
1257  }
1258 
1265  public function info($id)
1266  {
1267  $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1268  $sql .= ' fk_user_creat, fk_user_modif';
1269  $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1270  $sql .= ' WHERE t.rowid = '.((int) $id);
1271  $result = $this->db->query($sql);
1272  if ($result) {
1273  if ($this->db->num_rows($result)) {
1274  $obj = $this->db->fetch_object($result);
1275  $this->id = $obj->rowid;
1276 
1277  $this->user_creation_id = $obj->fk_user_creat;
1278  $this->user_modification_id = $obj->fk_user_modif;
1279  $this->date_creation = $this->db->jdate($obj->datec);
1280  $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1281  }
1282 
1283  $this->db->free($result);
1284  } else {
1285  dol_print_error($this->db);
1286  }
1287  }
1288 
1294  public function getLinesArray()
1295  {
1296  $this->lines = array();
1297 
1298  $objectline = new BOMLine($this->db);
1299  $result = $objectline->fetchAll('ASC', 'position', 0, 0, array('customsql'=>'fk_bom = '.((int) $this->id)));
1300 
1301  if (is_numeric($result)) {
1302  $this->error = $objectline->error;
1303  $this->errors = $objectline->errors;
1304  return $result;
1305  } else {
1306  $this->lines = $result;
1307  return $this->lines;
1308  }
1309  }
1310 
1322  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
1323  {
1324  global $conf, $langs;
1325 
1326  $langs->load("mrp");
1327  $outputlangs->load("products");
1328 
1329  if (!dol_strlen($modele)) {
1330  $modele = '';
1331 
1332  if ($this->model_pdf) {
1333  $modele = $this->model_pdf;
1334  } elseif (!empty($conf->global->BOM_ADDON_PDF)) {
1335  $modele = $conf->global->BOM_ADDON_PDF;
1336  }
1337  }
1338 
1339  $modelpath = "core/modules/bom/doc/";
1340  if (!empty($modele)) {
1341  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
1342  } else {
1343  return 0;
1344  }
1345  }
1346 
1353  public function initAsSpecimen()
1354  {
1355  $this->initAsSpecimenCommon();
1356  $this->ref = 'BOM-123';
1357  $this->date = $this->date_creation;
1358  }
1359 
1360 
1367  public function doScheduledJob()
1368  {
1369  global $conf, $langs;
1370 
1371  //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
1372 
1373  $error = 0;
1374  $this->output = '';
1375  $this->error = '';
1376 
1377  dol_syslog(__METHOD__, LOG_DEBUG);
1378 
1379  $now = dol_now();
1380 
1381  $this->db->begin();
1382 
1383  // ...
1384 
1385  $this->db->commit();
1386 
1387  return $error;
1388  }
1389 
1396  public function calculateCosts()
1397  {
1398  global $conf, $hookmanager;
1399 
1400  include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1401  $this->unit_cost = 0;
1402  $this->total_cost = 0;
1403 
1404  $parameters=array();
1405  $reshook = $hookmanager->executeHooks('calculateCostsBom', $parameters, $this); // Note that $action and $object may have been modified by hook
1406 
1407  if ($reshook > 0) {
1408  return $hookmanager->resPrint;
1409  }
1410 
1411  if (is_array($this->lines) && count($this->lines)) {
1412  require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1413  $productFournisseur = new ProductFournisseur($this->db);
1414  $tmpproduct = new Product($this->db);
1415 
1416  foreach ($this->lines as &$line) {
1417  $tmpproduct->cost_price = 0;
1418  $tmpproduct->pmp = 0;
1419  $result = $tmpproduct->fetch($line->fk_product, '', '', '', 0, 1, 1); // We discard selling price and language loading
1420 
1421  if ($tmpproduct->type == $tmpproduct::TYPE_PRODUCT) {
1422  if (empty($line->fk_bom_child)) {
1423  if ($result < 0) {
1424  $this->error = $tmpproduct->error;
1425  return -1;
1426  }
1427  $line->unit_cost = price2num((!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp);
1428  if (empty($line->unit_cost)) {
1429  if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
1430  if ($productFournisseur->fourn_remise_percent != "0") {
1431  $line->unit_cost = $productFournisseur->fourn_unitprice_with_discount;
1432  } else {
1433  $line->unit_cost = $productFournisseur->fourn_unitprice;
1434  }
1435  }
1436  }
1437 
1438  $line->total_cost = price2num($line->qty * $line->unit_cost, 'MT');
1439 
1440  $this->total_cost += $line->total_cost;
1441  } else {
1442  $bom_child = new BOM($this->db);
1443  $res = $bom_child->fetch($line->fk_bom_child);
1444  if ($res > 0) {
1445  $bom_child->calculateCosts();
1446  $line->childBom[] = $bom_child;
1447  $this->total_cost += price2num($bom_child->total_cost * $line->qty, 'MT');
1448  $this->total_cost += $line->total_cost;
1449  } else {
1450  $this->error = $bom_child->error;
1451  return -2;
1452  }
1453  }
1454  } else {
1455  // Convert qty of line into hours
1456  $unitforline = measuringUnitString($line->fk_unit, '', '', 1);
1457  $qtyhourforline = convertDurationtoHour($line->qty, $unitforline);
1458 
1459  if (isModEnabled('workstation') && !empty($tmpproduct->fk_default_workstation)) {
1460  $workstation = new Workstation($this->db);
1461  $res = $workstation->fetch($tmpproduct->fk_default_workstation);
1462 
1463  if ($res > 0) $line->total_cost = price2num($qtyhourforline * ($workstation->thm_operator_estimated + $workstation->thm_machine_estimated), 'MT');
1464  else {
1465  $this->error = $workstation->error;
1466  return -3;
1467  }
1468  } else {
1469  $defaultdurationofservice = $tmpproduct->duration;
1470  $reg = array();
1471  $qtyhourservice = 0;
1472  if (preg_match('/^(\d+)([a-z]+)$/', $defaultdurationofservice, $reg)) {
1473  $qtyhourservice = convertDurationtoHour($reg[1], $reg[2]);
1474  }
1475 
1476  if ($qtyhourservice) {
1477  $line->total_cost = price2num($qtyhourforline / $qtyhourservice * $tmpproduct->cost_price, 'MT');
1478  } else {
1479  $line->total_cost = price2num($line->qty * $tmpproduct->cost_price, 'MT');
1480  }
1481  }
1482 
1483  $this->total_cost += $line->total_cost;
1484  }
1485  }
1486 
1487  $this->total_cost = price2num($this->total_cost, 'MT');
1488 
1489  if ($this->qty > 0) {
1490  $this->unit_cost = price2num($this->total_cost / $this->qty, 'MU');
1491  } elseif ($this->qty < 0) {
1492  $this->unit_cost = price2num($this->total_cost * $this->qty, 'MU');
1493  }
1494  }
1495 
1496  return 1;
1497  }
1498 
1507  public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
1508  {
1509  $tables = array(
1510  'bom_bomline'
1511  );
1512 
1513  return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
1514  }
1515 
1523  public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
1524  {
1525  if (!empty($this->lines)) {
1526  foreach ($this->lines as $line) {
1527  if (!empty($line->childBom)) {
1528  foreach ($line->childBom as $childBom) $childBom->getNetNeeds($TNetNeeds, $line->qty*$qty);
1529  } else {
1530  if (empty($TNetNeeds[$line->fk_product])) {
1531  $TNetNeeds[$line->fk_product] = 0;
1532  }
1533  $TNetNeeds[$line->fk_product] += $line->qty*$qty;
1534  }
1535  }
1536  }
1537  }
1538 
1547  public function getNetNeedsTree(&$TNetNeeds = array(), $qty = 0, $level = 0)
1548  {
1549  if (!empty($this->lines)) {
1550  foreach ($this->lines as $line) {
1551  if (!empty($line->childBom)) {
1552  foreach ($line->childBom as $childBom) {
1553  $TNetNeeds[$childBom->id]['bom'] = $childBom;
1554  $TNetNeeds[$childBom->id]['parentid'] = $this->id;
1555  $TNetNeeds[$childBom->id]['qty'] = $line->qty*$qty;
1556  $TNetNeeds[$childBom->id]['level'] = $level;
1557  $childBom->getNetNeedsTree($TNetNeeds, $line->qty*$qty, $level+1);
1558  }
1559  } else {
1560  $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
1561  $TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
1562  }
1563  }
1564  }
1565  }
1566 
1575  public function getParentBomTreeRecursive(&$TParentBom, $bom_id = '', $level = 1)
1576  {
1577 
1578  // Protection against infinite loop
1579  if ($level > 1000) {
1580  return;
1581  }
1582 
1583  if (empty($bom_id)) $bom_id=$this->id;
1584 
1585  $sql = 'SELECT l.fk_bom, b.label
1586  FROM '.MAIN_DB_PREFIX.'bom_bomline l
1587  INNER JOIN '.MAIN_DB_PREFIX.$this->table_element.' b ON b.rowid = l.fk_bom
1588  WHERE fk_bom_child = '.((int) $bom_id);
1589 
1590  $resql = $this->db->query($sql);
1591  if (!empty($resql)) {
1592  while ($res = $this->db->fetch_object($resql)) {
1593  $TParentBom[$res->fk_bom] = $res->fk_bom;
1594  $this->getParentBomTreeRecursive($TParentBom, $res->fk_bom, $level+1);
1595  }
1596  }
1597  }
1598 
1606  public function getKanbanView($option = '', $arraydata = null)
1607  {
1608  global $db,$langs;
1609 
1610  $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1611 
1612  $prod = new Product($db);
1613  $prod->fetch($this->fk_product);
1614 
1615  $return = '<div class="box-flex-item box-flex-grow-zero">';
1616  $return .= '<div class="info-box info-box-sm">';
1617  $return .= '<span class="info-box-icon bg-infobox-action">';
1618  $return .= img_picto('', $this->picto);
1619  $return .= '</span>';
1620  $return .= '<div class="info-box-content">';
1621  $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : '').'</span>';
1622  $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1623  if (property_exists($this, 'fields') && !empty($this->fields['bomtype']['arrayofkeyval'])) {
1624  $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Type").' : </span>';
1625  if ($this->bomtype == 0) {
1626  $return .= '<span class="info-box-label">'.$this->fields['bomtype']['arrayofkeyval'][0].'</span>';
1627  } else {
1628  $return .= '<span class="info-box-label">'.$this->fields['bomtype']['arrayofkeyval'][1].'</span>';
1629  }
1630  }
1631  if (property_exists($this, 'fk_product') && !is_null($this->fk_product)) {
1632  $return .= '<br><span class="info-box-label">'.$prod->getNomUrl(1).'</span>';
1633  }
1634  if (method_exists($this, 'getLibStatut')) {
1635  $return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(3).'</div>';
1636  }
1637 
1638  $return .= '</div>';
1639  $return .= '</div>';
1640  $return .= '</div>';
1641  return $return;
1642  }
1643 }
1644 
1645 
1650 {
1654  public $element = 'bomline';
1655 
1659  public $table_element = 'bom_bomline';
1660 
1664  public $ismultientitymanaged = 0;
1665 
1669  public $isextrafieldmanaged = 1;
1670 
1674  public $picto = 'bomline';
1675 
1676 
1696  // BEGIN MODULEBUILDER PROPERTIES
1700  public $fields = array(
1701  'rowid' => array('type'=>'integer', 'label'=>'LineID', 'enabled'=>1, 'visible'=>-1, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>"Id",),
1702  'fk_bom' => array('type'=>'integer:BillOfMaterials:societe/class/bom.class.php', 'label'=>'BillOfMaterials', 'enabled'=>1, 'visible'=>1, 'position'=>10, 'notnull'=>1, 'index'=>1,),
1703  'fk_product' => array('type'=>'integer:Product:product/class/product.class.php', 'label'=>'Product', 'enabled'=>1, 'visible'=>1, 'position'=>20, 'notnull'=>1, 'index'=>1,),
1704  'fk_bom_child' => array('type'=>'integer:BOM:bom/class/bom.class.php', 'label'=>'BillOfMaterials', 'enabled'=>1, 'visible'=>-1, 'position'=>40, 'notnull'=>-1,),
1705  'description' => array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>-1, 'position'=>60, 'notnull'=>-1,),
1706  'qty' => array('type'=>'double(24,8)', 'label'=>'Quantity', 'enabled'=>1, 'visible'=>1, 'position'=>100, 'notnull'=>1, 'isameasure'=>'1',),
1707  'qty_frozen' => array('type'=>'smallint', 'label'=>'QuantityFrozen', 'enabled'=>1, 'visible'=>1, 'default'=>0, 'position'=>105, 'css'=>'maxwidth50imp', 'help'=>'QuantityConsumedInvariable'),
1708  'disable_stock_change' => array('type'=>'smallint', 'label'=>'DisableStockChange', 'enabled'=>1, 'visible'=>1, 'default'=>0, 'position'=>108, 'css'=>'maxwidth50imp', 'help'=>'DisableStockChangeHelp'),
1709  'efficiency' => array('type'=>'double(24,8)', 'label'=>'ManufacturingEfficiency', 'enabled'=>1, 'visible'=>0, 'default'=>1, 'position'=>110, 'notnull'=>1, 'css'=>'maxwidth50imp', 'help'=>'ValueOfEfficiencyConsumedMeans'),
1710  'fk_unit' => array('type'=>'integer', 'label'=>'Unit', 'enabled'=>1, 'visible'=>1, 'position'=>120, 'notnull'=>-1,),
1711  'position' => array('type'=>'integer', 'label'=>'Rank', 'enabled'=>1, 'visible'=>0, 'default'=>0, 'position'=>200, 'notnull'=>1,),
1712  'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>1000, 'notnull'=>-1,),
1713  'fk_default_workstation' =>array('type'=>'integer', 'label'=>'DefaultWorkstation', 'enabled'=>1, 'visible'=>1, 'notnull'=>0, 'position'=>1050)
1714  );
1715 
1719  public $rowid;
1720 
1724  public $fk_bom;
1725 
1729  public $fk_product;
1730 
1734  public $fk_bom_child;
1735 
1739  public $description;
1740  public $qty;
1741 
1745  public $qty_frozen;
1746  public $disable_stock_change;
1747  public $efficiency;
1748 
1752  public $position;
1753 
1757  public $import_key;
1758  // END MODULEBUILDER PROPERTIES
1759 
1763  public $total_cost = 0;
1764 
1768  public $unit_cost = 0;
1769 
1770 
1774  public $childBom = array();
1775 
1779  public $fk_default_workstation;
1780 
1781 
1782 
1788  public function __construct(DoliDB $db)
1789  {
1790  global $conf, $langs;
1791 
1792  $this->db = $db;
1793 
1794  if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && isset($this->fields['rowid'])) {
1795  $this->fields['rowid']['visible'] = 0;
1796  }
1797  if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
1798  $this->fields['entity']['enabled'] = 0;
1799  }
1800 
1801  // Unset fields that are disabled
1802  foreach ($this->fields as $key => $val) {
1803  if (isset($val['enabled']) && empty($val['enabled'])) {
1804  unset($this->fields[$key]);
1805  }
1806  }
1807 
1808  // Translate some data of arrayofkeyval
1809  foreach ($this->fields as $key => $val) {
1810  if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
1811  foreach ($val['arrayofkeyval'] as $key2 => $val2) {
1812  $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
1813  }
1814  }
1815  }
1816  }
1817 
1825  public function create(User $user, $notrigger = false)
1826  {
1827  if ($this->efficiency < 0 || $this->efficiency > 1) {
1828  $this->efficiency = 1;
1829  }
1830 
1831  return $this->createCommon($user, $notrigger);
1832  }
1833 
1841  public function fetch($id, $ref = null)
1842  {
1843  $result = $this->fetchCommon($id, $ref);
1844  //if ($result > 0 && !empty($this->table_element_line)) $this->fetchLines();
1845  return $result;
1846  }
1847 
1859  public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
1860  {
1861  global $conf;
1862 
1863  dol_syslog(__METHOD__, LOG_DEBUG);
1864 
1865  $records = array();
1866 
1867  $sql = 'SELECT ';
1868  $sql .= $this->getFieldList();
1869  $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1870  if ($this->ismultientitymanaged) {
1871  $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
1872  } else {
1873  $sql .= ' WHERE 1 = 1';
1874  }
1875  // Manage filter
1876  $sqlwhere = array();
1877  if (count($filter) > 0) {
1878  foreach ($filter as $key => $value) {
1879  if ($key == 't.rowid') {
1880  $sqlwhere[] = $key." = ".((int) $value);
1881  } elseif (strpos($key, 'date') !== false) {
1882  $sqlwhere[] = $key." = '".$this->db->idate($value)."'";
1883  } elseif ($key == 'customsql') {
1884  $sqlwhere[] = $value;
1885  } else {
1886  $sqlwhere[] = $key." LIKE '%".$this->db->escape($value)."%'";
1887  }
1888  }
1889  }
1890  if (count($sqlwhere) > 0) {
1891  $sql .= ' AND ('.implode(' '.$this->db->escape($filtermode).' ', $sqlwhere).')';
1892  }
1893 
1894  if (!empty($sortfield)) {
1895  $sql .= $this->db->order($sortfield, $sortorder);
1896  }
1897  if (!empty($limit)) {
1898  $sql .= $this->db->plimit($limit, $offset);
1899  }
1900 
1901  $resql = $this->db->query($sql);
1902  if ($resql) {
1903  $num = $this->db->num_rows($resql);
1904 
1905  while ($obj = $this->db->fetch_object($resql)) {
1906  $record = new self($this->db);
1907  $record->setVarsFromFetchObj($obj);
1908 
1909  $records[$record->id] = $record;
1910  }
1911  $this->db->free($resql);
1912 
1913  return $records;
1914  } else {
1915  $this->errors[] = 'Error '.$this->db->lasterror();
1916  dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
1917 
1918  return -1;
1919  }
1920  }
1921 
1929  public function update(User $user, $notrigger = false)
1930  {
1931  if ($this->efficiency < 0 || $this->efficiency > 1) {
1932  $this->efficiency = 1;
1933  }
1934 
1935  return $this->updateCommon($user, $notrigger);
1936  }
1937 
1945  public function delete(User $user, $notrigger = false)
1946  {
1947  return $this->deleteCommon($user, $notrigger);
1948  //return $this->deleteCommon($user, $notrigger, 1);
1949  }
1950 
1961  public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1962  {
1963  global $db, $conf, $langs, $hookmanager;
1964 
1965  if (!empty($conf->dol_no_mouse_hover)) {
1966  $notooltip = 1; // Force disable tooltips
1967  }
1968 
1969  $result = '';
1970 
1971  $label = '<u>'.$langs->trans("BillOfMaterialsLine").'</u>';
1972  $label .= '<br>';
1973  $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref;
1974 
1975  $url = DOL_URL_ROOT.'/bom/bomline_card.php?id='.$this->id;
1976 
1977  if ($option != 'nolink') {
1978  // Add param to save lastsearch_values or not
1979  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1980  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1981  $add_save_lastsearch_values = 1;
1982  }
1983  if ($add_save_lastsearch_values) {
1984  $url .= '&save_lastsearch_values=1';
1985  }
1986  }
1987 
1988  $linkclose = '';
1989  if (empty($notooltip)) {
1990  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1991  $label = $langs->trans("ShowBillOfMaterialsLine");
1992  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1993  }
1994  $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
1995  $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
1996  } else {
1997  $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1998  }
1999 
2000  $linkstart = '<a href="'.$url.'"';
2001  $linkstart .= $linkclose.'>';
2002  $linkend = '</a>';
2003 
2004  $result .= $linkstart;
2005  if ($withpicto) {
2006  $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
2007  }
2008  if ($withpicto != 2) {
2009  $result .= $this->ref;
2010  }
2011  $result .= $linkend;
2012  //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
2013 
2014  global $action, $hookmanager;
2015  $hookmanager->initHooks(array('bomlinedao'));
2016  $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
2017  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2018  if ($reshook > 0) {
2019  $result = $hookmanager->resPrint;
2020  } else {
2021  $result .= $hookmanager->resPrint;
2022  }
2023 
2024  return $result;
2025  }
2026 
2033  public function getLibStatut($mode = 0)
2034  {
2035  return $this->LibStatut($this->status, $mode);
2036  }
2037 
2038  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2046  public function LibStatut($status, $mode = 0)
2047  {
2048  // phpcs:enable
2049  return '';
2050  }
2051 
2058  public function info($id)
2059  {
2060  $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
2061  $sql .= ' fk_user_creat, fk_user_modif';
2062  $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
2063  $sql .= ' WHERE t.rowid = '.((int) $id);
2064  $result = $this->db->query($sql);
2065  if ($result) {
2066  if ($this->db->num_rows($result)) {
2067  $obj = $this->db->fetch_object($result);
2068  $this->id = $obj->rowid;
2069  $this->user_creation_id = $obj->fk_user_creat;
2070  $this->user_modification_id = $obj->fk_user_modif;
2071  $this->date_creation = $this->db->jdate($obj->datec);
2072  $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
2073  }
2074  $this->db->free($result);
2075  } else {
2076  dol_print_error($this->db);
2077  }
2078  }
2079 
2086  public function initAsSpecimen()
2087  {
2088  $this->initAsSpecimenCommon();
2089  }
2090 }
$object ref
Definition: info.php:78
Class for BOM.
Definition: bom.class.php:43
static replaceProduct(DoliDB $db, $origin_id, $dest_id)
Function used to replace a product id with another one.
Definition: bom.class.php:1507
fetchLines()
Load object lines in memory from the database.
Definition: bom.class.php:423
__construct(DoliDB $db)
Constructor.
Definition: bom.class.php:262
calculateCosts()
BOM costs calculation based on cost_price or pmp of each BOM line.
Definition: bom.class.php:1396
info($id)
Load the info information in the object.
Definition: bom.class.php:1265
getLibStatut($mode=0)
Return label of the status.
Definition: bom.class.php:1224
initAsSpecimen()
Initialise object with example values Id must be 0 if object instance is a specimen.
Definition: bom.class.php:1353
validate($user, $notrigger=0)
Validate bom.
Definition: bom.class.php:910
reopen($user, $notrigger=0)
Set cancel status.
Definition: bom.class.php:1077
fetchAll($sortorder='', $sortfield='', $limit=0, $offset=0, array $filter=array(), $filtermode='AND')
Load list of objects in memory from the database.
Definition: bom.class.php:494
cancel($user, $notrigger=0)
Set cancel status.
Definition: bom.class.php:1053
create(User $user, $notrigger=false)
Create object into database.
Definition: bom.class.php:299
LibStatut($status, $mode=0)
Return the status.
Definition: bom.class.php:1237
doScheduledJob()
Action executed by scheduler CAN BE A CRON TASK.
Definition: bom.class.php:1367
getTooltipContentArray($params)
getTooltipContentArray
Definition: bom.class.php:1100
fetchLinesbytypeproduct($typeproduct=0)
Load object lines in memory from the database by type of product.
Definition: bom.class.php:438
createFromClone(User $user, $fromid)
Clone an object into another one.
Definition: bom.class.php:315
fetch($id, $ref=null)
Load object in memory from the database.
Definition: bom.class.php:406
getNomUrl($withpicto=0, $option='', $notooltip=0, $morecss='', $save_lastsearch_value=-1)
Return a link to the object card (with optionaly the picto)
Definition: bom.class.php:1142
getNetNeeds(&$TNetNeeds=array(), $qty=0)
Get Net needs by product.
Definition: bom.class.php:1523
addLine($fk_product, $qty, $qty_frozen=0, $disable_stock_change=0, $efficiency=1.0, $position=-1, $fk_bom_child=null, $import_key=null, $fk_unit='', $array_options=0, $fk_default_workstation=null)
Add an BOM line into database (linked to BOM)
Definition: bom.class.php:602
getNextNumRef($prod)
Returns the reference to the following non used BOM depending on the active numbering module defined ...
Definition: bom.class.php:862
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
Definition: bom.class.php:1322
getLinesArray()
Create an array of lines.
Definition: bom.class.php:1294
setDraft($user, $notrigger=0)
Set draft status.
Definition: bom.class.php:1029
updateLine($rowid, $qty, $qty_frozen=0, $disable_stock_change=0, $efficiency=1.0, $position=-1, $import_key=null, $fk_unit=0, $array_options=0)
Update an BOM line into database.
Definition: bom.class.php:709
update(User $user, $notrigger=false)
Update object into database.
Definition: bom.class.php:564
getKanbanView($option='', $arraydata=null)
Return clicable link of object (with eventually picto)
Definition: bom.class.php:1606
deleteLine(User $user, $idline, $notrigger=false)
Delete a line of object in database.
Definition: bom.class.php:815
getParentBomTreeRecursive(&$TParentBom, $bom_id='', $level=1)
Recursively retrieves all parent bom in the tree that leads to the $bom_id bom.
Definition: bom.class.php:1575
getNetNeedsTree(&$TNetNeeds=array(), $qty=0, $level=0)
Get Net needs Tree by product or bom.
Definition: bom.class.php:1547
Class for BOMLine.
Definition: bom.class.php:1650
create(User $user, $notrigger=false)
Create object into database.
Definition: bom.class.php:1825
fetchAll($sortorder='', $sortfield='', $limit=0, $offset=0, array $filter=array(), $filtermode='AND')
Load list of objects in memory from the database.
Definition: bom.class.php:1859
fetch($id, $ref=null)
Load object in memory from the database.
Definition: bom.class.php:1841
getLibStatut($mode=0)
Return label of the status.
Definition: bom.class.php:2033
update(User $user, $notrigger=false)
Update object into database.
Definition: bom.class.php:1929
__construct(DoliDB $db)
Constructor.
Definition: bom.class.php:1788
getNomUrl($withpicto=0, $option='', $notooltip=0, $morecss='', $save_lastsearch_value=-1)
Return a link to the object card (with optionaly the picto)
Definition: bom.class.php:1961
initAsSpecimen()
Initialise object with example values Id must be 0 if object instance is a specimen.
Definition: bom.class.php:2086
info($id)
Load the info information in the object.
Definition: bom.class.php:2058
LibStatut($status, $mode=0)
Return the status.
Definition: bom.class.php:2046
Parent class of all other business classes (invoices, contracts, proposals, orders,...
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
getFieldList($alias='')
Function to concat keys of fields.
setErrorsFromObject($object)
setErrorsFromObject
fetchCommon($id, $ref=null, $morewhere='')
Load object in memory from the database.
createCommon(User $user, $notrigger=false)
Create object into database.
deleteCommon(User $user, $notrigger=false, $forcechilddeletion=0)
Delete object in database.
setStatusCommon($user, $status, $notrigger=0, $triggercode='')
Set to a status.
initAsSpecimenCommon()
Initialise object with example values Id must be 0 if object instance is a specimen.
copy_linked_contact($objFrom, $source='internal')
Copy contact from one element to current.
fetch_product()
Load the product with id $this->fk_product into this->product.
updateCommon(User $user, $notrigger=false)
Update object into database.
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product id with another one.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
fetchLinesCommon($morewhere='')
Load object in memory from the database.
call_trigger($triggerName, $user)
Call trigger based on this instance.
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Class to manage Dolibarr database access.
Class to manage predefined suppliers products.
Class to manage products or services.
Class to manage Dolibarr users.
Definition: user.class.php:48
Class for Workstation.
if(isModEnabled('facture') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') && $user->hasRight('don', 'lire')) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->hasRight("commande", "lire") &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $sql
Social contributions to pay.
Definition: index.php:746
convertDurationtoHour($duration_value, $duration_unit)
Convert duration to hour.
Definition: date.lib.php:331
dol_dir_list($path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition: files.lib.php:62
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return dolibarr global constant int value.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
measuringUnitString($unit, $measuring_style='', $scale='', $use_short_label=0, $outputlangs=null)
Return translation label of a unit key.