dolibarr 19.0.3
bom.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2019 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2023 Benjamin Falière <benjamin.faliere@altairis.fr>
4 * Copyright (C) 2023 Charlene Benke <charlene@patas-monkey.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
26// Put here all includes required by your class file
27require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
28require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
29require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
30
31if (isModEnabled('workstation')) {
32 require_once DOL_DOCUMENT_ROOT.'/workstation/class/workstation.class.php';
33}
34
35//require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
36//require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
37
38
42class BOM extends CommonObject
43{
47 public $module = 'bom';
48
52 public $element = 'bom';
53
57 public $table_element = 'bom_bom';
58
62 public $ismultientitymanaged = 1;
63
67 public $isextrafieldmanaged = 1;
68
72 public $picto = 'bom';
73
77 public $product;
78
79 const STATUS_DRAFT = 0;
80 const STATUS_VALIDATED = 1;
81 const STATUS_CANCELED = 9;
82
83
110 // BEGIN MODULEBUILDER PROPERTIES
114 public $fields = array(
115 'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>"Id",),
116 'entity' => array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'notnull'=> 1, 'default'=>1, 'index'=>1, 'position'=>5),
117 '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'),
118 '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'),
119 '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'),
120 //'bomtype' => array('type'=>'integer', 'label'=>'Type', 'enabled'=>1, 'visible'=>-1, 'position'=>32, 'notnull'=>1, 'default'=>'0', 'arrayofkeyval'=>array(0=>'Manufacturing')),
121 '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'),
122 'description' => array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>-1, 'position'=>60, 'notnull'=>-1,),
123 'qty' => array('type'=>'real', 'label'=>'Quantity', 'enabled'=>1, 'visible'=>1, 'default'=>1, 'position'=>55, 'notnull'=>1, 'isameasure'=>'1', 'css'=>'maxwidth50imp right'),
124 //'efficiency' => array('type'=>'real', 'label'=>'ManufacturingEfficiency', 'enabled'=>1, 'visible'=>-1, 'default'=>1, 'position'=>100, 'notnull'=>0, 'css'=>'maxwidth50imp', 'help'=>'ValueOfMeansLossForProductProduced'),
125 'duration' => array('type'=>'duration', 'label'=>'EstimatedDuration', 'enabled'=>1, 'visible'=>-1, 'position'=>101, 'notnull'=>-1, 'css'=>'maxwidth50imp', 'help'=>'EstimatedDurationDesc'),
126 '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'),
127 'note_public' => array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>-2, 'position'=>161, 'notnull'=>-1,),
128 'note_private' => array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>-2, 'position'=>162, 'notnull'=>-1,),
129 'date_creation' => array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>300, 'notnull'=>1,),
130 'tms' => array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-2, 'position'=>501, 'notnull'=>1,),
131 'date_valid' => array('type'=>'datetime', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-2, 'position'=>502, 'notnull'=>0,),
132 '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'),
133 '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'),
134 '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'),
135 'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>1000, 'notnull'=>-1,),
136 'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>1, 'visible'=>0, 'position'=>1010),
137 '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')),
138 );
139
143 public $rowid;
144
148 public $ref;
149
153 public $label;
154
158 public $bomtype;
159
163 public $description;
164
168 public $date_creation;
169
173 public $date_valid;
174
175 public $tms;
176
180 public $fk_user_creat;
181
185 public $fk_user_modif;
186
190 public $fk_user_valid;
191
195 public $fk_warehouse;
196
200 public $import_key;
201
205 public $status;
206
210 public $fk_product;
211 public $qty;
212 public $duration;
213 public $efficiency;
214 // END MODULEBUILDER PROPERTIES
215
216
217 // If this object has a subtable with lines
218
222 public $table_element_line = 'bom_bomline';
223
227 public $fk_element = 'fk_bom';
228
232 public $class_element_line = 'BOMLine';
233
234 // /**
235 // * @var array List of child tables. To test if we can delete object.
236 // */
237 // protected $childtables=array();
238
242 protected $childtablesoncascade = array('bom_bomline');
243
247 public $lines = array();
248
252 public $total_cost = 0;
253
257 public $unit_cost = 0;
258
259
265 public function __construct(DoliDB $db)
266 {
267 global $conf, $langs;
268
269 $this->db = $db;
270
271 if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid'])) {
272 $this->fields['rowid']['visible'] = 0;
273 }
274 if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
275 $this->fields['entity']['enabled'] = 0;
276 }
277
278 // Unset fields that are disabled
279 foreach ($this->fields as $key => $val) {
280 if (isset($val['enabled']) && empty($val['enabled'])) {
281 unset($this->fields[$key]);
282 }
283 }
284
285 // Translate some data of arrayofkeyval
286 foreach ($this->fields as $key => $val) {
287 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
288 foreach ($val['arrayofkeyval'] as $key2 => $val2) {
289 $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
290 }
291 }
292 }
293 }
294
302 public function create(User $user, $notrigger = false)
303 {
304 if ($this->efficiency <= 0 || $this->efficiency > 1) {
305 $this->efficiency = 1;
306 }
307
308 return $this->createCommon($user, $notrigger);
309 }
310
318 public function createFromClone(User $user, $fromid)
319 {
320 global $langs, $hookmanager, $extrafields;
321 $error = 0;
322
323 dol_syslog(__METHOD__, LOG_DEBUG);
324
325 $object = new self($this->db);
326
327 $this->db->begin();
328
329 // Load source object
330 $result = $object->fetchCommon($fromid);
331 if ($result > 0 && !empty($object->table_element_line)) {
332 $object->fetchLines();
333 }
334
335 // Get lines so they will be clone
336 //foreach ($object->lines as $line)
337 // $line->fetch_optionals();
338
339 // Reset some properties
340 unset($object->id);
341 unset($object->fk_user_creat);
342 unset($object->import_key);
343
344 // Clear fields
345 $object->ref = empty($this->fields['ref']['default']) ? $langs->trans("copy_of_").$object->ref : $this->fields['ref']['default'];
346 $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf")." ".$object->label : $this->fields['label']['default'];
347 $object->status = self::STATUS_DRAFT;
348 // ...
349 // Clear extrafields that are unique
350 if (is_array($object->array_options) && count($object->array_options) > 0) {
351 $extrafields->fetch_name_optionals_label($object->table_element);
352 foreach ($object->array_options as $key => $option) {
353 $shortkey = preg_replace('/options_/', '', $key);
354 if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) {
355 //var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
356 unset($object->array_options[$key]);
357 }
358 }
359 }
360
361 // Create clone
362 $object->context['createfromclone'] = 'createfromclone';
363 $result = $object->createCommon($user);
364 if ($result < 0) {
365 $error++;
366 $this->error = $object->error;
367 $this->errors = $object->errors;
368 }
369
370 if (!$error) {
371 // copy internal contacts
372 if ($this->copy_linked_contact($object, 'internal') < 0) {
373 $error++;
374 }
375 }
376
377 if (!$error) {
378 // copy external contacts if same company
379 if (property_exists($this, 'socid') && $this->socid == $object->socid) {
380 if ($this->copy_linked_contact($object, 'external') < 0) {
381 $error++;
382 }
383 }
384 }
385
386 // If there is lines, create lines too
387
388
389
390 unset($object->context['createfromclone']);
391
392 // End
393 if (!$error) {
394 $this->db->commit();
395 return $object;
396 } else {
397 $this->db->rollback();
398 return -1;
399 }
400 }
401
409 public function fetch($id, $ref = null)
410 {
411 $result = $this->fetchCommon($id, $ref);
412
413 if ($result > 0 && !empty($this->table_element_line)) {
414 $this->fetchLines();
415 }
416 //$this->calculateCosts(); // This consume a high number of subrequests. Do not call it into fetch but when you need it.
417
418 return $result;
419 }
420
426 public function fetchLines()
427 {
428 $this->lines = array();
429
430 $result = $this->fetchLinesCommon();
431 return $result;
432 }
433
441 public function fetchLinesbytypeproduct($typeproduct = 0)
442 {
443 $this->lines = array();
444
445 $objectlineclassname = get_class($this).'Line';
446 if (!class_exists($objectlineclassname)) {
447 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
448 return -1;
449 }
450
451 $objectline = new $objectlineclassname($this->db);
452
453 $sql = "SELECT ".$objectline->getFieldList('l');
454 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
455 $sql .= " LEFT JOIN ".$this->db->prefix()."product as p ON p.rowid = l.fk_product";
456 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
457 $sql .= " AND p.fk_product_type = ". ((int) $typeproduct);
458 if (isset($objectline->fields['position'])) {
459 $sql .= $this->db->order('position', 'ASC');
460 }
461
462 $resql = $this->db->query($sql);
463 if ($resql) {
464 $num_rows = $this->db->num_rows($resql);
465 $i = 0;
466 while ($i < $num_rows) {
467 $obj = $this->db->fetch_object($resql);
468 if ($obj) {
469 $newline = new $objectlineclassname($this->db);
470 $newline->setVarsFromFetchObj($obj);
471
472 $this->lines[] = $newline;
473 }
474 $i++;
475 }
476
477 return $num_rows;
478 } else {
479 $this->error = $this->db->lasterror();
480 $this->errors[] = $this->error;
481 return -1;
482 }
483 }
484
485
497 public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
498 {
499 global $conf;
500
501 dol_syslog(__METHOD__, LOG_DEBUG);
502
503 $records = array();
504
505 $sql = 'SELECT ';
506 $sql .= $this->getFieldList();
507 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
508 if ($this->ismultientitymanaged) {
509 $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
510 } else {
511 $sql .= ' WHERE 1 = 1';
512 }
513 // Manage filter
514 $sqlwhere = array();
515 if (count($filter) > 0) {
516 foreach ($filter as $key => $value) {
517 if ($key == 't.rowid') {
518 $sqlwhere[] = $key." = ".((int) $value);
519 } elseif (strpos($key, 'date') !== false) {
520 $sqlwhere[] = $key." = '".$this->db->idate($value)."'";
521 } elseif ($key == 'customsql') {
522 $sqlwhere[] = $value;
523 } else {
524 $sqlwhere[] = $key." LIKE '%".$this->db->escape($value)."%'";
525 }
526 }
527 }
528 if (count($sqlwhere) > 0) {
529 $sql .= " AND (".implode(" ".$filtermode." ", $sqlwhere).")";
530 }
531
532 if (!empty($sortfield)) {
533 $sql .= $this->db->order($sortfield, $sortorder);
534 }
535 if (!empty($limit)) {
536 $sql .= $this->db->plimit($limit, $offset);
537 }
538
539 $resql = $this->db->query($sql);
540 if ($resql) {
541 $num = $this->db->num_rows($resql);
542
543 while ($obj = $this->db->fetch_object($resql)) {
544 $record = new self($this->db);
545 $record->setVarsFromFetchObj($obj);
546
547 $records[$record->id] = $record;
548 }
549 $this->db->free($resql);
550
551 return $records;
552 } else {
553 $this->errors[] = 'Error '.$this->db->lasterror();
554 dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
555
556 return -1;
557 }
558 }
559
567 public function update(User $user, $notrigger = false)
568 {
569 if ($this->efficiency <= 0 || $this->efficiency > 1) {
570 $this->efficiency = 1;
571 }
572
573 return $this->updateCommon($user, $notrigger);
574 }
575
583 public function delete(User $user, $notrigger = false)
584 {
585 return $this->deleteCommon($user, $notrigger);
586 //return $this->deleteCommon($user, $notrigger, 1);
587 }
588
605 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 = array(), $fk_default_workstation = null)
606 {
607 global $mysoc, $conf, $langs, $user;
608
609 $logtext = "::addLine bomid=$this->id, qty=$qty, fk_product=$fk_product, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
610 $logtext .= ", fk_bom_child=$fk_bom_child, import_key=$import_key";
611 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
612
613 if ($this->statut == self::STATUS_DRAFT) {
614 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
615
616 // Clean parameters
617 if (empty($qty)) {
618 $qty = 0;
619 }
620 if (empty($qty_frozen)) {
621 $qty_frozen = 0;
622 }
623 if (empty($disable_stock_change)) {
624 $disable_stock_change = 0;
625 }
626 if (empty($efficiency)) {
627 $efficiency = 1.0;
628 }
629 if (empty($fk_bom_child)) {
630 $fk_bom_child = null;
631 }
632 if (empty($import_key)) {
633 $import_key = null;
634 }
635 if (empty($position)) {
636 $position = -1;
637 }
638
639 $qty = price2num($qty);
640 $efficiency = price2num($efficiency);
641 $position = price2num($position);
642
643 $this->db->begin();
644
645 // Rank to use
646 $rangMax = $this->line_max();
647 $rankToUse = $position;
648 if ($rankToUse <= 0 or $rankToUse > $rangMax) { // New line after existing lines
649 $rankToUse = $rangMax + 1;
650 } else { // New line between the existing lines
651 foreach ($this->lines as $bl) {
652 if ($bl->position >= $rankToUse) {
653 $bl->position++;
654 $bl->update($user);
655 }
656 }
657 }
658
659 // Insert line
660 $line = new BOMLine($this->db);
661
662 $line->context = $this->context;
663
664 $line->fk_bom = $this->id;
665 $line->fk_product = $fk_product;
666 $line->qty = $qty;
667 $line->qty_frozen = $qty_frozen;
668 $line->disable_stock_change = $disable_stock_change;
669 $line->efficiency = $efficiency;
670 $line->fk_bom_child = $fk_bom_child;
671 $line->import_key = $import_key;
672 $line->position = $rankToUse;
673 $line->fk_unit = $fk_unit;
674 $line->fk_default_workstation = $fk_default_workstation;
675
676 if (is_array($array_options) && count($array_options) > 0) {
677 $line->array_options = $array_options;
678 }
679
680 $result = $line->create($user);
681
682 if ($result > 0) {
683 $this->calculateCosts();
684 $this->db->commit();
685 return $result;
686 } else {
687 $this->setErrorsFromObject($line);
688 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
689 $this->db->rollback();
690 return -2;
691 }
692 } else {
693 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
694 return -3;
695 }
696 }
697
713 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 = array(), $fk_default_workstation = null)
714 {
715 global $mysoc, $conf, $langs, $user;
716
717 $logtext = "::updateLine bomid=$this->id, qty=$qty, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
718 $logtext .= ", import_key=$import_key";
719 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
720
721 if ($this->statut == self::STATUS_DRAFT) {
722 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
723
724 // Clean parameters
725 if (empty($qty)) {
726 $qty = 0;
727 }
728 if (empty($qty_frozen)) {
729 $qty_frozen = 0;
730 }
731 if (empty($disable_stock_change)) {
732 $disable_stock_change = 0;
733 }
734 if (empty($efficiency)) {
735 $efficiency = 1.0;
736 }
737 if (empty($import_key)) {
738 $import_key = null;
739 }
740 if (empty($position)) {
741 $position = -1;
742 }
743
744 $qty = price2num($qty);
745 $efficiency = price2num($efficiency);
746 $position = price2num($position);
747
748 $this->db->begin();
749
750 //Fetch current line from the database and then clone the object and set it in $oldline property
751 $line = new BOMLine($this->db);
752 $line->fetch($rowid);
753 $line->fetch_optionals();
754
755 $staticLine = clone $line;
756 $line->oldcopy = $staticLine;
757 $line->context = $this->context;
758
759 // Rank to use
760 $rankToUse = (int) $position;
761 if ($rankToUse != $line->oldcopy->position) { // check if position have a new value
762 foreach ($this->lines as $bl) {
763 if ($bl->position >= $rankToUse and $bl->position < ($line->oldcopy->position + 1)) { // move rank up
764 $bl->position++;
765 $bl->update($user);
766 }
767 if ($bl->position <= $rankToUse and $bl->position > ($line->oldcopy->position)) { // move rank down
768 $bl->position--;
769 $bl->update($user);
770 }
771 }
772 }
773
774
775 $line->fk_bom = $this->id;
776 $line->qty = $qty;
777 $line->qty_frozen = $qty_frozen;
778 $line->disable_stock_change = $disable_stock_change;
779 $line->efficiency = $efficiency;
780 $line->import_key = $import_key;
781 $line->position = $rankToUse;
782
783
784 if (!empty($fk_unit)) {
785 $line->fk_unit = $fk_unit;
786 }
787
788
789 if (is_array($array_options) && count($array_options) > 0) {
790 // We replace values in this->line->array_options only for entries defined into $array_options
791 foreach ($array_options as $key => $value) {
792 $line->array_options[$key] = $array_options[$key];
793 }
794 }
795 if ($line->fk_default_workstation != $fk_default_workstation) {
796 $line->fk_default_workstation = $fk_default_workstation;
797 }
798
799 $result = $line->update($user);
800
801 if ($result > 0) {
802 $this->calculateCosts();
803 $this->db->commit();
804 return $result;
805 } else {
806 $this->setErrorsFromObject($line);
807 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
808 $this->db->rollback();
809 return -2;
810 }
811 } else {
812 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
813 return -3;
814 }
815 }
816
825 public function deleteLine(User $user, $idline, $notrigger = false)
826 {
827 if ($this->status < 0) {
828 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
829 return -2;
830 }
831
832 $this->db->begin();
833
834 //Fetch current line from the database and then clone the object and set it in $oldline property
835 $line = new BOMLine($this->db);
836 $line->fetch($idline);
837 $line->fetch_optionals();
838
839 $staticLine = clone $line;
840 $line->oldcopy = $staticLine;
841 $line->context = $this->context;
842
843 $result = $line->delete($user, $notrigger);
844
845 //Positions (rank) reordering
846 foreach ($this->lines as $bl) {
847 if ($bl->position > ($line->oldcopy->position)) { // move rank down
848 $bl->position--;
849 $bl->update($user);
850 }
851 }
852
853 if ($result > 0) {
854 $this->calculateCosts();
855 $this->db->commit();
856 return $result;
857 } else {
858 $this->setErrorsFromObject($line);
859 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
860 $this->db->rollback();
861 return -2;
862 }
863 }
864
872 public function getNextNumRef($prod)
873 {
874 global $langs, $conf;
875 $langs->load("mrp");
876
877 if (getDolGlobalString('BOM_ADDON')) {
878 $mybool = false;
879
880 $file = getDolGlobalString('BOM_ADDON') . ".php";
881 $classname = $conf->global->BOM_ADDON;
882
883 // Include file with class
884 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
885 foreach ($dirmodels as $reldir) {
886 $dir = dol_buildpath($reldir."core/modules/bom/");
887
888 // Load file with numbering class (if found)
889 $mybool |= @include_once $dir.$file;
890 }
891
892 if ($mybool === false) {
893 dol_print_error('', "Failed to include file ".$file);
894 return '';
895 }
896
897 $obj = new $classname();
898 $numref = $obj->getNextValue($prod, $this);
899
900 if ($numref != "") {
901 return $numref;
902 } else {
903 $this->error = $obj->error;
904 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
905 return "";
906 }
907 } else {
908 print $langs->trans("Error")." ".$langs->trans("Error_BOM_ADDON_NotDefined");
909 return "";
910 }
911 }
912
920 public function validate($user, $notrigger = 0)
921 {
922 global $conf;
923
924 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
925
926 $error = 0;
927
928 // Protection
929 if ($this->status == self::STATUS_VALIDATED) {
930 dol_syslog(get_class($this)."::validate action abandonned: already validated", LOG_WARNING);
931 return 0;
932 }
933
934 /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->create))
935 || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
936 {
937 $this->error='NotEnoughPermissions';
938 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
939 return -1;
940 }*/
941
942 $now = dol_now();
943
944 $this->db->begin();
945
946 // Define new ref
947 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
948 $this->fetch_product();
949 $num = $this->getNextNumRef($this->product);
950 } else {
951 $num = $this->ref;
952 }
953 $this->newref = dol_sanitizeFileName($num);
954
955 // Validate
956 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
957 $sql .= " SET ref = '".$this->db->escape($num)."',";
958 $sql .= " status = ".self::STATUS_VALIDATED.",";
959 $sql .= " date_valid='".$this->db->idate($now)."',";
960 $sql .= " fk_user_valid = ".((int) $user->id);
961 $sql .= " WHERE rowid = ".((int) $this->id);
962
963 dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
964 $resql = $this->db->query($sql);
965 if (!$resql) {
966 dol_print_error($this->db);
967 $this->error = $this->db->lasterror();
968 $error++;
969 }
970
971 if (!$error && !$notrigger) {
972 // Call trigger
973 $result = $this->call_trigger('BOM_VALIDATE', $user);
974 if ($result < 0) {
975 $error++;
976 }
977 // End call triggers
978 }
979
980 if (!$error) {
981 $this->oldref = $this->ref;
982
983 // Rename directory if dir was a temporary ref
984 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
985 // Now we rename also files into index
986 $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)."'";
987 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
988 $resql = $this->db->query($sql);
989 if (!$resql) {
990 $error++;
991 $this->error = $this->db->lasterror();
992 }
993 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'bom/".$this->db->escape($this->newref)."'";
994 $sql .= " WHERE filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
995 $resql = $this->db->query($sql);
996 if (!$resql) {
997 $error++;
998 $this->error = $this->db->lasterror();
999 }
1000
1001 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1002 $oldref = dol_sanitizeFileName($this->ref);
1003 $newref = dol_sanitizeFileName($num);
1004 $dirsource = $conf->bom->dir_output.'/'.$oldref;
1005 $dirdest = $conf->bom->dir_output.'/'.$newref;
1006 if (!$error && file_exists($dirsource)) {
1007 dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
1008
1009 if (@rename($dirsource, $dirdest)) {
1010 dol_syslog("Rename ok");
1011 // Rename docs starting with $oldref with $newref
1012 $listoffiles = dol_dir_list($conf->bom->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
1013 foreach ($listoffiles as $fileentry) {
1014 $dirsource = $fileentry['name'];
1015 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
1016 $dirsource = $fileentry['path'].'/'.$dirsource;
1017 $dirdest = $fileentry['path'].'/'.$dirdest;
1018 @rename($dirsource, $dirdest);
1019 }
1020 }
1021 }
1022 }
1023 }
1024
1025 // Set new ref and current status
1026 if (!$error) {
1027 $this->ref = $num;
1028 $this->status = self::STATUS_VALIDATED;
1029 }
1030
1031 if (!$error) {
1032 $this->db->commit();
1033 return 1;
1034 } else {
1035 $this->db->rollback();
1036 return -1;
1037 }
1038 }
1039
1047 public function setDraft($user, $notrigger = 0)
1048 {
1049 // Protection
1050 if ($this->status <= self::STATUS_DRAFT) {
1051 return 0;
1052 }
1053
1054 /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->write))
1055 || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
1056 {
1057 $this->error='Permission denied';
1058 return -1;
1059 }*/
1060
1061 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'BOM_UNVALIDATE');
1062 }
1063
1071 public function cancel($user, $notrigger = 0)
1072 {
1073 // Protection
1074 if ($this->status != self::STATUS_VALIDATED) {
1075 return 0;
1076 }
1077
1078 /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->write))
1079 || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
1080 {
1081 $this->error='Permission denied';
1082 return -1;
1083 }*/
1084
1085 return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'BOM_CLOSE');
1086 }
1087
1095 public function reopen($user, $notrigger = 0)
1096 {
1097 // Protection
1098 if ($this->status != self::STATUS_CANCELED) {
1099 return 0;
1100 }
1101
1102 /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->write))
1103 || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
1104 {
1105 $this->error='Permission denied';
1106 return -1;
1107 }*/
1108
1109 return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'BOM_REOPEN');
1110 }
1111
1118 public function getTooltipContentArray($params)
1119 {
1120 global $conf, $langs, $user;
1121
1122 $langs->loadLangs(['product', 'mrp']);
1123
1124 $datas = [];
1125
1126 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1127 return ['optimize' => $langs->trans("ShowBillOfMaterials")];
1128 }
1129 $picto = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("BillOfMaterials").'</u>';
1130 if (isset($this->status)) {
1131 $picto .= ' '.$this->getLibStatut(5);
1132 }
1133 $datas['picto'] = $picto;
1134 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1135 if (isset($this->label)) {
1136 $datas['label'] = '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
1137 }
1138 if (!empty($this->fk_product) && $this->fk_product > 0) {
1139 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1140 $product = new Product($this->db);
1141 $resultFetch = $product->fetch($this->fk_product);
1142 if ($resultFetch > 0) {
1143 $datas['product'] = "<br><b>".$langs->trans("Product").'</b>: '.$product->ref.' - '.$product->label;
1144 }
1145 }
1146
1147 return $datas;
1148 }
1149
1160 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1161 {
1162 global $db, $conf, $langs, $hookmanager;
1163
1164 if (!empty($conf->dol_no_mouse_hover)) {
1165 $notooltip = 1; // Force disable tooltips
1166 }
1167
1168 $result = '';
1169 $params = [
1170 'id' => $this->id,
1171 'objecttype' => $this->element,
1172 'option' => $option,
1173 ];
1174 $classfortooltip = 'classfortooltip';
1175 $dataparams = '';
1176 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1177 $classfortooltip = 'classforajaxtooltip';
1178 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1179 $label = '';
1180 } else {
1181 $label = implode($this->getTooltipContentArray($params));
1182 }
1183
1184 $url = DOL_URL_ROOT.'/bom/bom_card.php?id='.$this->id;
1185
1186 if ($option != 'nolink') {
1187 // Add param to save lastsearch_values or not
1188 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1189 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1190 $add_save_lastsearch_values = 1;
1191 }
1192 if ($add_save_lastsearch_values) {
1193 $url .= '&save_lastsearch_values=1';
1194 }
1195 }
1196
1197 $linkclose = '';
1198 if (empty($notooltip)) {
1199 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1200 $label = $langs->trans("ShowBillOfMaterials");
1201 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1202 }
1203 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1204 $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1205 } else {
1206 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1207 }
1208
1209 $linkstart = '<a href="'.$url.'"';
1210 $linkstart .= $linkclose.'>';
1211 $linkend = '</a>';
1212
1213 $result .= $linkstart;
1214 if ($withpicto) {
1215 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1216 }
1217 if ($withpicto != 2) {
1218 $result .= $this->ref;
1219 }
1220 $result .= $linkend;
1221 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1222
1223 global $action, $hookmanager;
1224 $hookmanager->initHooks(array('bomdao'));
1225 $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
1226 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1227 if ($reshook > 0) {
1228 $result = $hookmanager->resPrint;
1229 } else {
1230 $result .= $hookmanager->resPrint;
1231 }
1232
1233 return $result;
1234 }
1235
1242 public function getLibStatut($mode = 0)
1243 {
1244 return $this->LibStatut($this->status, $mode);
1245 }
1246
1247 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1255 public function LibStatut($status, $mode = 0)
1256 {
1257 // phpcs:enable
1258 if (empty($this->labelStatus)) {
1259 global $langs;
1260 //$langs->load("mrp");
1261 $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1262 $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Enabled');
1263 $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Disabled');
1264 }
1265
1266 $statusType = 'status'.$status;
1267 if ($status == self::STATUS_VALIDATED) {
1268 $statusType = 'status4';
1269 }
1270 if ($status == self::STATUS_CANCELED) {
1271 $statusType = 'status6';
1272 }
1273
1274 return dolGetStatus($this->labelStatus[$status], $this->labelStatus[$status], '', $statusType, $mode);
1275 }
1276
1283 public function info($id)
1284 {
1285 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1286 $sql .= ' fk_user_creat, fk_user_modif';
1287 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1288 $sql .= ' WHERE t.rowid = '.((int) $id);
1289 $result = $this->db->query($sql);
1290 if ($result) {
1291 if ($this->db->num_rows($result)) {
1292 $obj = $this->db->fetch_object($result);
1293
1294 $this->id = $obj->rowid;
1295
1296 $this->user_creation_id = $obj->fk_user_creat;
1297 $this->user_modification_id = $obj->fk_user_modif;
1298 $this->date_creation = $this->db->jdate($obj->datec);
1299 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1300 }
1301
1302 $this->db->free($result);
1303 } else {
1304 dol_print_error($this->db);
1305 }
1306 }
1307
1313 public function getLinesArray()
1314 {
1315 $this->lines = array();
1316
1317 $objectline = new BOMLine($this->db);
1318 $result = $objectline->fetchAll('ASC', 'position', 0, 0, array('customsql'=>'fk_bom = '.((int) $this->id)));
1319
1320 if (is_numeric($result)) {
1321 $this->error = $objectline->error;
1322 $this->errors = $objectline->errors;
1323 return $result;
1324 } else {
1325 $this->lines = $result;
1326 return $this->lines;
1327 }
1328 }
1329
1341 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
1342 {
1343 global $conf, $langs;
1344
1345 $langs->load("mrp");
1346 $outputlangs->load("products");
1347
1348 if (!dol_strlen($modele)) {
1349 $modele = '';
1350
1351 if ($this->model_pdf) {
1352 $modele = $this->model_pdf;
1353 } elseif (getDolGlobalString('BOM_ADDON_PDF')) {
1354 $modele = $conf->global->BOM_ADDON_PDF;
1355 }
1356 }
1357
1358 $modelpath = "core/modules/bom/doc/";
1359 if (!empty($modele)) {
1360 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
1361 } else {
1362 return 0;
1363 }
1364 }
1365
1372 public function initAsSpecimen()
1373 {
1374 $this->initAsSpecimenCommon();
1375 $this->ref = 'BOM-123';
1376 $this->date_creation = dol_now() - 20000;
1377 }
1378
1379
1386 public function doScheduledJob()
1387 {
1388 global $conf, $langs;
1389
1390 //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
1391
1392 $error = 0;
1393 $this->output = '';
1394 $this->error = '';
1395
1396 dol_syslog(__METHOD__, LOG_DEBUG);
1397
1398 $now = dol_now();
1399
1400 $this->db->begin();
1401
1402 // ...
1403
1404 $this->db->commit();
1405
1406 return $error;
1407 }
1408
1415 public function calculateCosts()
1416 {
1417 global $conf, $hookmanager;
1418
1419 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1420 $this->unit_cost = 0;
1421 $this->total_cost = 0;
1422
1423 $parameters=array();
1424 $reshook = $hookmanager->executeHooks('calculateCostsBom', $parameters, $this); // Note that $action and $object may have been modified by hook
1425
1426 if ($reshook > 0) {
1427 return $hookmanager->resPrint;
1428 }
1429
1430 if (is_array($this->lines) && count($this->lines)) {
1431 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1432 $productFournisseur = new ProductFournisseur($this->db);
1433 $tmpproduct = new Product($this->db);
1434
1435 foreach ($this->lines as &$line) {
1436 $tmpproduct->cost_price = 0;
1437 $tmpproduct->pmp = 0;
1438 $result = $tmpproduct->fetch($line->fk_product, '', '', '', 0, 1, 1); // We discard selling price and language loading
1439
1440 if ($tmpproduct->type == $tmpproduct::TYPE_PRODUCT) {
1441 if (empty($line->fk_bom_child)) {
1442 if ($result < 0) {
1443 $this->error = $tmpproduct->error;
1444 return -1;
1445 }
1446 $line->unit_cost = price2num((!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp);
1447 if (empty($line->unit_cost)) {
1448 if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
1449 if ($productFournisseur->fourn_remise_percent != "0") {
1450 $line->unit_cost = $productFournisseur->fourn_unitprice_with_discount;
1451 } else {
1452 $line->unit_cost = $productFournisseur->fourn_unitprice;
1453 }
1454 }
1455 }
1456
1457 $line->total_cost = price2num($line->qty * $line->unit_cost, 'MT');
1458
1459 $this->total_cost += $line->total_cost;
1460 } else {
1461 $bom_child = new BOM($this->db);
1462 $res = $bom_child->fetch($line->fk_bom_child);
1463 if ($res > 0) {
1464 $bom_child->calculateCosts();
1465 $line->childBom[] = $bom_child;
1466 $this->total_cost += price2num($bom_child->total_cost * $line->qty, 'MT');
1467 $this->total_cost += $line->total_cost;
1468 } else {
1469 $this->error = $bom_child->error;
1470 return -2;
1471 }
1472 }
1473 } else {
1474 // Convert qty of line into hours
1475 $unitforline = measuringUnitString($line->fk_unit, '', '', 1);
1476 $qtyhourforline = convertDurationtoHour($line->qty, $unitforline);
1477
1478 if (isModEnabled('workstation') && !empty($line->fk_default_workstation)) {
1479 $workstation = new Workstation($this->db);
1480 $res = $workstation->fetch($line->fk_default_workstation);
1481
1482 if ($res > 0) {
1483 $line->total_cost = price2num($qtyhourforline * ($workstation->thm_operator_estimated + $workstation->thm_machine_estimated), 'MT');
1484 } else {
1485 $this->error = $workstation->error;
1486 return -3;
1487 }
1488 } else {
1489 $defaultdurationofservice = $tmpproduct->duration;
1490 $reg = array();
1491 $qtyhourservice = 0;
1492 if (preg_match('/^(\d+)([a-z]+)$/', $defaultdurationofservice, $reg)) {
1493 $qtyhourservice = convertDurationtoHour($reg[1], $reg[2]);
1494 }
1495
1496 if ($qtyhourservice) {
1497 $line->total_cost = price2num($qtyhourforline / $qtyhourservice * $tmpproduct->cost_price, 'MT');
1498 } else {
1499 $line->total_cost = price2num($line->qty * $tmpproduct->cost_price, 'MT');
1500 }
1501 }
1502
1503 $this->total_cost += $line->total_cost;
1504 }
1505 }
1506
1507 $this->total_cost = price2num($this->total_cost, 'MT');
1508
1509 if ($this->qty > 0) {
1510 $this->unit_cost = price2num($this->total_cost / $this->qty, 'MU');
1511 } elseif ($this->qty < 0) {
1512 $this->unit_cost = price2num($this->total_cost * $this->qty, 'MU');
1513 }
1514 }
1515
1516 return 1;
1517 }
1518
1527 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
1528 {
1529 $tables = array(
1530 'bom_bomline'
1531 );
1532
1533 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
1534 }
1535
1543 public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
1544 {
1545 if (!empty($this->lines)) {
1546 foreach ($this->lines as $line) {
1547 if (!empty($line->childBom)) {
1548 foreach ($line->childBom as $childBom) {
1549 $childBom->getNetNeeds($TNetNeeds, $line->qty*$qty);
1550 }
1551 } else {
1552 if (empty($TNetNeeds[$line->fk_product])) {
1553 $TNetNeeds[$line->fk_product] = 0;
1554 }
1555 $TNetNeeds[$line->fk_product] += $line->qty*$qty;
1556 }
1557 }
1558 }
1559 }
1560
1569 public function getNetNeedsTree(&$TNetNeeds = array(), $qty = 0, $level = 0)
1570 {
1571 if (!empty($this->lines)) {
1572 foreach ($this->lines as $line) {
1573 if (!empty($line->childBom)) {
1574 foreach ($line->childBom as $childBom) {
1575 $TNetNeeds[$childBom->id]['bom'] = $childBom;
1576 $TNetNeeds[$childBom->id]['parentid'] = $this->id;
1577 $TNetNeeds[$childBom->id]['qty'] = $line->qty*$qty;
1578 $TNetNeeds[$childBom->id]['level'] = $level;
1579 $childBom->getNetNeedsTree($TNetNeeds, $line->qty*$qty, $level+1);
1580 }
1581 } else {
1582 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
1583 $TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
1584 }
1585 }
1586 }
1587 }
1588
1597 public function getParentBomTreeRecursive(&$TParentBom, $bom_id = 0, $level = 1)
1598 {
1599
1600 // Protection against infinite loop
1601 if ($level > 1000) {
1602 return;
1603 }
1604
1605 if (empty($bom_id)) {
1606 $bom_id=$this->id;
1607 }
1608
1609 $sql = 'SELECT l.fk_bom, b.label
1610 FROM '.MAIN_DB_PREFIX.'bom_bomline l
1611 INNER JOIN '.MAIN_DB_PREFIX.$this->table_element.' b ON b.rowid = l.fk_bom
1612 WHERE fk_bom_child = '.((int) $bom_id);
1613
1614 $resql = $this->db->query($sql);
1615 if (!empty($resql)) {
1616 while ($res = $this->db->fetch_object($resql)) {
1617 $TParentBom[$res->fk_bom] = $res->fk_bom;
1618 $this->getParentBomTreeRecursive($TParentBom, $res->fk_bom, $level+1);
1619 }
1620 }
1621 }
1622
1630 public function getKanbanView($option = '', $arraydata = null)
1631 {
1632 global $db,$langs;
1633
1634 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1635
1636 $return = '<div class="box-flex-item box-flex-grow-zero">';
1637 $return .= '<div class="info-box info-box-sm">';
1638 $return .= '<span class="info-box-icon bg-infobox-action">';
1639 $return .= img_picto('', $this->picto);
1640 $return .= '</span>';
1641 $return .= '<div class="info-box-content">';
1642 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : '').'</span>';
1643 if ($selected >= 0) {
1644 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1645 }
1646 if (property_exists($this, 'fields') && !empty($this->fields['bomtype']['arrayofkeyval'])) {
1647 $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Type").' : </span>';
1648 if ($this->bomtype == 0) {
1649 $return .= '<span class="info-box-label">'.$this->fields['bomtype']['arrayofkeyval'][0].'</span>';
1650 } else {
1651 $return .= '<span class="info-box-label">'.$this->fields['bomtype']['arrayofkeyval'][1].'</span>';
1652 }
1653 }
1654 if (!empty($arraydata['prod'])) {
1655 $prod = $arraydata['prod'];
1656 $return .= '<br><span class="info-box-label">'.$prod->getNomUrl(1).'</span>';
1657 }
1658 if (method_exists($this, 'getLibStatut')) {
1659 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1660 }
1661
1662 $return .= '</div>';
1663 $return .= '</div>';
1664 $return .= '</div>';
1665 return $return;
1666 }
1667}
1668
1669
1674{
1678 public $element = 'bomline';
1679
1683 public $table_element = 'bom_bomline';
1684
1688 public $ismultientitymanaged = 0;
1689
1693 public $isextrafieldmanaged = 1;
1694
1698 public $picto = 'bomline';
1699
1700
1720 // BEGIN MODULEBUILDER PROPERTIES
1724 public $fields = array(
1725 'rowid' => array('type'=>'integer', 'label'=>'LineID', 'enabled'=>1, 'visible'=>-1, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>"Id",),
1726 'fk_bom' => array('type'=>'integer:BillOfMaterials:societe/class/bom.class.php', 'label'=>'BillOfMaterials', 'enabled'=>1, 'visible'=>1, 'position'=>10, 'notnull'=>1, 'index'=>1,),
1727 'fk_product' => array('type'=>'integer:Product:product/class/product.class.php', 'label'=>'Product', 'enabled'=>1, 'visible'=>1, 'position'=>20, 'notnull'=>1, 'index'=>1,),
1728 'fk_bom_child' => array('type'=>'integer:BOM:bom/class/bom.class.php', 'label'=>'BillOfMaterials', 'enabled'=>1, 'visible'=>-1, 'position'=>40, 'notnull'=>-1,),
1729 'description' => array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>-1, 'position'=>60, 'notnull'=>-1,),
1730 'qty' => array('type'=>'double(24,8)', 'label'=>'Quantity', 'enabled'=>1, 'visible'=>1, 'position'=>100, 'notnull'=>1, 'isameasure'=>'1',),
1731 'qty_frozen' => array('type'=>'smallint', 'label'=>'QuantityFrozen', 'enabled'=>1, 'visible'=>1, 'default'=>0, 'position'=>105, 'css'=>'maxwidth50imp', 'help'=>'QuantityConsumedInvariable'),
1732 'disable_stock_change' => array('type'=>'smallint', 'label'=>'DisableStockChange', 'enabled'=>1, 'visible'=>1, 'default'=>0, 'position'=>108, 'css'=>'maxwidth50imp', 'help'=>'DisableStockChangeHelp'),
1733 'efficiency' => array('type'=>'double(24,8)', 'label'=>'ManufacturingEfficiency', 'enabled'=>1, 'visible'=>0, 'default'=>1, 'position'=>110, 'notnull'=>1, 'css'=>'maxwidth50imp', 'help'=>'ValueOfEfficiencyConsumedMeans'),
1734 'fk_unit' => array('type'=>'integer', 'label'=>'Unit', 'enabled'=>1, 'visible'=>1, 'position'=>120, 'notnull'=>-1,),
1735 'position' => array('type'=>'integer', 'label'=>'Rank', 'enabled'=>1, 'visible'=>0, 'default'=>0, 'position'=>200, 'notnull'=>1,),
1736 'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>1000, 'notnull'=>-1,),
1737 'fk_default_workstation' =>array('type'=>'integer', 'label'=>'DefaultWorkstation', 'enabled'=>1, 'visible'=>1, 'notnull'=>0, 'position'=>1050)
1738 );
1739
1743 public $rowid;
1744
1748 public $fk_bom;
1749
1753 public $fk_product;
1754
1758 public $fk_bom_child;
1759
1763 public $description;
1764
1768 public $qty;
1769
1773 public $qty_frozen;
1774
1778 public $disable_stock_change;
1779
1783 public $efficiency;
1784
1788 public $position;
1789
1793 public $import_key;
1794 // END MODULEBUILDER PROPERTIES
1795
1799 public $total_cost = 0;
1800
1804 public $unit_cost = 0;
1805
1809 public $childBom = array();
1810
1814 public $fk_unit;
1815
1819 public $fk_default_workstation;
1820
1821
1822
1828 public function __construct(DoliDB $db)
1829 {
1830 global $conf, $langs;
1831
1832 $this->db = $db;
1833
1834 if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid'])) {
1835 $this->fields['rowid']['visible'] = 0;
1836 }
1837 if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
1838 $this->fields['entity']['enabled'] = 0;
1839 }
1840
1841 // Unset fields that are disabled
1842 foreach ($this->fields as $key => $val) {
1843 if (isset($val['enabled']) && empty($val['enabled'])) {
1844 unset($this->fields[$key]);
1845 }
1846 }
1847
1848 // Translate some data of arrayofkeyval
1849 foreach ($this->fields as $key => $val) {
1850 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
1851 foreach ($val['arrayofkeyval'] as $key2 => $val2) {
1852 $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
1853 }
1854 }
1855 }
1856 }
1857
1865 public function create(User $user, $notrigger = false)
1866 {
1867 if ($this->efficiency < 0 || $this->efficiency > 1) {
1868 $this->efficiency = 1;
1869 }
1870
1871 return $this->createCommon($user, $notrigger);
1872 }
1873
1881 public function fetch($id, $ref = null)
1882 {
1883 $result = $this->fetchCommon($id, $ref);
1884 //if ($result > 0 && !empty($this->table_element_line)) $this->fetchLines();
1885 return $result;
1886 }
1887
1899 public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
1900 {
1901 global $conf;
1902
1903 dol_syslog(__METHOD__, LOG_DEBUG);
1904
1905 $records = array();
1906
1907 $sql = 'SELECT ';
1908 $sql .= $this->getFieldList();
1909 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1910 if ($this->ismultientitymanaged) {
1911 $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
1912 } else {
1913 $sql .= ' WHERE 1 = 1';
1914 }
1915 // Manage filter
1916 $sqlwhere = array();
1917 if (count($filter) > 0) {
1918 foreach ($filter as $key => $value) {
1919 if ($key == 't.rowid') {
1920 $sqlwhere[] = $key." = ".((int) $value);
1921 } elseif (strpos($key, 'date') !== false) {
1922 $sqlwhere[] = $key." = '".$this->db->idate($value)."'";
1923 } elseif ($key == 'customsql') {
1924 $sqlwhere[] = $value;
1925 } else {
1926 $sqlwhere[] = $key." LIKE '%".$this->db->escape($value)."%'";
1927 }
1928 }
1929 }
1930 if (count($sqlwhere) > 0) {
1931 $sql .= ' AND ('.implode(' '.$this->db->escape($filtermode).' ', $sqlwhere).')';
1932 }
1933
1934 if (!empty($sortfield)) {
1935 $sql .= $this->db->order($sortfield, $sortorder);
1936 }
1937 if (!empty($limit)) {
1938 $sql .= $this->db->plimit($limit, $offset);
1939 }
1940
1941 $resql = $this->db->query($sql);
1942 if ($resql) {
1943 $num = $this->db->num_rows($resql);
1944
1945 while ($obj = $this->db->fetch_object($resql)) {
1946 $record = new self($this->db);
1947 $record->setVarsFromFetchObj($obj);
1948
1949 $records[$record->id] = $record;
1950 }
1951 $this->db->free($resql);
1952
1953 return $records;
1954 } else {
1955 $this->errors[] = 'Error '.$this->db->lasterror();
1956 dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
1957
1958 return -1;
1959 }
1960 }
1961
1969 public function update(User $user, $notrigger = false)
1970 {
1971 if ($this->efficiency < 0 || $this->efficiency > 1) {
1972 $this->efficiency = 1;
1973 }
1974
1975 return $this->updateCommon($user, $notrigger);
1976 }
1977
1985 public function delete(User $user, $notrigger = false)
1986 {
1987 return $this->deleteCommon($user, $notrigger);
1988 //return $this->deleteCommon($user, $notrigger, 1);
1989 }
1990
2001 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
2002 {
2003 global $db, $conf, $langs, $hookmanager;
2004
2005 if (!empty($conf->dol_no_mouse_hover)) {
2006 $notooltip = 1; // Force disable tooltips
2007 }
2008
2009 $result = '';
2010
2011 $label = '<u>'.$langs->trans("BillOfMaterialsLine").'</u>';
2012 $label .= '<br>';
2013 $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref;
2014
2015 $url = DOL_URL_ROOT.'/bom/bomline_card.php?id='.$this->id;
2016
2017 if ($option != 'nolink') {
2018 // Add param to save lastsearch_values or not
2019 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2020 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2021 $add_save_lastsearch_values = 1;
2022 }
2023 if ($add_save_lastsearch_values) {
2024 $url .= '&save_lastsearch_values=1';
2025 }
2026 }
2027
2028 $linkclose = '';
2029 if (empty($notooltip)) {
2030 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2031 $label = $langs->trans("ShowBillOfMaterialsLine");
2032 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
2033 }
2034 $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
2035 $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
2036 } else {
2037 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
2038 }
2039
2040 $linkstart = '<a href="'.$url.'"';
2041 $linkstart .= $linkclose.'>';
2042 $linkend = '</a>';
2043
2044 $result .= $linkstart;
2045 if ($withpicto) {
2046 $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);
2047 }
2048 if ($withpicto != 2) {
2049 $result .= $this->ref;
2050 }
2051 $result .= $linkend;
2052 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
2053
2054 global $action, $hookmanager;
2055 $hookmanager->initHooks(array('bomlinedao'));
2056 $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
2057 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2058 if ($reshook > 0) {
2059 $result = $hookmanager->resPrint;
2060 } else {
2061 $result .= $hookmanager->resPrint;
2062 }
2063
2064 return $result;
2065 }
2066
2073 public function getLibStatut($mode = 0)
2074 {
2075 return $this->LibStatut($this->status, $mode);
2076 }
2077
2078 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2086 public function LibStatut($status, $mode = 0)
2087 {
2088 // phpcs:enable
2089 return '';
2090 }
2091
2098 public function info($id)
2099 {
2100 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
2101 $sql .= ' fk_user_creat, fk_user_modif';
2102 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
2103 $sql .= ' WHERE t.rowid = '.((int) $id);
2104 $result = $this->db->query($sql);
2105 if ($result) {
2106 if ($this->db->num_rows($result)) {
2107 $obj = $this->db->fetch_object($result);
2108
2109 $this->id = $obj->rowid;
2110
2111 $this->user_creation_id = $obj->fk_user_creat;
2112 $this->user_modification_id = $obj->fk_user_modif;
2113 $this->date_creation = $this->db->jdate($obj->datec);
2114 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
2115 }
2116 $this->db->free($result);
2117 } else {
2118 dol_print_error($this->db);
2119 }
2120 }
2121
2128 public function initAsSpecimen()
2129 {
2130 $this->initAsSpecimenCommon();
2131 }
2132}
print $langs trans("AuditedSecurityEvents").'</strong >< span class="opacitymedium"></span >< br > status
Definition security.php:604
$object ref
Definition info.php:79
Class for BOM.
Definition bom.class.php:43
static replaceProduct(DoliDB $db, $origin_id, $dest_id)
Function used to replace a product id with another one.
fetchLines()
Load object lines in memory from the database.
__construct(DoliDB $db)
Constructor.
calculateCosts()
BOM costs calculation based on cost_price or pmp of each BOM line.
info($id)
Load the info information in the object.
getLibStatut($mode=0)
Return label of the status.
initAsSpecimen()
Initialise object with example values Id must be 0 if object instance is a specimen.
validate($user, $notrigger=0)
Validate bom.
reopen($user, $notrigger=0)
Set cancel status.
fetchAll($sortorder='', $sortfield='', $limit=0, $offset=0, array $filter=array(), $filtermode='AND')
Load list of objects in memory from the database.
cancel($user, $notrigger=0)
Set cancel status.
create(User $user, $notrigger=false)
Create object into database.
LibStatut($status, $mode=0)
Return the status.
doScheduledJob()
Action executed by scheduler CAN BE A CRON TASK.
getParentBomTreeRecursive(&$TParentBom, $bom_id=0, $level=1)
Recursively retrieves all parent bom in the tree that leads to the $bom_id bom.
getTooltipContentArray($params)
getTooltipContentArray
fetchLinesbytypeproduct($typeproduct=0)
Load object lines in memory from the database by type of product.
createFromClone(User $user, $fromid)
Clone an object into another one.
fetch($id, $ref=null)
Load object in memory from the database.
getNomUrl($withpicto=0, $option='', $notooltip=0, $morecss='', $save_lastsearch_value=-1)
Return a link to the object card (with optionaly the picto)
updateLine($rowid, $qty, $qty_frozen=0, $disable_stock_change=0, $efficiency=1.0, $position=-1, $import_key=null, $fk_unit=0, $array_options=array(), $fk_default_workstation=null)
Update an BOM line into database.
getNetNeeds(&$TNetNeeds=array(), $qty=0)
Get Net needs by product.
getNextNumRef($prod)
Returns the reference to the following non used BOM depending on the active numbering module defined ...
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=array(), $fk_default_workstation=null)
Add an BOM line into database (linked to BOM)
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
getLinesArray()
Create an array of lines.
setDraft($user, $notrigger=0)
Set draft status.
update(User $user, $notrigger=false)
Update object into database.
getKanbanView($option='', $arraydata=null)
Return clicable link of object (with eventually picto)
deleteLine(User $user, $idline, $notrigger=false)
Delete a line of object in database.
getNetNeedsTree(&$TNetNeeds=array(), $qty=0, $level=0)
Get Net needs Tree by product or bom.
Class for BOMLine.
create(User $user, $notrigger=false)
Create object into database.
fetchAll($sortorder='', $sortfield='', $limit=0, $offset=0, array $filter=array(), $filtermode='AND')
Load list of objects in memory from the database.
fetch($id, $ref=null)
Load object in memory from the database.
getLibStatut($mode=0)
Return label of the status.
update(User $user, $notrigger=false)
Update object into database.
__construct(DoliDB $db)
Constructor.
getNomUrl($withpicto=0, $option='', $notooltip=0, $morecss='', $save_lastsearch_value=-1)
Return a link to the object card (with optionaly the picto)
initAsSpecimen()
Initialise object with example values Id must be 0 if object instance is a specimen.
info($id)
Load the info information in the object.
LibStatut($status, $mode=0)
Return the status.
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.
setErrorsFromObject($object)
setErrorsFromObject
createCommon(User $user, $notrigger=false)
Create object into database.
deleteCommon(User $user, $notrigger=false, $forcechilddeletion=0)
Delete object in database.
getFieldList($alias='', $excludefields=array())
Function to concat keys of fields.
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.
fetchLinesCommon($morewhere='', $noextrafields=0)
Load object in memory from the 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)
fetchCommon($id, $ref=null, $morewhere='', $noextrafields=0)
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.
Class for Workstation.
convertDurationtoHour($duration_value, $duration_unit)
Convert duration to hour.
Definition date.lib.php:335
dol_dir_list($path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:62
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
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.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
publicphonebutton2 phonegreen basiclayout basiclayout TotalHT VATCode TotalVAT TotalLT1 TotalLT2 TotalTTC TotalHT clearboth nowraponall right right takeposterminal SELECT e e e e e statut
Definition invoice.php:1926
measuringUnitString($unit, $measuring_style='', $scale='', $use_short_label=0, $outputlangs=null)
Return translation label of a unit key.