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