dolibarr  17.0.4
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  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <https://www.gnu.org/licenses/>.
17  */
18 
25 // Put here all includes required by your class file
26 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
27 require_once DOL_DOCUMENT_ROOT.'/workstation/class/workstation.class.php';
28 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
29 //require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
30 //require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
31 
32 
36 class BOM extends CommonObject
37 {
41  public $element = 'bom';
42 
46  public $table_element = 'bom_bom';
47 
51  public $ismultientitymanaged = 1;
52 
56  public $isextrafieldmanaged = 1;
57 
61  public $picto = 'bom';
62 
63 
64  const STATUS_DRAFT = 0;
65  const STATUS_VALIDATED = 1;
66  const STATUS_CANCELED = 9;
67 
68 
95  // BEGIN MODULEBUILDER PROPERTIES
99  public $fields = array(
100  'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>"Id",),
101  'entity' => array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'notnull'=> 1, 'default'=>1, 'index'=>1, 'position'=>5),
102  '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'),
103  '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'),
104  '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'),
105  //'bomtype' => array('type'=>'integer', 'label'=>'Type', 'enabled'=>1, 'visible'=>-1, 'position'=>32, 'notnull'=>1, 'default'=>'0', 'arrayofkeyval'=>array(0=>'Manufacturing')),
106  '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'),
107  'description' => array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>-1, 'position'=>60, 'notnull'=>-1,),
108  'qty' => array('type'=>'real', 'label'=>'Quantity', 'enabled'=>1, 'visible'=>1, 'default'=>1, 'position'=>55, 'notnull'=>1, 'isameasure'=>'1', 'css'=>'maxwidth50imp right'),
109  //'efficiency' => array('type'=>'real', 'label'=>'ManufacturingEfficiency', 'enabled'=>1, 'visible'=>-1, 'default'=>1, 'position'=>100, 'notnull'=>0, 'css'=>'maxwidth50imp', 'help'=>'ValueOfMeansLossForProductProduced'),
110  'duration' => array('type'=>'duration', 'label'=>'EstimatedDuration', 'enabled'=>1, 'visible'=>-1, 'position'=>101, 'notnull'=>-1, 'css'=>'maxwidth50imp', 'help'=>'EstimatedDurationDesc'),
111  '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'),
112  'note_public' => array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>-2, 'position'=>161, 'notnull'=>-1,),
113  'note_private' => array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>-2, 'position'=>162, 'notnull'=>-1,),
114  'date_creation' => array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>300, 'notnull'=>1,),
115  'tms' => array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-2, 'position'=>501, 'notnull'=>1,),
116  'date_valid' => array('type'=>'datetime', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-2, 'position'=>502, 'notnull'=>0,),
117  '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'),
118  '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'),
119  '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'),
120  'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>1000, 'notnull'=>-1,),
121  'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>1, 'visible'=>0, 'position'=>1010),
122  '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')),
123  );
124 
128  public $rowid;
129 
133  public $ref;
134 
138  public $label;
139 
143  public $bomtype;
144 
148  public $description;
149 
153  public $date_creation;
154 
155 
156  public $tms;
157 
161  public $fk_user_creat;
162 
166  public $fk_user_modif;
167 
171  public $import_key;
172 
176  public $status;
177 
181  public $fk_product;
182  public $qty;
183  public $efficiency;
184  // END MODULEBUILDER PROPERTIES
185 
186 
187  // If this object has a subtable with lines
188 
192  public $table_element_line = 'bom_bomline';
193 
197  public $fk_element = 'fk_bom';
198 
202  public $class_element_line = 'BOMLine';
203 
204  // /**
205  // * @var array List of child tables. To test if we can delete object.
206  // */
207  // protected $childtables=array();
208 
212  protected $childtablesoncascade = array('bom_bomline');
213 
217  public $lines = array();
218 
222  public $total_cost = 0;
223 
227  public $unit_cost = 0;
228 
229 
230 
236  public function __construct(DoliDB $db)
237  {
238  global $conf, $langs;
239 
240  $this->db = $db;
241 
242  if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && isset($this->fields['rowid'])) {
243  $this->fields['rowid']['visible'] = 0;
244  }
245  if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
246  $this->fields['entity']['enabled'] = 0;
247  }
248 
249  // Unset fields that are disabled
250  foreach ($this->fields as $key => $val) {
251  if (isset($val['enabled']) && empty($val['enabled'])) {
252  unset($this->fields[$key]);
253  }
254  }
255 
256  // Translate some data of arrayofkeyval
257  foreach ($this->fields as $key => $val) {
258  if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
259  foreach ($val['arrayofkeyval'] as $key2 => $val2) {
260  $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
261  }
262  }
263  }
264  }
265 
273  public function create(User $user, $notrigger = false)
274  {
275  if ($this->efficiency <= 0 || $this->efficiency > 1) {
276  $this->efficiency = 1;
277  }
278 
279  return $this->createCommon($user, $notrigger);
280  }
281 
289  public function createFromClone(User $user, $fromid)
290  {
291  global $langs, $hookmanager, $extrafields;
292  $error = 0;
293 
294  dol_syslog(__METHOD__, LOG_DEBUG);
295 
296  $object = new self($this->db);
297 
298  $this->db->begin();
299 
300  // Load source object
301  $result = $object->fetchCommon($fromid);
302  if ($result > 0 && !empty($object->table_element_line)) {
303  $object->fetchLines();
304  }
305 
306  // Get lines so they will be clone
307  //foreach ($object->lines as $line)
308  // $line->fetch_optionals();
309 
310  // Reset some properties
311  unset($object->id);
312  unset($object->fk_user_creat);
313  unset($object->import_key);
314 
315  // Clear fields
316  $object->ref = empty($this->fields['ref']['default']) ? $langs->trans("copy_of_").$object->ref : $this->fields['ref']['default'];
317  $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf")." ".$object->label : $this->fields['label']['default'];
318  $object->status = self::STATUS_DRAFT;
319  // ...
320  // Clear extrafields that are unique
321  if (is_array($object->array_options) && count($object->array_options) > 0) {
322  $extrafields->fetch_name_optionals_label($object->table_element);
323  foreach ($object->array_options as $key => $option) {
324  $shortkey = preg_replace('/options_/', '', $key);
325  if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) {
326  //var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
327  unset($object->array_options[$key]);
328  }
329  }
330  }
331 
332  // Create clone
333  $object->context['createfromclone'] = 'createfromclone';
334  $result = $object->createCommon($user);
335  if ($result < 0) {
336  $error++;
337  $this->error = $object->error;
338  $this->errors = $object->errors;
339  }
340 
341  if (!$error) {
342  // copy internal contacts
343  if ($this->copy_linked_contact($object, 'internal') < 0) {
344  $error++;
345  }
346  }
347 
348  if (!$error) {
349  // copy external contacts if same company
350  if (property_exists($this, 'socid') && $this->socid == $object->socid) {
351  if ($this->copy_linked_contact($object, 'external') < 0) {
352  $error++;
353  }
354  }
355  }
356 
357  // If there is lines, create lines too
358 
359 
360 
361  unset($object->context['createfromclone']);
362 
363  // End
364  if (!$error) {
365  $this->db->commit();
366  return $object;
367  } else {
368  $this->db->rollback();
369  return -1;
370  }
371  }
372 
380  public function fetch($id, $ref = null)
381  {
382  $result = $this->fetchCommon($id, $ref);
383 
384  if ($result > 0 && !empty($this->table_element_line)) {
385  $this->fetchLines();
386  }
387  //$this->calculateCosts(); // This consume a high number of subrequests. Do not call it into fetch but when you need it.
388 
389  return $result;
390  }
391 
397  public function fetchLines()
398  {
399  $this->lines = array();
400 
401  $result = $this->fetchLinesCommon();
402  return $result;
403  }
404 
412  public function fetchLinesbytypeproduct($typeproduct = 0)
413  {
414  $this->lines = array();
415 
416  $objectlineclassname = get_class($this).'Line';
417  if (!class_exists($objectlineclassname)) {
418  $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
419  return -1;
420  }
421 
422  $objectline = new $objectlineclassname($this->db);
423 
424  $sql = "SELECT ".$objectline->getFieldList('l');
425  $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
426  $sql .= " LEFT JOIN ".$this->db->prefix()."product as p ON p.rowid = l.fk_product";
427  $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
428  $sql .= " AND p.fk_product_type = ". ((int) $typeproduct);
429  if (isset($objectline->fields['position'])) {
430  $sql .= $this->db->order('position', 'ASC');
431  }
432 
433  $resql = $this->db->query($sql);
434  if ($resql) {
435  $num_rows = $this->db->num_rows($resql);
436  $i = 0;
437  while ($i < $num_rows) {
438  $obj = $this->db->fetch_object($resql);
439  if ($obj) {
440  $newline = new $objectlineclassname($this->db);
441  $newline->setVarsFromFetchObj($obj);
442 
443  $this->lines[] = $newline;
444  }
445  $i++;
446  }
447 
448  return $num_rows;
449  } else {
450  $this->error = $this->db->lasterror();
451  $this->errors[] = $this->error;
452  return -1;
453  }
454  }
455 
456 
468  public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
469  {
470  global $conf;
471 
472  dol_syslog(__METHOD__, LOG_DEBUG);
473 
474  $records = array();
475 
476  $sql = 'SELECT ';
477  $sql .= $this->getFieldList();
478  $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
479  if ($this->ismultientitymanaged) {
480  $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
481  } else {
482  $sql .= ' WHERE 1 = 1';
483  }
484  // Manage filter
485  $sqlwhere = array();
486  if (count($filter) > 0) {
487  foreach ($filter as $key => $value) {
488  if ($key == 't.rowid') {
489  $sqlwhere[] = $key." = ".((int) $value);
490  } elseif (strpos($key, 'date') !== false) {
491  $sqlwhere[] = $key." = '".$this->db->idate($value)."'";
492  } elseif ($key == 'customsql') {
493  $sqlwhere[] = $value;
494  } else {
495  $sqlwhere[] = $key." LIKE '%".$this->db->escape($value)."%'";
496  }
497  }
498  }
499  if (count($sqlwhere) > 0) {
500  $sql .= " AND (".implode(" ".$filtermode." ", $sqlwhere).")";
501  }
502 
503  if (!empty($sortfield)) {
504  $sql .= $this->db->order($sortfield, $sortorder);
505  }
506  if (!empty($limit)) {
507  $sql .= $this->db->plimit($limit, $offset);
508  }
509 
510  $resql = $this->db->query($sql);
511  if ($resql) {
512  $num = $this->db->num_rows($resql);
513 
514  while ($obj = $this->db->fetch_object($resql)) {
515  $record = new self($this->db);
516  $record->setVarsFromFetchObj($obj);
517 
518  $records[$record->id] = $record;
519  }
520  $this->db->free($resql);
521 
522  return $records;
523  } else {
524  $this->errors[] = 'Error '.$this->db->lasterror();
525  dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
526 
527  return -1;
528  }
529  }
530 
538  public function update(User $user, $notrigger = false)
539  {
540  if ($this->efficiency <= 0 || $this->efficiency > 1) {
541  $this->efficiency = 1;
542  }
543 
544  return $this->updateCommon($user, $notrigger);
545  }
546 
554  public function delete(User $user, $notrigger = false)
555  {
556  return $this->deleteCommon($user, $notrigger);
557  //return $this->deleteCommon($user, $notrigger, 1);
558  }
559 
575  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)
576  {
577  global $mysoc, $conf, $langs, $user;
578 
579  $logtext = "::addLine bomid=$this->id, qty=$qty, fk_product=$fk_product, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
580  $logtext .= ", fk_bom_child=$fk_bom_child, import_key=$import_key";
581  dol_syslog(get_class($this).$logtext, LOG_DEBUG);
582 
583  if ($this->statut == self::STATUS_DRAFT) {
584  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
585 
586  // Clean parameters
587  if (empty($qty)) {
588  $qty = 0;
589  }
590  if (empty($qty_frozen)) {
591  $qty_frozen = 0;
592  }
593  if (empty($disable_stock_change)) {
594  $disable_stock_change = 0;
595  }
596  if (empty($efficiency)) {
597  $efficiency = 1.0;
598  }
599  if (empty($fk_bom_child)) {
600  $fk_bom_child = null;
601  }
602  if (empty($import_key)) {
603  $import_key = null;
604  }
605  if (empty($position)) {
606  $position = -1;
607  }
608 
609  $qty = price2num($qty);
610  $efficiency = price2num($efficiency);
611  $position = price2num($position);
612 
613  $this->db->begin();
614 
615  // Rank to use
616  $rangMax = $this->line_max();
617  $rankToUse = $position;
618  if ($rankToUse <= 0 or $rankToUse > $rangMax) { // New line after existing lines
619  $rankToUse = $rangMax + 1;
620  } else { // New line between the existing lines
621  foreach ($this->lines as $bl) {
622  if ($bl->position >= $rankToUse) {
623  $bl->position++;
624  $bl->update($user);
625  }
626  }
627  }
628 
629  // Insert line
630  $this->line = new BOMLine($this->db);
631 
632  $this->line->context = $this->context;
633 
634  $this->line->fk_bom = $this->id;
635  $this->line->fk_product = $fk_product;
636  $this->line->qty = $qty;
637  $this->line->qty_frozen = $qty_frozen;
638  $this->line->disable_stock_change = $disable_stock_change;
639  $this->line->efficiency = $efficiency;
640  $this->line->fk_bom_child = $fk_bom_child;
641  $this->line->import_key = $import_key;
642  $this->line->position = $rankToUse;
643  $this->line->fk_unit = $fk_unit;
644 
645  if (is_array($array_options) && count($array_options) > 0) {
646  $this->line->array_options = $array_options;
647  }
648 
649  $result = $this->line->create($user);
650 
651  if ($result > 0) {
652  $this->calculateCosts();
653  $this->db->commit();
654  return $result;
655  } else {
656  $this->error = $this->line->error;
657  dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
658  $this->db->rollback();
659  return -2;
660  }
661  } else {
662  dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
663  return -3;
664  }
665  }
666 
681  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)
682  {
683  global $mysoc, $conf, $langs, $user;
684 
685  $logtext = "::updateLine bomid=$this->id, qty=$qty, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
686  $logtext .= ", import_key=$import_key";
687  dol_syslog(get_class($this).$logtext, LOG_DEBUG);
688 
689  if ($this->statut == self::STATUS_DRAFT) {
690  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
691 
692  // Clean parameters
693  if (empty($qty)) {
694  $qty = 0;
695  }
696  if (empty($qty_frozen)) {
697  $qty_frozen = 0;
698  }
699  if (empty($disable_stock_change)) {
700  $disable_stock_change = 0;
701  }
702  if (empty($efficiency)) {
703  $efficiency = 1.0;
704  }
705  if (empty($import_key)) {
706  $import_key = null;
707  }
708  if (empty($position)) {
709  $position = -1;
710  }
711 
712  $qty = price2num($qty);
713  $efficiency = price2num($efficiency);
714  $position = price2num($position);
715 
716  $this->db->begin();
717 
718  //Fetch current line from the database and then clone the object and set it in $oldline property
719  $line = new BOMLine($this->db);
720  $line->fetch($rowid);
721  $line->fetch_optionals();
722 
723  $staticLine = clone $line;
724  $line->oldcopy = $staticLine;
725  $this->line = $line;
726  $this->line->context = $this->context;
727 
728  // Rank to use
729  $rankToUse = (int) $position;
730  if ($rankToUse != $line->oldcopy->position) { // check if position have a new value
731  foreach ($this->lines as $bl) {
732  if ($bl->position >= $rankToUse AND $bl->position < ($line->oldcopy->position + 1)) { // move rank up
733  $bl->position++;
734  $bl->update($user);
735  }
736  if ($bl->position <= $rankToUse AND $bl->position > ($line->oldcopy->position)) { // move rank down
737  $bl->position--;
738  $bl->update($user);
739  }
740  }
741  }
742 
743 
744  $this->line->fk_bom = $this->id;
745  $this->line->qty = $qty;
746  $this->line->qty_frozen = $qty_frozen;
747  $this->line->disable_stock_change = $disable_stock_change;
748  $this->line->efficiency = $efficiency;
749  $this->line->import_key = $import_key;
750  $this->line->position = $rankToUse;
751  if (!empty($fk_unit)) {
752  $this->line->fk_unit = $fk_unit;
753  }
754 
755  if (is_array($array_options) && count($array_options) > 0) {
756  // We replace values in this->line->array_options only for entries defined into $array_options
757  foreach ($array_options as $key => $value) {
758  $this->line->array_options[$key] = $array_options[$key];
759  }
760  }
761 
762  $result = $this->line->update($user);
763 
764  if ($result > 0) {
765  $this->calculateCosts();
766  $this->db->commit();
767  return $result;
768  } else {
769  $this->error = $this->line->error;
770  dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
771  $this->db->rollback();
772  return -2;
773  }
774  } else {
775  dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
776  return -3;
777  }
778  }
779 
788  public function deleteLine(User $user, $idline, $notrigger = false)
789  {
790  if ($this->status < 0) {
791  $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
792  return -2;
793  }
794 
795  $this->db->begin();
796 
797  //Fetch current line from the database and then clone the object and set it in $oldline property
798  $line = new BOMLine($this->db);
799  $line->fetch($idline);
800  $line->fetch_optionals();
801 
802  $staticLine = clone $line;
803  $line->oldcopy = $staticLine;
804  $this->line = $line;
805  $this->line->context = $this->context;
806 
807  $result = $this->line->delete($user, $notrigger);
808 
809  //Positions (rank) reordering
810  foreach ($this->lines as $bl) {
811  if ($bl->position > ($line->oldcopy->position)) { // move rank down
812  $bl->position--;
813  $bl->update($user);
814  }
815  }
816 
817  if ($result > 0) {
818  $this->calculateCosts();
819  $this->db->commit();
820  return $result;
821  } else {
822  $this->error = $this->line->error;
823  dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
824  $this->db->rollback();
825  return -2;
826  }
827  }
828 
836  public function getNextNumRef($prod)
837  {
838  global $langs, $conf;
839  $langs->load("mrp");
840 
841  if (!empty($conf->global->BOM_ADDON)) {
842  $mybool = false;
843 
844  $file = $conf->global->BOM_ADDON.".php";
845  $classname = $conf->global->BOM_ADDON;
846 
847  // Include file with class
848  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
849  foreach ($dirmodels as $reldir) {
850  $dir = dol_buildpath($reldir."core/modules/bom/");
851 
852  // Load file with numbering class (if found)
853  $mybool |= @include_once $dir.$file;
854  }
855 
856  if ($mybool === false) {
857  dol_print_error('', "Failed to include file ".$file);
858  return '';
859  }
860 
861  $obj = new $classname();
862  $numref = $obj->getNextValue($prod, $this);
863 
864  if ($numref != "") {
865  return $numref;
866  } else {
867  $this->error = $obj->error;
868  //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
869  return "";
870  }
871  } else {
872  print $langs->trans("Error")." ".$langs->trans("Error_BOM_ADDON_NotDefined");
873  return "";
874  }
875  }
876 
884  public function validate($user, $notrigger = 0)
885  {
886  global $conf, $langs;
887 
888  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
889 
890  $error = 0;
891 
892  // Protection
893  if ($this->status == self::STATUS_VALIDATED) {
894  dol_syslog(get_class($this)."::validate action abandonned: already validated", LOG_WARNING);
895  return 0;
896  }
897 
898  /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->create))
899  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
900  {
901  $this->error='NotEnoughPermissions';
902  dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
903  return -1;
904  }*/
905 
906  $now = dol_now();
907 
908  $this->db->begin();
909 
910  // Define new ref
911  if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
912  $this->fetch_product();
913  $num = $this->getNextNumRef($this->product);
914  } else {
915  $num = $this->ref;
916  }
917  $this->newref = dol_sanitizeFileName($num);
918 
919  // Validate
920  $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
921  $sql .= " SET ref = '".$this->db->escape($num)."',";
922  $sql .= " status = ".self::STATUS_VALIDATED.",";
923  $sql .= " date_valid='".$this->db->idate($now)."',";
924  $sql .= " fk_user_valid = ".((int) $user->id);
925  $sql .= " WHERE rowid = ".((int) $this->id);
926 
927  dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
928  $resql = $this->db->query($sql);
929  if (!$resql) {
930  dol_print_error($this->db);
931  $this->error = $this->db->lasterror();
932  $error++;
933  }
934 
935  if (!$error && !$notrigger) {
936  // Call trigger
937  $result = $this->call_trigger('BOM_VALIDATE', $user);
938  if ($result < 0) {
939  $error++;
940  }
941  // End call triggers
942  }
943 
944  if (!$error) {
945  $this->oldref = $this->ref;
946 
947  // Rename directory if dir was a temporary ref
948  if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
949  // Now we rename also files into index
950  $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)."'";
951  $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
952  $resql = $this->db->query($sql);
953  if (!$resql) {
954  $error++; $this->error = $this->db->lasterror();
955  }
956 
957  // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
958  $oldref = dol_sanitizeFileName($this->ref);
959  $newref = dol_sanitizeFileName($num);
960  $dirsource = $conf->bom->dir_output.'/'.$oldref;
961  $dirdest = $conf->bom->dir_output.'/'.$newref;
962  if (!$error && file_exists($dirsource)) {
963  dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
964 
965  if (@rename($dirsource, $dirdest)) {
966  dol_syslog("Rename ok");
967  // Rename docs starting with $oldref with $newref
968  $listoffiles = dol_dir_list($conf->bom->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
969  foreach ($listoffiles as $fileentry) {
970  $dirsource = $fileentry['name'];
971  $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
972  $dirsource = $fileentry['path'].'/'.$dirsource;
973  $dirdest = $fileentry['path'].'/'.$dirdest;
974  @rename($dirsource, $dirdest);
975  }
976  }
977  }
978  }
979  }
980 
981  // Set new ref and current status
982  if (!$error) {
983  $this->ref = $num;
984  $this->status = self::STATUS_VALIDATED;
985  }
986 
987  if (!$error) {
988  $this->db->commit();
989  return 1;
990  } else {
991  $this->db->rollback();
992  return -1;
993  }
994  }
995 
1003  public function setDraft($user, $notrigger = 0)
1004  {
1005  // Protection
1006  if ($this->status <= self::STATUS_DRAFT) {
1007  return 0;
1008  }
1009 
1010  /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->write))
1011  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
1012  {
1013  $this->error='Permission denied';
1014  return -1;
1015  }*/
1016 
1017  return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'BOM_UNVALIDATE');
1018  }
1019 
1027  public function cancel($user, $notrigger = 0)
1028  {
1029  // Protection
1030  if ($this->status != self::STATUS_VALIDATED) {
1031  return 0;
1032  }
1033 
1034  /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->write))
1035  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
1036  {
1037  $this->error='Permission denied';
1038  return -1;
1039  }*/
1040 
1041  return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'BOM_CLOSE');
1042  }
1043 
1051  public function reopen($user, $notrigger = 0)
1052  {
1053  // Protection
1054  if ($this->status != self::STATUS_CANCELED) {
1055  return 0;
1056  }
1057 
1058  /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->write))
1059  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
1060  {
1061  $this->error='Permission denied';
1062  return -1;
1063  }*/
1064 
1065  return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'BOM_REOPEN');
1066  }
1067 
1068 
1079  public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1080  {
1081  global $db, $conf, $langs, $hookmanager;
1082 
1083  if (!empty($conf->dol_no_mouse_hover)) {
1084  $notooltip = 1; // Force disable tooltips
1085  }
1086 
1087  $result = '';
1088 
1089  $label = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("BillOfMaterials").'</u>';
1090  if (isset($this->status)) {
1091  $label .= ' '.$this->getLibStatut(5);
1092  }
1093  $label .= '<br>';
1094  $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref;
1095  if (isset($this->label)) {
1096  $label .= '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
1097  }
1098  if (!empty($this->fk_product) && $this->fk_product > 0) {
1099  include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1100  $product = new Product($db);
1101  $resultFetch = $product->fetch($this->fk_product);
1102  if ($resultFetch > 0) {
1103  $label .= "<br><b>".$langs->trans("Product").'</b>: '.$product->ref.' - '.$product->label;
1104  }
1105  }
1106 
1107 
1108  $url = DOL_URL_ROOT.'/bom/bom_card.php?id='.$this->id;
1109 
1110  if ($option != 'nolink') {
1111  // Add param to save lastsearch_values or not
1112  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1113  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1114  $add_save_lastsearch_values = 1;
1115  }
1116  if ($add_save_lastsearch_values) {
1117  $url .= '&save_lastsearch_values=1';
1118  }
1119  }
1120 
1121  $linkclose = '';
1122  if (empty($notooltip)) {
1123  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1124  $label = $langs->trans("ShowBillOfMaterials");
1125  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1126  }
1127  $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
1128  $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
1129  } else {
1130  $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1131  }
1132 
1133  $linkstart = '<a href="'.$url.'"';
1134  $linkstart .= $linkclose.'>';
1135  $linkend = '</a>';
1136 
1137  $result .= $linkstart;
1138  if ($withpicto) {
1139  $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);
1140  }
1141  if ($withpicto != 2) {
1142  $result .= $this->ref;
1143  }
1144  $result .= $linkend;
1145  //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1146 
1147  global $action, $hookmanager;
1148  $hookmanager->initHooks(array('bomdao'));
1149  $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
1150  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1151  if ($reshook > 0) {
1152  $result = $hookmanager->resPrint;
1153  } else {
1154  $result .= $hookmanager->resPrint;
1155  }
1156 
1157  return $result;
1158  }
1159 
1166  public function getLibStatut($mode = 0)
1167  {
1168  return $this->LibStatut($this->status, $mode);
1169  }
1170 
1171  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1179  public function LibStatut($status, $mode = 0)
1180  {
1181  // phpcs:enable
1182  if (empty($this->labelStatus)) {
1183  global $langs;
1184  //$langs->load("mrp");
1185  $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1186  $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Enabled');
1187  $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Disabled');
1188  }
1189 
1190  $statusType = 'status'.$status;
1191  if ($status == self::STATUS_VALIDATED) {
1192  $statusType = 'status4';
1193  }
1194  if ($status == self::STATUS_CANCELED) {
1195  $statusType = 'status6';
1196  }
1197 
1198  return dolGetStatus($this->labelStatus[$status], $this->labelStatus[$status], '', $statusType, $mode);
1199  }
1200 
1207  public function info($id)
1208  {
1209  $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1210  $sql .= ' fk_user_creat, fk_user_modif';
1211  $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1212  $sql .= ' WHERE t.rowid = '.((int) $id);
1213  $result = $this->db->query($sql);
1214  if ($result) {
1215  if ($this->db->num_rows($result)) {
1216  $obj = $this->db->fetch_object($result);
1217  $this->id = $obj->rowid;
1218 
1219  $this->user_creation_id = $obj->fk_user_creat;
1220  $this->user_modification_id = $obj->fk_user_modif;
1221  $this->date_creation = $this->db->jdate($obj->datec);
1222  $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1223  }
1224 
1225  $this->db->free($result);
1226  } else {
1227  dol_print_error($this->db);
1228  }
1229  }
1230 
1236  public function getLinesArray()
1237  {
1238  $this->lines = array();
1239 
1240  $objectline = new BOMLine($this->db);
1241  $result = $objectline->fetchAll('ASC', 'position', 0, 0, array('customsql'=>'fk_bom = '.((int) $this->id)));
1242 
1243  if (is_numeric($result)) {
1244  $this->error = $objectline->error;
1245  $this->errors = $objectline->errors;
1246  return $result;
1247  } else {
1248  $this->lines = $result;
1249  return $this->lines;
1250  }
1251  }
1252 
1264  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
1265  {
1266  global $conf, $langs;
1267 
1268  $langs->load("mrp");
1269  $outputlangs->load("products");
1270 
1271  if (!dol_strlen($modele)) {
1272  $modele = '';
1273 
1274  if ($this->model_pdf) {
1275  $modele = $this->model_pdf;
1276  } elseif (!empty($conf->global->BOM_ADDON_PDF)) {
1277  $modele = $conf->global->BOM_ADDON_PDF;
1278  }
1279  }
1280 
1281  $modelpath = "core/modules/bom/doc/";
1282  if (!empty($modele)) {
1283  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
1284  } else {
1285  return 0;
1286  }
1287  }
1288 
1295  public function initAsSpecimen()
1296  {
1297  $this->initAsSpecimenCommon();
1298  $this->ref = 'BOM-123';
1299  $this->date = $this->date_creation;
1300  }
1301 
1302 
1309  public function doScheduledJob()
1310  {
1311  global $conf, $langs;
1312 
1313  //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
1314 
1315  $error = 0;
1316  $this->output = '';
1317  $this->error = '';
1318 
1319  dol_syslog(__METHOD__, LOG_DEBUG);
1320 
1321  $now = dol_now();
1322 
1323  $this->db->begin();
1324 
1325  // ...
1326 
1327  $this->db->commit();
1328 
1329  return $error;
1330  }
1331 
1338  public function calculateCosts()
1339  {
1340  global $conf, $hookmanager;
1341 
1342  include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1343  $this->unit_cost = 0;
1344  $this->total_cost = 0;
1345 
1346  $parameters=array();
1347  $reshook = $hookmanager->executeHooks('calculateCostsBom', $parameters, $this); // Note that $action and $object may have been modified by hook
1348 
1349  if ($reshook > 0) {
1350  return $hookmanager->resPrint;
1351  }
1352 
1353  if (is_array($this->lines) && count($this->lines)) {
1354  require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1355  $productFournisseur = new ProductFournisseur($this->db);
1356  $tmpproduct = new Product($this->db);
1357 
1358  foreach ($this->lines as &$line) {
1359  $tmpproduct->cost_price = 0;
1360  $tmpproduct->pmp = 0;
1361  $result = $tmpproduct->fetch($line->fk_product, '', '', '', 0, 1, 1); // We discard selling price and language loading
1362 
1363  if ($tmpproduct->type == $tmpproduct::TYPE_PRODUCT) {
1364  if (empty($line->fk_bom_child)) {
1365  if ($result < 0) {
1366  $this->error = $tmpproduct->error;
1367  return -1;
1368  }
1369  $line->unit_cost = price2num((!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp);
1370  if (empty($line->unit_cost)) {
1371  if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
1372  if ($productFournisseur->fourn_remise_percent != "0") {
1373  $line->unit_cost = $productFournisseur->fourn_unitprice_with_discount;
1374  } else {
1375  $line->unit_cost = $productFournisseur->fourn_unitprice;
1376  }
1377  }
1378  }
1379 
1380  $line->total_cost = price2num($line->qty * $line->unit_cost, 'MT');
1381 
1382  $this->total_cost += $line->total_cost;
1383  } else {
1384  $bom_child = new BOM($this->db);
1385  $res = $bom_child->fetch($line->fk_bom_child);
1386  if ($res > 0) {
1387  $bom_child->calculateCosts();
1388  $line->childBom[] = $bom_child;
1389  $this->total_cost += price2num($bom_child->total_cost * $line->qty, 'MT');
1390  $this->total_cost += $line->total_cost;
1391  } else {
1392  $this->error = $bom_child->error;
1393  return -2;
1394  }
1395  }
1396  } else {
1397  // Convert qty of line into hours
1398  $unitforline = measuringUnitString($line->fk_unit, '', '', 1);
1399  $qtyhourforline = convertDurationtoHour($line->qty, $unitforline);
1400 
1401  if (isModEnabled('workstation') && !empty($tmpproduct->fk_default_workstation)) {
1402  $workstation = new Workstation($this->db);
1403  $res = $workstation->fetch($tmpproduct->fk_default_workstation);
1404 
1405  if ($res > 0) $line->total_cost = price2num($qtyhourforline * ($workstation->thm_operator_estimated + $workstation->thm_machine_estimated), 'MT');
1406  else {
1407  $this->error = $workstation->error;
1408  return -3;
1409  }
1410  } else {
1411  $defaultdurationofservice = $tmpproduct->duration;
1412  $reg = array();
1413  $qtyhourservice = 0;
1414  if (preg_match('/^(\d+)([a-z]+)$/', $defaultdurationofservice, $reg)) {
1415  $qtyhourservice = convertDurationtoHour($reg[1], $reg[2]);
1416  }
1417 
1418  if ($qtyhourservice) {
1419  $line->total_cost = price2num($qtyhourforline / $qtyhourservice * $tmpproduct->cost_price, 'MT');
1420  } else {
1421  $line->total_cost = price2num($line->qty * $tmpproduct->cost_price, 'MT');
1422  }
1423  }
1424 
1425  $this->total_cost += $line->total_cost;
1426  }
1427  }
1428 
1429  $this->total_cost = price2num($this->total_cost, 'MT');
1430 
1431  if ($this->qty > 0) {
1432  $this->unit_cost = price2num($this->total_cost / $this->qty, 'MU');
1433  } elseif ($this->qty < 0) {
1434  $this->unit_cost = price2num($this->total_cost * $this->qty, 'MU');
1435  }
1436  }
1437  }
1438 
1447  public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
1448  {
1449  $tables = array(
1450  'bom_bomline'
1451  );
1452 
1453  return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
1454  }
1455 
1463  public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
1464  {
1465  if (!empty($this->lines)) {
1466  foreach ($this->lines as $line) {
1467  if (!empty($line->childBom)) {
1468  foreach ($line->childBom as $childBom) $childBom->getNetNeeds($TNetNeeds, $line->qty*$qty);
1469  } else {
1470  if (empty($TNetNeeds[$line->fk_product])) {
1471  $TNetNeeds[$line->fk_product] = 0;
1472  }
1473  $TNetNeeds[$line->fk_product] += $line->qty*$qty;
1474  }
1475  }
1476  }
1477  }
1478 
1487  public function getNetNeedsTree(&$TNetNeeds = array(), $qty = 0, $level = 0)
1488  {
1489  if (!empty($this->lines)) {
1490  foreach ($this->lines as $line) {
1491  if (!empty($line->childBom)) {
1492  foreach ($line->childBom as $childBom) {
1493  $TNetNeeds[$childBom->id]['bom'] = $childBom;
1494  $TNetNeeds[$childBom->id]['parentid'] = $this->id;
1495  $TNetNeeds[$childBom->id]['qty'] = $line->qty*$qty;
1496  $TNetNeeds[$childBom->id]['level'] = $level;
1497  $childBom->getNetNeedsTree($TNetNeeds, $line->qty*$qty, $level+1);
1498  }
1499  } else {
1500  $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
1501  $TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
1502  }
1503  }
1504  }
1505  }
1506 
1515  public function getParentBomTreeRecursive(&$TParentBom, $bom_id = '', $level = 1)
1516  {
1517 
1518  // Protection against infinite loop
1519  if ($level > 1000) {
1520  return;
1521  }
1522 
1523  if (empty($bom_id)) $bom_id=$this->id;
1524 
1525  $sql = 'SELECT l.fk_bom, b.label
1526  FROM '.MAIN_DB_PREFIX.'bom_bomline l
1527  INNER JOIN '.MAIN_DB_PREFIX.$this->table_element.' b ON b.rowid = l.fk_bom
1528  WHERE fk_bom_child = '.((int) $bom_id);
1529 
1530  $resql = $this->db->query($sql);
1531  if (!empty($resql)) {
1532  while ($res = $this->db->fetch_object($resql)) {
1533  $TParentBom[$res->fk_bom] = $res->fk_bom;
1534  $this->getParentBomTreeRecursive($TParentBom, $res->fk_bom, $level+1);
1535  }
1536  }
1537  }
1538 }
1539 
1540 
1545 {
1549  public $element = 'bomline';
1550 
1554  public $table_element = 'bom_bomline';
1555 
1559  public $ismultientitymanaged = 0;
1560 
1564  public $isextrafieldmanaged = 1;
1565 
1569  public $picto = 'bomline';
1570 
1571 
1591  // BEGIN MODULEBUILDER PROPERTIES
1595  public $fields = array(
1596  'rowid' => array('type'=>'integer', 'label'=>'LineID', 'enabled'=>1, 'visible'=>-1, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>"Id",),
1597  'fk_bom' => array('type'=>'integer:BillOfMaterials:societe/class/bom.class.php', 'label'=>'BillOfMaterials', 'enabled'=>1, 'visible'=>1, 'position'=>10, 'notnull'=>1, 'index'=>1,),
1598  'fk_product' => array('type'=>'integer:Product:product/class/product.class.php', 'label'=>'Product', 'enabled'=>1, 'visible'=>1, 'position'=>20, 'notnull'=>1, 'index'=>1,),
1599  'fk_bom_child' => array('type'=>'integer:BOM:bom/class/bom.class.php', 'label'=>'BillOfMaterials', 'enabled'=>1, 'visible'=>-1, 'position'=>40, 'notnull'=>-1,),
1600  'description' => array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>-1, 'position'=>60, 'notnull'=>-1,),
1601  'qty' => array('type'=>'double(24,8)', 'label'=>'Quantity', 'enabled'=>1, 'visible'=>1, 'position'=>100, 'notnull'=>1, 'isameasure'=>'1',),
1602  'qty_frozen' => array('type'=>'smallint', 'label'=>'QuantityFrozen', 'enabled'=>1, 'visible'=>1, 'default'=>0, 'position'=>105, 'css'=>'maxwidth50imp', 'help'=>'QuantityConsumedInvariable'),
1603  'disable_stock_change' => array('type'=>'smallint', 'label'=>'DisableStockChange', 'enabled'=>1, 'visible'=>1, 'default'=>0, 'position'=>108, 'css'=>'maxwidth50imp', 'help'=>'DisableStockChangeHelp'),
1604  'efficiency' => array('type'=>'double(24,8)', 'label'=>'ManufacturingEfficiency', 'enabled'=>1, 'visible'=>0, 'default'=>1, 'position'=>110, 'notnull'=>1, 'css'=>'maxwidth50imp', 'help'=>'ValueOfEfficiencyConsumedMeans'),
1605  'fk_unit' => array('type'=>'integer', 'label'=>'Unit', 'enabled'=>1, 'visible'=>1, 'position'=>120, 'notnull'=>-1,),
1606  'position' => array('type'=>'integer', 'label'=>'Rank', 'enabled'=>1, 'visible'=>0, 'default'=>0, 'position'=>200, 'notnull'=>1,),
1607  'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>1000, 'notnull'=>-1,),
1608  );
1609 
1613  public $rowid;
1614 
1618  public $fk_bom;
1619 
1623  public $fk_product;
1624 
1628  public $fk_bom_child;
1629 
1633  public $description;
1634  public $qty;
1635 
1639  public $qty_frozen;
1640  public $disable_stock_change;
1641  public $efficiency;
1642 
1646  public $position;
1647 
1651  public $import_key;
1652  // END MODULEBUILDER PROPERTIES
1653 
1657  public $total_cost = 0;
1658 
1662  public $unit_cost = 0;
1663 
1664 
1668  public $childBom = array();
1669 
1670 
1676  public function __construct(DoliDB $db)
1677  {
1678  global $conf, $langs;
1679 
1680  $this->db = $db;
1681 
1682  if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && isset($this->fields['rowid'])) {
1683  $this->fields['rowid']['visible'] = 0;
1684  }
1685  if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
1686  $this->fields['entity']['enabled'] = 0;
1687  }
1688 
1689  // Unset fields that are disabled
1690  foreach ($this->fields as $key => $val) {
1691  if (isset($val['enabled']) && empty($val['enabled'])) {
1692  unset($this->fields[$key]);
1693  }
1694  }
1695 
1696  // Translate some data of arrayofkeyval
1697  foreach ($this->fields as $key => $val) {
1698  if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
1699  foreach ($val['arrayofkeyval'] as $key2 => $val2) {
1700  $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
1701  }
1702  }
1703  }
1704  }
1705 
1713  public function create(User $user, $notrigger = false)
1714  {
1715  if ($this->efficiency < 0 || $this->efficiency > 1) {
1716  $this->efficiency = 1;
1717  }
1718 
1719  return $this->createCommon($user, $notrigger);
1720  }
1721 
1729  public function fetch($id, $ref = null)
1730  {
1731  $result = $this->fetchCommon($id, $ref);
1732  //if ($result > 0 && !empty($this->table_element_line)) $this->fetchLines();
1733  return $result;
1734  }
1735 
1747  public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
1748  {
1749  global $conf;
1750 
1751  dol_syslog(__METHOD__, LOG_DEBUG);
1752 
1753  $records = array();
1754 
1755  $sql = 'SELECT ';
1756  $sql .= $this->getFieldList();
1757  $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1758  if ($this->ismultientitymanaged) {
1759  $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
1760  } else {
1761  $sql .= ' WHERE 1 = 1';
1762  }
1763  // Manage filter
1764  $sqlwhere = array();
1765  if (count($filter) > 0) {
1766  foreach ($filter as $key => $value) {
1767  if ($key == 't.rowid') {
1768  $sqlwhere[] = $key." = ".((int) $value);
1769  } elseif (strpos($key, 'date') !== false) {
1770  $sqlwhere[] = $key." = '".$this->db->idate($value)."'";
1771  } elseif ($key == 'customsql') {
1772  $sqlwhere[] = $value;
1773  } else {
1774  $sqlwhere[] = $key." LIKE '%".$this->db->escape($value)."%'";
1775  }
1776  }
1777  }
1778  if (count($sqlwhere) > 0) {
1779  $sql .= ' AND ('.implode(' '.$this->db->escape($filtermode).' ', $sqlwhere).')';
1780  }
1781 
1782  if (!empty($sortfield)) {
1783  $sql .= $this->db->order($sortfield, $sortorder);
1784  }
1785  if (!empty($limit)) {
1786  $sql .= $this->db->plimit($limit, $offset);
1787  }
1788 
1789  $resql = $this->db->query($sql);
1790  if ($resql) {
1791  $num = $this->db->num_rows($resql);
1792 
1793  while ($obj = $this->db->fetch_object($resql)) {
1794  $record = new self($this->db);
1795  $record->setVarsFromFetchObj($obj);
1796 
1797  $records[$record->id] = $record;
1798  }
1799  $this->db->free($resql);
1800 
1801  return $records;
1802  } else {
1803  $this->errors[] = 'Error '.$this->db->lasterror();
1804  dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
1805 
1806  return -1;
1807  }
1808  }
1809 
1817  public function update(User $user, $notrigger = false)
1818  {
1819  if ($this->efficiency < 0 || $this->efficiency > 1) {
1820  $this->efficiency = 1;
1821  }
1822 
1823  return $this->updateCommon($user, $notrigger);
1824  }
1825 
1833  public function delete(User $user, $notrigger = false)
1834  {
1835  return $this->deleteCommon($user, $notrigger);
1836  //return $this->deleteCommon($user, $notrigger, 1);
1837  }
1838 
1849  public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1850  {
1851  global $db, $conf, $langs, $hookmanager;
1852 
1853  if (!empty($conf->dol_no_mouse_hover)) {
1854  $notooltip = 1; // Force disable tooltips
1855  }
1856 
1857  $result = '';
1858 
1859  $label = '<u>'.$langs->trans("BillOfMaterialsLine").'</u>';
1860  $label .= '<br>';
1861  $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref;
1862 
1863  $url = DOL_URL_ROOT.'/bom/bomline_card.php?id='.$this->id;
1864 
1865  if ($option != 'nolink') {
1866  // Add param to save lastsearch_values or not
1867  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1868  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1869  $add_save_lastsearch_values = 1;
1870  }
1871  if ($add_save_lastsearch_values) {
1872  $url .= '&save_lastsearch_values=1';
1873  }
1874  }
1875 
1876  $linkclose = '';
1877  if (empty($notooltip)) {
1878  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1879  $label = $langs->trans("ShowBillOfMaterialsLine");
1880  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1881  }
1882  $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
1883  $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
1884  } else {
1885  $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1886  }
1887 
1888  $linkstart = '<a href="'.$url.'"';
1889  $linkstart .= $linkclose.'>';
1890  $linkend = '</a>';
1891 
1892  $result .= $linkstart;
1893  if ($withpicto) {
1894  $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);
1895  }
1896  if ($withpicto != 2) {
1897  $result .= $this->ref;
1898  }
1899  $result .= $linkend;
1900  //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1901 
1902  global $action, $hookmanager;
1903  $hookmanager->initHooks(array('bomlinedao'));
1904  $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
1905  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1906  if ($reshook > 0) {
1907  $result = $hookmanager->resPrint;
1908  } else {
1909  $result .= $hookmanager->resPrint;
1910  }
1911 
1912  return $result;
1913  }
1914 
1921  public function getLibStatut($mode = 0)
1922  {
1923  return $this->LibStatut($this->status, $mode);
1924  }
1925 
1926  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1934  public function LibStatut($status, $mode = 0)
1935  {
1936  // phpcs:enable
1937  return '';
1938  }
1939 
1946  public function info($id)
1947  {
1948  $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1949  $sql .= ' fk_user_creat, fk_user_modif';
1950  $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1951  $sql .= ' WHERE t.rowid = '.((int) $id);
1952  $result = $this->db->query($sql);
1953  if ($result) {
1954  if ($this->db->num_rows($result)) {
1955  $obj = $this->db->fetch_object($result);
1956  $this->id = $obj->rowid;
1957  $this->user_creation_id = $obj->fk_user_creat;
1958  $this->user_modification_id = $obj->fk_user_modif;
1959  $this->date_creation = $this->db->jdate($obj->datec);
1960  $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1961  }
1962  $this->db->free($result);
1963  } else {
1964  dol_print_error($this->db);
1965  }
1966  }
1967 
1974  public function initAsSpecimen()
1975  {
1976  $this->initAsSpecimenCommon();
1977  }
1978 }
$object ref
Definition: info.php:78
Class for BOM.
Definition: bom.class.php:37
static replaceProduct(DoliDB $db, $origin_id, $dest_id)
Function used to replace a product id with another one.
Definition: bom.class.php:1447
fetchLines()
Load object lines in memory from the database.
Definition: bom.class.php:397
__construct(DoliDB $db)
Constructor.
Definition: bom.class.php:236
calculateCosts()
BOM costs calculation based on cost_price or pmp of each BOM line.
Definition: bom.class.php:1338
info($id)
Load the info information in the object.
Definition: bom.class.php:1207
getLibStatut($mode=0)
Return label of the status.
Definition: bom.class.php:1166
initAsSpecimen()
Initialise object with example values Id must be 0 if object instance is a specimen.
Definition: bom.class.php:1295
validate($user, $notrigger=0)
Validate bom.
Definition: bom.class.php:884
reopen($user, $notrigger=0)
Set cancel status.
Definition: bom.class.php:1051
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:468
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)
Add an BOM line into database (linked to BOM)
Definition: bom.class.php:575
cancel($user, $notrigger=0)
Set cancel status.
Definition: bom.class.php:1027
create(User $user, $notrigger=false)
Create object into database.
Definition: bom.class.php:273
LibStatut($status, $mode=0)
Return the status.
Definition: bom.class.php:1179
doScheduledJob()
Action executed by scheduler CAN BE A CRON TASK.
Definition: bom.class.php:1309
fetchLinesbytypeproduct($typeproduct=0)
Load object lines in memory from the database by type of product.
Definition: bom.class.php:412
createFromClone(User $user, $fromid)
Clone an object into another one.
Definition: bom.class.php:289
fetch($id, $ref=null)
Load object in memory from the database.
Definition: bom.class.php:380
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:1079
getNetNeeds(&$TNetNeeds=array(), $qty=0)
Get Net needs by product.
Definition: bom.class.php:1463
getNextNumRef($prod)
Returns the reference to the following non used BOM depending on the active numbering module defined ...
Definition: bom.class.php:836
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
Definition: bom.class.php:1264
getLinesArray()
Create an array of lines.
Definition: bom.class.php:1236
setDraft($user, $notrigger=0)
Set draft status.
Definition: bom.class.php:1003
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:681
update(User $user, $notrigger=false)
Update object into database.
Definition: bom.class.php:538
deleteLine(User $user, $idline, $notrigger=false)
Delete a line of object in database.
Definition: bom.class.php:788
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:1515
getNetNeedsTree(&$TNetNeeds=array(), $qty=0, $level=0)
Get Net needs Tree by product or bom.
Definition: bom.class.php:1487
Class for BOMLine.
Definition: bom.class.php:1545
create(User $user, $notrigger=false)
Create object into database.
Definition: bom.class.php:1713
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:1747
fetch($id, $ref=null)
Load object in memory from the database.
Definition: bom.class.php:1729
getLibStatut($mode=0)
Return label of the status.
Definition: bom.class.php:1921
update(User $user, $notrigger=false)
Update object into database.
Definition: bom.class.php:1817
__construct(DoliDB $db)
Constructor.
Definition: bom.class.php:1676
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:1849
initAsSpecimen()
Initialise object with example values Id must be 0 if object instance is a specimen.
Definition: bom.class.php:1974
info($id)
Load the info information in the object.
Definition: bom.class.php:1946
LibStatut($status, $mode=0)
Return the status.
Definition: bom.class.php:1934
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.
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.
static commonReplaceProduct(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product id with another one.
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.
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:47
Class for Workstation.
if(isModEnabled('facture') &&!empty($user->rights->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') &&!empty($user->rights->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)) $resql
Social contributions to pay.
Definition: index.php:745
convertDurationtoHour($duration_value, $duration_unit)
Convert duration to hour.
Definition: date.lib.php:330
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:61
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.
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.
$conf db
API class for accounts.
Definition: inc.php:41