dolibarr 23.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 * Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
6 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
28// Put here all includes required by your class file
29require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
30require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
31require_once DOL_DOCUMENT_ROOT.'/bom/class/bomline.class.php';
32
33if (isModEnabled('workstation')) {
34 require_once DOL_DOCUMENT_ROOT.'/workstation/class/workstation.class.php';
35}
36
37
41class BOM extends CommonObject
42{
46 public $module = 'bom';
47
51 public $element = 'bom';
52
57 public $TRIGGER_PREFIX = 'BOM';
58
62 public $table_element = 'bom_bom';
63
67 public $picto = 'bom';
68
72 public $product;
73
74
75 const STATUS_DRAFT = 0;
76 const STATUS_VALIDATED = 1;
77 const STATUS_CANCELED = 9;
78
79
106 // BEGIN MODULEBUILDER PROPERTIES
110 public $fields = array(
111 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -2, 'position' => 1, 'notnull' => 1, 'index' => 1, 'comment' => "Id",),
112 'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'default' => '1', 'index' => 1, 'position' => 5),
113 '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'),
114 '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'),
115 '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'),
116 //'bomtype' => array('type'=>'integer', 'label'=>'Type', 'enabled'=>1, 'visible'=>-1, 'position'=>32, 'notnull'=>1, 'default'=>'0', 'arrayofkeyval'=>array(0=>'Manufacturing')),
117 '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'),
118 'description' => array('type' => 'text', 'label' => 'Description', 'enabled' => 1, 'visible' => -1, 'position' => 60, 'notnull' => -1,),
119 'qty' => array('type' => 'real', 'label' => 'Quantity', 'enabled' => 1, 'visible' => 1, 'default' => '1', 'position' => 55, 'notnull' => 1, 'isameasure' => 1, 'css' => 'maxwidth50imp right'),
120 //'efficiency' => array('type'=>'real', 'label'=>'ManufacturingEfficiency', 'enabled'=>1, 'visible'=>-1, 'default'=>'1', 'position'=>100, 'notnull'=>0, 'css'=>'maxwidth50imp', 'help'=>'ValueOfMeansLossForProductProduced'),
121 'duration' => array('type' => 'duration', 'label' => 'EstimatedDuration', 'enabled' => 1, 'visible' => -1, 'position' => 101, 'notnull' => -1, 'css' => 'maxwidth50imp', 'help' => 'EstimatedDurationDesc'),
122 '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'),
123 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => -2, 'position' => 161, 'notnull' => -1,),
124 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => -2, 'position' => 162, 'notnull' => -1,),
125 'date_creation' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'position' => 300, 'notnull' => 1,),
126 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -2, 'position' => 501, 'notnull' => 1,),
127 'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -2, 'position' => 502, 'notnull' => 0,),
128 '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'),
129 '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'),
130 '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'),
131 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 1000, 'notnull' => -1,),
132 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'Model pdf', 'enabled' => 1, 'visible' => 0, 'position' => 1010),
133 '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')),
134 );
135
139 public $rowid;
140
144 public $ref;
145
149 public $label;
150
154 public $bomtype;
155
159 public $description;
160
164 public $date_valid;
165
169 public $fk_user_creat;
170
174 public $fk_user_modif;
175
179 public $fk_user_valid;
180
184 public $fk_warehouse;
185
189 public $import_key;
190
194 public $status;
195
199 public $fk_product;
200
204 public $qty;
205
209 public $duration;
210
214 public $efficiency;
215 // END MODULEBUILDER PROPERTIES
216
217
218 // If this object has a subtable with lines
219
223 public $table_element_line = 'bom_bomline';
224
228 public $fk_element = 'fk_bom';
229
233 public $class_element_line = 'BOMLine';
234
235 // /**
236 // * @var array List of child tables. To test if we can delete object.
237 // */
238 // protected $childtables=array();
239
243 protected $childtablesoncascade = array('bom_bomline');
244
248 public $lines = array();
249
253 public $total_cost = 0;
254
258 public $unit_cost = 0;
259
260
266 public function __construct(DoliDB $db)
267 {
268 global $langs;
269
270 $this->db = $db;
271
272 $this->ismultientitymanaged = 1;
273 $this->isextrafieldmanaged = 1;
274
275 if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid'])) {
276 $this->fields['rowid']['visible'] = 0;
277 }
278 if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
279 $this->fields['entity']['enabled'] = 0;
280 }
281
282 // Unset fields that are disabled
283 foreach ($this->fields as $key => $val) {
284 if (isset($val['enabled']) && empty($val['enabled'])) {
285 unset($this->fields[$key]);
286 }
287 }
288
289 // Translate some data of arrayofkeyval
290 foreach ($this->fields as $key => $val) {
291 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
292 foreach ($val['arrayofkeyval'] as $key2 => $val2) {
293 $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
294 }
295 }
296 }
297 }
298
306 public function create(User $user, $notrigger = 0)
307 {
308 if ($this->efficiency <= 0 || $this->efficiency > 1) {
309 $this->efficiency = 1;
310 }
311
312 return $this->createCommon($user, $notrigger);
313 }
314
322 public function createFromClone(User $user, $fromid)
323 {
324 global $langs, $extrafields;
325 $error = 0;
326
327 dol_syslog(__METHOD__, LOG_DEBUG);
328
329 $object = new self($this->db);
330
331 $this->db->begin();
332
333 // Load source object
334 $result = $object->fetchCommon($fromid);
335 if ($result > 0 && !empty($object->table_element_line)) {
336 $object->fetchLines();
337 }
338
339 // Get lines so they will be clone
340 //foreach ($object->lines as $line)
341 // $line->fetch_optionals();
342
343 // Reset some properties
344 unset($object->id);
345 unset($object->fk_user_creat);
346 unset($object->import_key);
347 unset($object->date_creation);
348 // Clear fields
349 $default_ref = $this->fields['ref']['default'] ?? null;
350 $object->ref = empty($default_ref) ? $langs->trans("copy_of_").$object->ref : $default_ref;
351 // @phan-suppress-next-line PhanTypeInvalidDimOffset
352 $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf")." ".$object->label : $this->fields['label']['default'];
353 $object->status = self::STATUS_DRAFT;
354 // ...
355 // Clear extrafields that are unique
356 if (is_array($object->array_options) && count($object->array_options) > 0) {
357 $extrafields->fetch_name_optionals_label($object->table_element);
358 foreach ($object->array_options as $key => $option) {
359 $shortkey = preg_replace('/options_/', '', $key);
360 if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) {
361 //var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
362 unset($object->array_options[$key]);
363 }
364 }
365 }
366
367 // Create clone
368 $object->context['createfromclone'] = 'createfromclone';
369 $result = $object->createCommon($user);
370 if ($result < 0) {
371 $error++;
373 }
374
375 if (!$error) {
376 // copy internal contacts
377 if ($this->copy_linked_contact($object, 'internal') < 0) {
378 $error++;
379 }
380 }
381
382 if (!$error) {
383 // copy external contacts if same company
384 // @phan-suppress-next-line PhanUndeclaredProperty
385 if (property_exists($this, 'socid') && $this->socid == $object->socid) {
386 if ($this->copy_linked_contact($object, 'external') < 0) {
387 $error++;
388 }
389 }
390 }
391
392 // If there is lines, create lines too from ->lines
393 // TODO
394
395 unset($object->context['createfromclone']);
396
397 // End
398 if (!$error) {
399 $this->db->commit();
400 return $object;
401 } else {
402 $this->db->rollback();
403 return -1;
404 }
405 }
406
414 public function fetch($id, $ref = null)
415 {
416 $result = $this->fetchCommon($id, $ref);
417
418 if ($result > 0 && !empty($this->table_element_line)) {
419 $this->fetchLines();
420 }
421 //$this->calculateCosts(); // This consume a high number of subrequests. Do not call it into fetch but when you need it.
422
423 return $result;
424 }
425
431 public function fetchLines()
432 {
433 $this->lines = array();
434
435 $result = $this->fetchLinesCommon();
436 return $result;
437 }
438
445 public function fetchLinesbytypeproduct($typeproduct = 0)
446 {
447 $this->lines = array();
448
449 $objectlineclassname = $this->class_element_line;
450 if (!class_exists($objectlineclassname)) {
451 $this->error = 'Error, class BOMLine not found during call of fetchLinesCommon';
452 return -1;
453 }
454
455 $objectline = new BOMLine($this->db);
456
457 '@phan-var-force BOMLine $objectline';
458
459 $sql = "SELECT ".$objectline->getFieldList('l');
460 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
461 $sql .= " LEFT JOIN ".$this->db->prefix()."product as p ON p.rowid = l.fk_product";
462 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
463 $sql .= " AND p.fk_product_type = ". ((int) $typeproduct);
464 if (isset($objectline->fields['position'])) {
465 $sql .= $this->db->order('position', 'ASC');
466 }
467
468 $resql = $this->db->query($sql);
469 if ($resql) {
470 $num_rows = $this->db->num_rows($resql);
471 $i = 0;
472 while ($i < $num_rows) {
473 $obj = $this->db->fetch_object($resql);
474 if ($obj) {
475 $newline = new BOMLine($this->db);
476 '@phan-var-force BOMLine $newline';
477 $newline->setVarsFromFetchObj($obj);
478
479 // Load also extrafields for the line
480 //if (empty($noextrafields)) {
481 $newline->fetch_optionals();
482 //}
483
484 $this->lines[] = $newline;
485 }
486 $i++;
487 }
488
489 return $num_rows;
490 } else {
491 $this->error = $this->db->lasterror();
492 $this->errors[] = $this->error;
493 return -1;
494 }
495 }
496
497
509 public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, $filter = '', $filtermode = 'AND')
510 {
511 dol_syslog(__METHOD__, LOG_DEBUG);
512
513 $records = array();
514
515 $sql = 'SELECT ';
516 $sql .= $this->getFieldList();
517 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
518 if ($this->ismultientitymanaged) {
519 $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
520 } else {
521 $sql .= ' WHERE 1 = 1';
522 }
523
524 // Manage filter
525 $errormessage = '';
526 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
527 if ($errormessage) {
528 $this->errors[] = $errormessage;
529 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
530 return -1;
531 }
532
533 if (!empty($sortfield)) {
534 $sql .= $this->db->order($sortfield, $sortorder);
535 }
536 if (!empty($limit)) {
537 $sql .= $this->db->plimit($limit, $offset);
538 }
539
540 $resql = $this->db->query($sql);
541 if ($resql) {
542 $num = $this->db->num_rows($resql);
543
544 while ($obj = $this->db->fetch_object($resql)) {
545 $record = new self($this->db);
546 $record->setVarsFromFetchObj($obj);
547
548 $records[$record->id] = $record;
549 }
550 $this->db->free($resql);
551
552 return $records;
553 } else {
554 $this->errors[] = 'Error '.$this->db->lasterror();
555 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
556
557 return -1;
558 }
559 }
560
568 public function update(User $user, $notrigger = 0)
569 {
570 if ($this->efficiency <= 0 || $this->efficiency > 1) {
571 $this->efficiency = 1;
572 }
573
574 return $this->updateCommon($user, $notrigger);
575 }
576
584 public function delete(User $user, $notrigger = 0)
585 {
586 return $this->deleteCommon($user, $notrigger);
587 //return $this->deleteCommon($user, $notrigger, 1);
588 }
589
606 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 = 0, $array_options = array(), $fk_default_workstation = null)
607 {
608 global $user;
609
610 $logtext = "::addLine bomid=$this->id, qty=$qty, fk_product=$fk_product, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
611 $logtext .= ", fk_bom_child=$fk_bom_child, import_key=$import_key";
612 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
613
614 if ($this->status == self::STATUS_DRAFT) {
615 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
616
617 // Clean parameters
618 if (empty($qty)) {
619 $qty = 0;
620 }
621 if (empty($qty_frozen)) {
622 $qty_frozen = 0;
623 }
624 if (empty($disable_stock_change)) {
625 $disable_stock_change = 0;
626 }
627 if (empty($efficiency)) {
628 $efficiency = 1.0;
629 }
630 if (empty($fk_bom_child)) {
631 $fk_bom_child = null;
632 }
633 if (empty($import_key)) {
634 $import_key = '';
635 }
636 if (empty($position)) {
637 $position = -1;
638 }
639
640 $qty = (float) price2num($qty);
641 $efficiency = (float) price2num($efficiency);
642 $position = (float) price2num($position);
643
644 $this->db->begin();
645
646 // Rank to use
647 $rangMax = $this->line_max();
648 $rankToUse = $position;
649 if ($rankToUse <= 0 or $rankToUse > $rangMax) { // New line after existing lines
650 $rankToUse = $rangMax + 1;
651 } else { // New line between the existing lines
652 foreach ($this->lines as $bl) {
653 if ($bl->position >= $rankToUse) {
654 $bl->position++;
655 $bl->update($user);
656 }
657 }
658 }
659
660 // Insert line
661 $line = new BOMLine($this->db);
662
663 $line->context = $this->context;
664
665 $line->fk_bom = $this->id;
666 $line->fk_product = $fk_product;
667 $line->qty = $qty;
668 $line->qty_frozen = $qty_frozen;
669 $line->disable_stock_change = $disable_stock_change;
670 $line->efficiency = $efficiency;
671 $line->fk_bom_child = $fk_bom_child;
672 $line->import_key = $import_key;
673 $line->position = $rankToUse;
674 $line->fk_unit = $fk_unit;
675 $line->fk_default_workstation = $fk_default_workstation;
676
677 if (is_array($array_options) && count($array_options) > 0) {
678 $line->array_options = $array_options;
679 }
680
681 $result = $line->create($user);
682
683 if ($result > 0) {
684 $this->calculateCosts();
685 $this->db->commit();
686 return $result;
687 } else {
688 $this->setErrorsFromObject($line);
689 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
690 $this->db->rollback();
691 return -2;
692 }
693 } else {
694 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
695 return -3;
696 }
697 }
698
714 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)
715 {
716 global $user;
717
718 $logtext = "::updateLine bomid=$this->id, qty=$qty, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
719 $logtext .= ", import_key=$import_key";
720 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
721
722 if ($this->status == self::STATUS_DRAFT) {
723 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
724
725 // Clean parameters
726 if (empty($qty)) {
727 $qty = 0;
728 }
729 if (empty($qty_frozen)) {
730 $qty_frozen = 0;
731 }
732 if (empty($disable_stock_change)) {
733 $disable_stock_change = 0;
734 }
735 if (empty($efficiency)) {
736 $efficiency = 1.0;
737 }
738 if (empty($import_key)) {
739 $import_key = '';
740 }
741 if (empty($position)) {
742 $position = -1;
743 }
744
745 $qty = (float) price2num($qty);
746 $efficiency = (float) price2num($efficiency);
747 $position = (float) price2num($position);
748
749 $this->db->begin();
750
751 // Fetch current line from the database and then clone the object and set it in $oldline property
752 $line = new BOMLine($this->db);
753 $line->fetch($rowid);
754 $line->fetch_optionals();
755
756 $staticLine = clone $line;
757 $line->oldcopy = $staticLine;
758 $line->context = $this->context;
759
760 // Rank to use
761 $rankToUse = (int) $position;
762 if ($rankToUse != $line->oldcopy->position) { // check if position have a new value
763 foreach ($this->lines as $bl) {
764 if ($bl->position >= $rankToUse and $bl->position < ($line->oldcopy->position + 1)) { // move rank up
765 $bl->position++;
766 $bl->update($user);
767 }
768 if ($bl->position <= $rankToUse and $bl->position > ($line->oldcopy->position)) { // move rank down
769 $bl->position--;
770 $bl->update($user);
771 }
772 }
773 }
774
775
776 $line->fk_bom = $this->id;
777 $line->qty = $qty;
778 $line->qty_frozen = $qty_frozen;
779 $line->disable_stock_change = $disable_stock_change;
780 $line->efficiency = $efficiency;
781 $line->import_key = $import_key;
782 $line->position = $rankToUse;
783
784
785 if (!empty($fk_unit)) {
786 $line->fk_unit = $fk_unit;
787 }
788
789
790 if (is_array($array_options) && count($array_options) > 0) {
791 // We replace values in this->line->array_options only for entries defined into $array_options
792 foreach ($array_options as $key => $value) {
793 $line->array_options[$key] = $array_options[$key];
794 }
795 }
796 if ($line->fk_default_workstation != $fk_default_workstation) {
797 $line->fk_default_workstation = ($fk_default_workstation > 0 ? $fk_default_workstation : 0);
798 }
799
800 $result = $line->update($user);
801
802 if ($result > 0) {
803 $this->calculateCosts();
804 $this->db->commit();
805 return $result;
806 } else {
807 $this->setErrorsFromObject($line);
808 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
809 $this->db->rollback();
810 return -2;
811 }
812 } else {
813 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
814 return -3;
815 }
816 }
817
826 public function deleteLine(User $user, $idline, $notrigger = 0)
827 {
828 if ($this->status < 0) {
829 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
830 return -2;
831 }
832
833 $this->db->begin();
834
835 // Fetch current line from the database and then clone the object and set it in $oldline property
836 $line = new BOMLine($this->db);
837 $line->fetch($idline);
838 $line->fetch_optionals();
839
840 $staticLine = clone $line;
841 $line->oldcopy = $staticLine;
842 $line->context = $this->context;
843
844 $result = $line->delete($user, $notrigger);
845
846 //Positions (rank) reordering
847 foreach ($this->lines as $bl) {
848 if ($bl->position > ($line->oldcopy->position)) { // move rank down
849 $bl->position--;
850 $bl->update($user);
851 }
852 }
853
854 if ($result > 0) {
855 $this->calculateCosts();
856 $this->db->commit();
857 return $result;
858 } else {
859 $this->setErrorsFromObject($line);
860 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
861 $this->db->rollback();
862 return -2;
863 }
864 }
865
873 public function getNextNumRef($prod)
874 {
875 global $langs, $conf;
876 $langs->load("mrp");
877
878 if (getDolGlobalString('BOM_ADDON')) {
879 $mybool = false;
880
881 $file = getDolGlobalString('BOM_ADDON') . ".php";
882 $classname = getDolGlobalString('BOM_ADDON');
883
884 // Include file with class
885 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
886 foreach ($dirmodels as $reldir) {
887 $dir = dol_buildpath($reldir."core/modules/bom/");
888
889 // Load file with numbering class (if found)
890 $mybool = ((bool) @include_once $dir.$file) || $mybool;
891 }
892
893 if (!$mybool) {
894 dol_print_error(null, "Failed to include file ".$file);
895 return '';
896 }
897
898 $obj = new $classname();
899 '@phan-var-force ModeleNumRefBoms $obj';
901 $numref = $obj->getNextValue($prod, $this);
902
903 if ($numref != "") {
904 return $numref;
905 } else {
906 $this->error = $obj->error;
907 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
908 return "";
909 }
910 } else {
911 print $langs->trans("Error")." ".$langs->trans("Error_BOM_ADDON_NotDefined");
912 return "";
913 }
914 }
915
923 public function validate($user, $notrigger = 0)
924 {
925 global $conf;
926
927 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
928
929 $error = 0;
930
931 // Protection
932 if ($this->status == self::STATUS_VALIDATED) {
933 dol_syslog(get_class($this)."::validate action abandoned: already validated", LOG_WARNING);
934 return 0;
935 }
936
937 $now = dol_now();
938
939 $this->db->begin();
940
941 // Define new ref
942 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
943 $res = $this->fetch_product();
944 if ($res < 0 || !is_object($this->product)) {
945 $this->db->rollback();
946 return -1;
947 }
948 $num = $this->getNextNumRef($this->product);
949 } else {
950 $num = (string) $this->ref;
951 }
952 $this->newref = dol_sanitizeFileName($num);
953
954 // Validate
955 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
956 $sql .= " SET ref = '".$this->db->escape($num)."',";
957 $sql .= " status = ".self::STATUS_VALIDATED.",";
958 $sql .= " date_valid='".$this->db->idate($now)."',";
959 $sql .= " fk_user_valid = ".((int) $user->id);
960 $sql .= " WHERE rowid = ".((int) $this->id);
961
962 dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
963 $resql = $this->db->query($sql);
964 if (!$resql) {
965 dol_print_error($this->db);
966 $this->error = $this->db->lasterror();
967 $error++;
968 }
969
970 if (!$error && !$notrigger) {
971 // Call trigger
972 $result = $this->call_trigger('BOM_VALIDATE', $user);
973 if ($result < 0) {
974 $error++;
975 }
976 // End call triggers
977 }
978
979 if (!$error) {
980 $this->oldref = $this->ref;
981
982 // Rename directory if dir was a temporary ref
983 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
984 // Now we rename also files into index
985 $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)."'";
986 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
987 $resql = $this->db->query($sql);
988 if (!$resql) {
989 $error++;
990 $this->error = $this->db->lasterror();
991 }
992 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'bom/".$this->db->escape($this->newref)."'";
993 $sql .= " WHERE filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
994 $resql = $this->db->query($sql);
995 if (!$resql) {
996 $error++;
997 $this->error = $this->db->lasterror();
998 }
999
1000 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1001 $oldref = dol_sanitizeFileName($this->ref);
1002 $newref = dol_sanitizeFileName($num);
1003 $dirsource = getMultidirOutput($this) . '/'.$oldref;
1004 $dirdest = getMultidirOutput($this) . '/'.$newref;
1005 if (!$error && file_exists($dirsource)) {
1006 dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
1007
1008 if (@rename($dirsource, $dirdest)) {
1009 dol_syslog("Rename ok");
1010 // Rename docs starting with $oldref with $newref
1011 $listoffiles = dol_dir_list(getMultidirOutput($this) . '/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
1012 foreach ($listoffiles as $fileentry) {
1013 $dirsource = $fileentry['name'];
1014 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
1015 $dirsource = $fileentry['path'].'/'.$dirsource;
1016 $dirdest = $fileentry['path'].'/'.$dirdest;
1017 @rename($dirsource, $dirdest);
1018 }
1019 }
1020 }
1021 }
1022 }
1023
1024 // Set new ref and current status
1025 if (!$error) {
1026 $this->ref = $num;
1027 $this->status = self::STATUS_VALIDATED;
1028 }
1029
1030 if (!$error) {
1031 $this->db->commit();
1032 return 1;
1033 } else {
1034 $this->db->rollback();
1035 return -1;
1036 }
1037 }
1038
1046 public function setDraft($user, $notrigger = 0)
1047 {
1048 // Protection
1049 if ($this->status <= self::STATUS_DRAFT) {
1050 return 0;
1051 }
1052
1053 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'BOM_UNVALIDATE');
1054 }
1055
1063 public function cancel($user, $notrigger = 0)
1064 {
1065 // Protection
1066 if ($this->status != self::STATUS_VALIDATED) {
1067 return 0;
1068 }
1069
1070 return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'BOM_CLOSE');
1071 }
1072
1080 public function reopen($user, $notrigger = 0)
1081 {
1082 // Protection
1083 if ($this->status != self::STATUS_CANCELED) {
1084 return 0;
1085 }
1086
1087 return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'BOM_REOPEN');
1088 }
1089
1096 public function getTooltipContentArray($params)
1097 {
1098 global $langs;
1099
1100 $langs->loadLangs(['product', 'mrp']);
1101
1102 $datas = [];
1103
1104 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1105 return ['optimize' => $langs->trans("ShowBillOfMaterials")];
1106 }
1107 $picto = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("BillOfMaterials").'</u>';
1108 if (isset($this->status)) {
1109 $picto .= ' '.$this->getLibStatut(5);
1110 }
1111 $datas['picto'] = $picto;
1112 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1113 if (isset($this->label)) {
1114 $datas['label'] = '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
1115 }
1116 if (!empty($this->fk_product) && $this->fk_product > 0) {
1117 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1118 $product = new Product($this->db);
1119 $resultFetch = $product->fetch($this->fk_product);
1120 if ($resultFetch > 0) {
1121 $datas['product'] = "<br><b>".$langs->trans("Product").'</b>: '.$product->ref.' - '.$product->label;
1122 }
1123 }
1124
1125 return $datas;
1126 }
1127
1138 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1139 {
1140 global $conf, $langs, $hookmanager;
1141
1142 if (!empty($conf->dol_no_mouse_hover)) {
1143 $notooltip = 1; // Force disable tooltips
1144 }
1145
1146 $result = '';
1147 $params = [
1148 'id' => $this->id,
1149 'objecttype' => $this->element,
1150 'option' => $option,
1151 ];
1152 $classfortooltip = 'classfortooltip';
1153 $dataparams = '';
1154 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1155 $classfortooltip = 'classforajaxtooltip';
1156 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1157 $label = '';
1158 } else {
1159 $label = implode($this->getTooltipContentArray($params));
1160 }
1161
1162 $url = DOL_URL_ROOT.'/bom/bom_card.php?id='.$this->id;
1163
1164 if ($option != 'nolink') {
1165 // Add param to save lastsearch_values or not
1166 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1167 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1168 $add_save_lastsearch_values = 1;
1169 }
1170 if ($add_save_lastsearch_values) {
1171 $url .= '&save_lastsearch_values=1';
1172 }
1173 }
1174
1175 $linkclose = '';
1176 if (empty($notooltip)) {
1177 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1178 $label = $langs->trans("ShowBillOfMaterials");
1179 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1180 }
1181 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1182 $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1183 } else {
1184 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1185 }
1186
1187 $linkstart = '<a href="'.$url.'"';
1188 $linkstart .= $linkclose.'>';
1189 $linkend = '</a>';
1190
1191 $result .= $linkstart;
1192 if ($withpicto) {
1193 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1194 }
1195 if ($withpicto != 2) {
1196 $result .= $this->ref;
1197 }
1198 $result .= $linkend;
1199 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1200
1201 global $action, $hookmanager;
1202 $hookmanager->initHooks(array('bomdao'));
1203 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1204 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1205 if ($reshook > 0) {
1206 $result = $hookmanager->resPrint;
1207 } else {
1208 $result .= $hookmanager->resPrint;
1209 }
1210
1211 return $result;
1212 }
1213
1220 public function getLibStatut($mode = 0)
1221 {
1222 return $this->LibStatut($this->status, $mode);
1223 }
1224
1225 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1233 public function LibStatut($status, $mode = 0)
1234 {
1235 // phpcs:enable
1236 if (empty($this->labelStatus)) {
1237 global $langs;
1238 //$langs->load("mrp");
1239 $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1240 $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Enabled');
1241 $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Disabled');
1242 }
1243
1244 $statusType = 'status'.$status;
1245 if ($status == self::STATUS_VALIDATED) {
1246 $statusType = 'status4';
1247 }
1248 if ($status == self::STATUS_CANCELED) {
1249 $statusType = 'status6';
1250 }
1251
1252 return dolGetStatus($this->labelStatus[$status], $this->labelStatus[$status], '', $statusType, $mode);
1253 }
1254
1261 public function info($id)
1262 {
1263 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1264 $sql .= ' fk_user_creat, fk_user_modif';
1265 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1266 $sql .= ' WHERE t.rowid = '.((int) $id);
1267 $result = $this->db->query($sql);
1268 if ($result) {
1269 if ($this->db->num_rows($result)) {
1270 $obj = $this->db->fetch_object($result);
1271
1272 $this->id = $obj->rowid;
1273
1274 $this->user_creation_id = $obj->fk_user_creat;
1275 $this->user_modification_id = $obj->fk_user_modif;
1276 $this->date_creation = $this->db->jdate($obj->datec);
1277 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1278 }
1279
1280 $this->db->free($result);
1281 } else {
1282 dol_print_error($this->db);
1283 }
1284 }
1285
1291 public function getLinesArray()
1292 {
1293 $this->lines = array();
1294
1295 $objectline = new BOMLine($this->db);
1296 $result = $objectline->fetchAll('ASC', 'position', 0, 0, '(fk_bom:=:'.((int) $this->id).')');
1297
1298 if (is_numeric($result)) {
1299 $this->error = $objectline->error;
1300 $this->errors = $objectline->errors;
1301 return $result;
1302 } else {
1303 $this->lines = $result;
1304 return $this->lines;
1305 }
1306 }
1307
1319 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
1320 {
1321 global $langs;
1322
1323 $langs->load("mrp");
1324 $outputlangs->load("products");
1325
1326 if (!dol_strlen($modele)) {
1327 $modele = '';
1328
1329 if ($this->model_pdf) {
1330 $modele = $this->model_pdf;
1331 } elseif (getDolGlobalString('BOM_ADDON_PDF')) {
1332 $modele = getDolGlobalString('BOM_ADDON_PDF');
1333 }
1334 }
1335
1336 $modelpath = "core/modules/bom/doc/";
1337 if (!empty($modele)) {
1338 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
1339 } else {
1340 return 0;
1341 }
1342 }
1343
1344 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1351 public function is_photo_available($sdir)
1352 {
1353 // phpcs:enable
1354 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1355 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
1356
1357 $sdir .= '/'.get_exdir(0, 0, 0, 0, $this, 'bom');
1358
1359 $dir_osencoded = dol_osencode($sdir);
1360 if (file_exists($dir_osencoded)) {
1361 $handle = opendir($dir_osencoded);
1362 if (is_resource($handle)) {
1363 while (($file = readdir($handle)) !== false) {
1364 if (!utf8_check($file)) {
1365 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
1366 }
1367 if (dol_is_file($sdir.$file) && image_format_supported($file) >= 0) {
1368 return true;
1369 }
1370 }
1371 }
1372 }
1373 return false;
1374 }
1375
1382 public function initAsSpecimen()
1383 {
1384 $this->initAsSpecimenCommon();
1385 $this->ref = 'BOM-123';
1386 $this->date_creation = dol_now() - 20000;
1387
1388 return 1;
1389 }
1390
1391
1398 public function calculateCosts()
1399 {
1400 global $hookmanager;
1401
1402 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1403 $this->unit_cost = 0;
1404 $this->total_cost = 0;
1405
1406 $parameters = array();
1407 $reshook = $hookmanager->executeHooks('calculateCostsBom', $parameters, $this); // Note that $action and $object may have been modified by hook
1408
1409 if ($reshook > 0) {
1410 $hookmanager->executeHooks('calculateCostsBomAfter', $parameters, $this); // Note that $action and $object may have been modified by hook
1411 return $hookmanager->resPrint;
1412 }
1413
1414 if (is_array($this->lines) && count($this->lines)) {
1415 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1416 $productFournisseur = new ProductFournisseur($this->db);
1417 $tmpproduct = new Product($this->db);
1418
1419 foreach ($this->lines as &$line) {
1420 $tmpproduct->cost_price = 0;
1421 $tmpproduct->pmp = 0;
1422 $result = $tmpproduct->fetch($line->fk_product, '', '', '', 0, 1, 1); // We discard selling price and language loading
1423
1424 $unit_cost = (float) (is_null($tmpproduct->cost_price) ? $tmpproduct->pmp : $tmpproduct->cost_price);
1425 if (empty($unit_cost)) { // @phpstan-ignore-line phpstan thinks this is always false. No,if unit_cost is 0, it is not.
1426 if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
1427 if ($productFournisseur->fourn_remise_percent != "0") {
1428 $line->unit_cost = $productFournisseur->fourn_unitprice_with_discount;
1429 } else {
1430 $line->unit_cost = $productFournisseur->fourn_unitprice;
1431 }
1432 } else {
1433 $line->unit_cost = 0;
1434 }
1435 } else {
1436 $line->unit_cost = (float) price2num($unit_cost);
1437 }
1438
1439 if ($tmpproduct->type == $tmpproduct::TYPE_PRODUCT) {
1440 if (empty($line->fk_bom_child)) {
1441 if ($result < 0) {
1442 $this->error = $tmpproduct->error;
1443 return -1;
1444 }
1445
1446 $line->total_cost = (float) $line->unit_cost * $line->qty / $line->efficiency;
1447
1448 $this->total_cost += $line->total_cost;
1449 } else {
1450 $bom_child = new BOM($this->db);
1451 $res = $bom_child->fetch((int) $line->fk_bom_child);
1452 if ($res > 0) {
1453 $bom_child->calculateCosts();
1454 $line->childBom[] = $bom_child;
1455 $line->total_cost = (float) $bom_child->unit_cost * $line->qty / $line->efficiency;
1456 $this->total_cost += $line->total_cost;
1457 } else {
1458 $this->error = $bom_child->error;
1459 return -2;
1460 }
1461 }
1462 } else {
1463 // Convert qty of line into hours
1464 require_once DOL_DOCUMENT_ROOT.'/core/class/cunits.class.php';
1465 $measuringUnits = new CUnits($this->db);
1466 $measuringUnits->fetch($line->fk_unit);
1467
1468 // The unit is a unit for time, so the $measuringUnits->scale is not a power of 10, but directly the factor to change unit into seconds
1469 $qtyhourforline = $line->qty * (int) $measuringUnits->scale / 3600;
1470
1471 if (isModEnabled('workstation') && !empty($line->fk_default_workstation)) {
1472 $workstation = new Workstation($this->db);
1473 $res = $workstation->fetch($line->fk_default_workstation);
1474
1475 if ($res > 0) {
1476 $line->total_cost = (float) $qtyhourforline * ($workstation->thm_operator_estimated + $workstation->thm_machine_estimated);
1477 } else {
1478 $this->error = $workstation->error;
1479 return -3;
1480 }
1481 } else {
1482 $defaultdurationofservice = $tmpproduct->duration;
1483 $reg = array();
1484 $qtyhourservice = 0;
1485 if (preg_match('/^(\d+)([a-z]+)$/', $defaultdurationofservice, $reg)) {
1486 $qtyhourservice = convertDurationtoHour((float) $reg[1], $reg[2]);
1487 }
1488
1489 if ($qtyhourservice) {
1490 $line->total_cost = (float) $qtyhourforline / $qtyhourservice * $line->unit_cost;
1491 } else {
1492 $line->total_cost = (float) $line->qty * $line->unit_cost;
1493 }
1494 }
1495
1496 $this->total_cost += $line->total_cost;
1497 }
1498 }
1499
1500 $this->total_cost = (float) price2num($this->total_cost, 'MT');
1501
1502 if ($this->qty > 0) {
1503 $this->unit_cost = (float) price2num($this->total_cost / $this->qty, 'MU');
1504 } elseif ($this->qty < 0) {
1505 $this->unit_cost = (float) price2num($this->total_cost * $this->qty, 'MU');
1506 }
1507 }
1508 $hookmanager->executeHooks('calculateCostsBomAfter', $parameters, $this);
1509
1510 return 1;
1511 }
1512
1521 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
1522 {
1523 $tables = array(
1524 'bom_bomline'
1525 );
1526
1527 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
1528 }
1529
1537 public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
1538 {
1539 if (!empty($this->lines)) {
1540 foreach ($this->lines as $line) {
1541 if (!empty($line->childBom)) {
1542 foreach ($line->childBom as $childBom) {
1543 $childBom->getNetNeeds($TNetNeeds, $line->qty * $qty / $childBom->qty);
1544 }
1545 } else {
1546 if (empty($TNetNeeds[$line->fk_product]['qty'])) {
1547 $TNetNeeds[$line->fk_product]['qty'] = 0.0;
1548 }
1549 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1550 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1551 $TNetNeeds[$line->fk_product]['fk_unit'] = $line->fk_unit;
1552 $TNetNeeds[$line->fk_product]['qty'] += $line->qty * $qty;
1553 }
1554 }
1555 }
1556 }
1557
1566 public function getNetNeedsTree(&$TNetNeeds = array(), $qty = 0, $level = 0)
1567 {
1568 if (!empty($this->lines)) {
1569 foreach ($this->lines as $line) {
1570 if (!empty($line->childBom)) {
1571 foreach ($line->childBom as $childBom) {
1572 $TNetNeeds[$childBom->id]['bom'] = $childBom;
1573 $TNetNeeds[$childBom->id]['parentid'] = $this->id;
1574 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1575 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1576 //$TNetNeeds[$childBom->id]['fk_unit'] = $line->fk_unit;
1577 $TNetNeeds[$childBom->id]['qty'] = $line->qty * $qty;
1578 $TNetNeeds[$childBom->id]['level'] = $level;
1579 $childBom->getNetNeedsTree($TNetNeeds, $line->qty * $qty / $childBom->qty, $level + 1);
1580 }
1581 } else {
1582 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1583 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1584 if (!isset($TNetNeeds[$this->id]['product'])) {
1585 $TNetNeeds[$this->id]['product'] = array();
1586 }
1587 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product])) {
1588 $TNetNeeds[$this->id]['product'][$line->fk_product] = array();
1589 }
1590 $TNetNeeds[$this->id]['product'][$line->fk_product]['fk_unit'] = $line->fk_unit;
1591 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product]['qty'])) {
1592 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] = 0.0;
1593 }
1594 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
1595 $TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
1596 }
1597 }
1598 }
1599 }
1600
1609 public function getParentBomTreeRecursive(&$TParentBom, $bom_id = 0, $level = 1)
1610 {
1611 // Protection against infinite loop
1612 if ($level > 1000) {
1613 return;
1614 }
1615
1616 if (empty($bom_id)) {
1617 $bom_id = $this->id;
1618 }
1619
1620 $sql = "SELECT l.fk_bom, b.label FROM ".MAIN_DB_PREFIX."bom_bomline as l INNER JOIN ".MAIN_DB_PREFIX.$this->table_element." b ON b.rowid = l.fk_bom";
1621 $sql .= " WHERE fk_bom_child = ".((int) $bom_id);
1622
1623 $resql = $this->db->query($sql);
1624 if (!empty($resql)) {
1625 while ($res = $this->db->fetch_object($resql)) {
1626 $TParentBom[(int) $res->fk_bom] = (int) $res->fk_bom;
1627 $this->getParentBomTreeRecursive($TParentBom, $res->fk_bom, $level + 1);
1628 }
1629 }
1630 }
1631
1639 public function getKanbanView($option = '', $arraydata = null)
1640 {
1641 global $langs;
1642
1643 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1644
1645 $return = '<div class="box-flex-item box-flex-grow-zero">';
1646 $return .= '<div class="info-box info-box-sm">';
1647 $return .= '<span class="info-box-icon bg-infobox-action">';
1648 $return .= img_picto('', $this->picto);
1649 $return .= '</span>';
1650 $return .= '<div class="info-box-content">';
1651 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl() . '</span>';
1652 if ($selected >= 0) {
1653 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1654 }
1655 $arrayofkeyval = $this->fields['bomtype']['arrayofkeyval'] ?? null;
1656 if (!empty($arrayofkeyval)) {
1657 $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Type").' : </span>';
1658 if ($this->bomtype == 0) {
1659 $return .= '<span class="info-box-label">'.$arrayofkeyval[0].'</span>';
1660 } else {
1661 $return .= '<span class="info-box-label">'.$arrayofkeyval[1].'</span>';
1662 }
1663 }
1664 if (!empty($arraydata['prod'])) {
1665 $prod = $arraydata['prod'];
1666 $return .= '<br><span class="info-box-label">'.$prod->getNomUrl(1).'</span>';
1667 }
1668 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1669
1670 $return .= '</div>';
1671 $return .= '</div>';
1672 $return .= '</div>';
1673
1674 return $return;
1675 }
1676}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
$object ref
Definition info.php:90
Class for BOM.
Definition bom.class.php:42
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.
is_photo_available($sdir)
Return if at least one photo is available.
__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.
create(User $user, $notrigger=0)
Create object into database.
validate($user, $notrigger=0)
Validate bom.
reopen($user, $notrigger=0)
Reopen if canceled.
cancel($user, $notrigger=0)
Set cancel status.
LibStatut($status, $mode=0)
Return the status.
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.
deleteLine(User $user, $idline, $notrigger=0)
Delete a line of object in database.
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 optionally 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.
update(User $user, $notrigger=0)
Update object into database.
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.
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with eventually picto)
fetchAll($sortorder='', $sortfield='', $limit=0, $offset=0, $filter='', $filtermode='AND')
Load list of objects in memory from the database.
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=0, $array_options=array(), $fk_default_workstation=null)
Add an BOM line into database (linked to BOM)
getNetNeedsTree(&$TNetNeeds=array(), $qty=0, $level=0)
Get/add Net needs Tree by product or bom.
Class for BOMLine.
Class of dictionary type of thirdparty (used by imports)
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=0)
Create object in the database.
getFieldList($alias='', $excludefields=array())
Function to concat keys of fields.
updateCommon(User $user, $notrigger=0)
Update object into 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.
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.
deleteCommon(User $user, $notrigger=0, $forcechilddeletion=0)
Delete object in database.
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.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:171
convertDurationtoHour($duration_value, $duration_unit)
Convert duration to hour.
Definition date.lib.php:341
dol_is_file($pathoffile)
Return if path is a file.
dol_dir_list($utf8_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:64
dol_now($mode='gmt')
Return date for now.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0)
forgeSQLFromUniversalSearchCriteria
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
if(!function_exists( 'utf8_encode')) if(!function_exists('utf8_decode')) if(!function_exists( 'str_starts_with')) if(!function_exists('str_ends_with')) if(!function_exists( 'str_contains')) getMultidirOutput($object, $module='', $forobject=0, $mode='output')
Return the full path of the directory where a module (or an object of a module) stores its files.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
utf8_check($str)
Check if a string is in UTF8.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.