dolibarr 24.0.0-beta
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-2026 Frédéric France <frederic.france@free.fr>
6 * Copyright (C) 2024-2026 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 $baseurl = DOL_URL_ROOT.'/bom/bom_card.php';
1163
1164 $query = ['id' => $this->id];
1165
1166 if ($option != 'nolink') {
1167 // Add param to save lastsearch_values or not
1168 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1169 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1170 $add_save_lastsearch_values = 1;
1171 }
1172 if ($add_save_lastsearch_values) {
1173 $query = array_merge($query, ['save_lastsearch_values' => 1]);
1174 }
1175 }
1176 $url = dolBuildUrl($baseurl, $query);
1177
1178 $linkclose = '';
1179 if (empty($notooltip)) {
1180 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1181 $label = $langs->trans("ShowBillOfMaterials");
1182 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1183 }
1184 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1185 $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1186 } else {
1187 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1188 }
1189
1190 $linkstart = '<a href="'.$url.'"';
1191 $linkstart .= $linkclose.'>';
1192 $linkend = '</a>';
1193
1194 $result .= $linkstart;
1195 if ($withpicto) {
1196 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1197 }
1198 if ($withpicto != 2) {
1199 $result .= $this->ref;
1200 }
1201 $result .= $linkend;
1202 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1203
1204 global $action, $hookmanager;
1205 $hookmanager->initHooks(array('bomdao'));
1206 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1207 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1208 if ($reshook > 0) {
1209 $result = $hookmanager->resPrint;
1210 } else {
1211 $result .= $hookmanager->resPrint;
1212 }
1213
1214 return $result;
1215 }
1216
1223 public function getLibStatut($mode = 0)
1224 {
1225 return $this->LibStatut($this->status, $mode);
1226 }
1227
1228 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1236 public function LibStatut($status, $mode = 0)
1237 {
1238 // phpcs:enable
1239 if (empty($this->labelStatus)) {
1240 global $langs;
1241 //$langs->load("mrp");
1242 $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1243 $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Enabled');
1244 $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Disabled');
1245 }
1246
1247 $statusType = 'status'.$status;
1248 if ($status == self::STATUS_VALIDATED) {
1249 $statusType = 'status4';
1250 }
1251 if ($status == self::STATUS_CANCELED) {
1252 $statusType = 'status6';
1253 }
1254
1255 return dolGetStatus($this->labelStatus[$status], $this->labelStatus[$status], '', $statusType, $mode);
1256 }
1257
1264 public function info($id)
1265 {
1266 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1267 $sql .= ' fk_user_creat, fk_user_modif';
1268 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1269 $sql .= ' WHERE t.rowid = '.((int) $id);
1270 $result = $this->db->query($sql);
1271 if ($result) {
1272 if ($this->db->num_rows($result)) {
1273 $obj = $this->db->fetch_object($result);
1274
1275 $this->id = $obj->rowid;
1276
1277 $this->user_creation_id = $obj->fk_user_creat;
1278 $this->user_modification_id = $obj->fk_user_modif;
1279 $this->date_creation = $this->db->jdate($obj->datec);
1280 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1281 }
1282
1283 $this->db->free($result);
1284 } else {
1285 dol_print_error($this->db);
1286 }
1287 }
1288
1294 public function getLinesArray()
1295 {
1296 $this->lines = array();
1297
1298 $objectline = new BOMLine($this->db);
1299 $result = $objectline->fetchAll('ASC', 'position', 0, 0, '(fk_bom:=:'.((int) $this->id).')');
1300
1301 if (is_numeric($result)) {
1302 $this->error = $objectline->error;
1303 $this->errors = $objectline->errors;
1304 return $result;
1305 } else {
1306 $this->lines = $result;
1307 return $this->lines;
1308 }
1309 }
1310
1322 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
1323 {
1324 global $langs;
1325
1326 $langs->load("mrp");
1327 $outputlangs->load("products");
1328
1329 if (!dol_strlen($modele)) {
1330 $modele = '';
1331
1332 if ($this->model_pdf) {
1333 $modele = $this->model_pdf;
1334 } elseif (getDolGlobalString('BOM_ADDON_PDF')) {
1335 $modele = getDolGlobalString('BOM_ADDON_PDF');
1336 }
1337 }
1338
1339 $modelpath = "core/modules/bom/doc/";
1340 if (!empty($modele)) {
1341 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
1342 } else {
1343 return 0;
1344 }
1345 }
1346
1347 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1354 public function is_photo_available($sdir)
1355 {
1356 // phpcs:enable
1357 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1358 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
1359
1360 $sdir .= '/'.get_exdir(0, 0, 0, 0, $this, 'bom');
1361
1362 $dir_osencoded = dol_osencode($sdir);
1363 if (file_exists($dir_osencoded)) {
1364 $handle = opendir($dir_osencoded);
1365 if (is_resource($handle)) {
1366 while (($file = readdir($handle)) !== false) {
1367 if (!utf8_check($file)) {
1368 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
1369 }
1370 if (dol_is_file($sdir.$file) && image_format_supported($file) >= 0) {
1371 return true;
1372 }
1373 }
1374 }
1375 }
1376 return false;
1377 }
1378
1385 public function initAsSpecimen()
1386 {
1387 $this->initAsSpecimenCommon();
1388 $this->ref = 'BOM-123';
1389 $this->date_creation = dol_now() - 20000;
1390
1391 return 1;
1392 }
1393
1394
1401 public function calculateCosts()
1402 {
1403 global $hookmanager;
1404
1405 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1406 $this->unit_cost = 0;
1407 $this->total_cost = 0;
1408
1409 $parameters = array();
1410 $reshook = $hookmanager->executeHooks('calculateCostsBom', $parameters, $this); // Note that $action and $object may have been modified by hook
1411
1412 if ($reshook > 0) {
1413 $hookmanager->executeHooks('calculateCostsBomAfter', $parameters, $this); // Note that $action and $object may have been modified by hook
1414 return $hookmanager->resPrint;
1415 }
1416
1417 if (is_array($this->lines) && count($this->lines)) {
1418 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1419 $productFournisseur = new ProductFournisseur($this->db);
1420 $tmpproduct = new Product($this->db);
1421
1422 foreach ($this->lines as &$line) {
1423 $tmpproduct->cost_price = 0;
1424 $tmpproduct->pmp = 0;
1425 $result = $tmpproduct->fetch($line->fk_product, '', '', '', 0, 1, 1); // We discard selling price and language loading
1426
1427 $unit_cost = (float) (is_null($tmpproduct->cost_price) ? $tmpproduct->pmp : $tmpproduct->cost_price);
1428 if (empty($unit_cost)) { // @phpstan-ignore-line phpstan thinks this is always false. No,if unit_cost is 0, it is not.
1429 if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
1430 if ($productFournisseur->fourn_remise_percent != "0") {
1431 $line->unit_cost = $productFournisseur->fourn_unitprice_with_discount;
1432 } else {
1433 $line->unit_cost = $productFournisseur->fourn_unitprice;
1434 }
1435 } else {
1436 $line->unit_cost = 0;
1437 }
1438 } else {
1439 $line->unit_cost = (float) price2num($unit_cost);
1440 }
1441
1442 if ($tmpproduct->type == $tmpproduct::TYPE_PRODUCT) {
1443 if (empty($line->fk_bom_child)) {
1444 if ($result < 0) {
1445 $this->error = $tmpproduct->error;
1446 return -1;
1447 }
1448
1449 $line->total_cost = (float) $line->unit_cost * $line->qty / $line->efficiency;
1450
1451 $this->total_cost += $line->total_cost;
1452 } else {
1453 $bom_child = new BOM($this->db);
1454 $res = $bom_child->fetch((int) $line->fk_bom_child);
1455 if ($res > 0) {
1456 $bom_child->calculateCosts();
1457 $line->childBom[] = $bom_child;
1458 $line->total_cost = (float) $bom_child->unit_cost * $line->qty / $line->efficiency;
1459 $this->total_cost += $line->total_cost;
1460 } else {
1461 $this->error = $bom_child->error;
1462 return -2;
1463 }
1464 }
1465 } else {
1466 // Convert qty of line into hours
1467 require_once DOL_DOCUMENT_ROOT.'/core/class/cunits.class.php';
1468 $measuringUnits = new CUnits($this->db);
1469 $measuringUnits->fetch($line->fk_unit);
1470
1471 // 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
1472 $qtyhourforline = $line->qty * (int) $measuringUnits->scale / 3600;
1473
1474 if (isModEnabled('workstation') && !empty($line->fk_default_workstation)) {
1475 $workstation = new Workstation($this->db);
1476 $res = $workstation->fetch($line->fk_default_workstation);
1477
1478 if ($res > 0) {
1479 $line->total_cost = (float) $qtyhourforline * ($workstation->thm_operator_estimated + $workstation->thm_machine_estimated);
1480 } else {
1481 $this->error = $workstation->error;
1482 return -3;
1483 }
1484 } else {
1485 $defaultdurationofservice = $tmpproduct->duration;
1486 $reg = array();
1487 $qtyhourservice = 0;
1488 if (preg_match('/^(\d+)([a-z]+)$/', $defaultdurationofservice, $reg)) {
1489 $qtyhourservice = convertDurationtoHour((float) $reg[1], $reg[2]);
1490 }
1491
1492 if ($qtyhourservice) {
1493 $line->total_cost = (float) $qtyhourforline / $qtyhourservice * $line->unit_cost;
1494 } else {
1495 $line->total_cost = (float) $line->qty * $line->unit_cost;
1496 }
1497 }
1498
1499 $this->total_cost += $line->total_cost;
1500 }
1501 }
1502
1503 $this->total_cost = (float) price2num($this->total_cost, 'MT');
1504
1505 if ($this->qty > 0) {
1506 $this->unit_cost = (float) price2num($this->total_cost / $this->qty, 'MU');
1507 } elseif ($this->qty < 0) {
1508 $this->unit_cost = (float) price2num($this->total_cost * $this->qty, 'MU');
1509 }
1510 }
1511 $hookmanager->executeHooks('calculateCostsBomAfter', $parameters, $this);
1512
1513 return 1;
1514 }
1515
1524 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
1525 {
1526 $tables = array(
1527 'bom_bomline'
1528 );
1529
1530 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
1531 }
1532
1540 public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
1541 {
1542 if (!empty($this->lines)) {
1543 foreach ($this->lines as $line) {
1544 if (!empty($line->childBom)) {
1545 foreach ($line->childBom as $childBom) {
1546 $childBom->getNetNeeds($TNetNeeds, $line->qty * $qty / $childBom->qty);
1547 }
1548 } else {
1549 if (empty($TNetNeeds[$line->fk_product]['qty'])) {
1550 $TNetNeeds[$line->fk_product]['qty'] = 0.0;
1551 }
1552 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1553 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1554 $TNetNeeds[$line->fk_product]['fk_unit'] = $line->fk_unit;
1555 $TNetNeeds[$line->fk_product]['qty'] += $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 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1578 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1579 //$TNetNeeds[$childBom->id]['fk_unit'] = $line->fk_unit;
1580 $TNetNeeds[$childBom->id]['qty'] = $line->qty * $qty;
1581 $TNetNeeds[$childBom->id]['level'] = $level;
1582 $TNetNeeds[$childBom->id]['position'] = $line->position;
1583 $childBom->getNetNeedsTree($TNetNeeds, $line->qty * $qty / $childBom->qty, $level + 1);
1584 }
1585 } else {
1586 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1587 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1588 if (!isset($TNetNeeds[$this->id]['product'])) {
1589 $TNetNeeds[$this->id]['product'] = array();
1590 }
1591 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product])) {
1592 $TNetNeeds[$this->id]['product'][$line->fk_product] = array();
1593 }
1594 $TNetNeeds[$this->id]['product'][$line->fk_product]['fk_unit'] = $line->fk_unit;
1595 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product]['qty'])) {
1596 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] = 0.0;
1597 }
1598 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
1599 $TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
1600 $TNetNeeds[$this->id]['product'][$line->fk_product]['position'] = $line->position;
1601 }
1602 }
1603 }
1604 }
1605
1614 public function getParentBomTreeRecursive(&$TParentBom, $bom_id = 0, $level = 1)
1615 {
1616 // Protection against infinite loop
1617 if ($level > 1000) {
1618 return;
1619 }
1620
1621 if (empty($bom_id)) {
1622 $bom_id = $this->id;
1623 }
1624
1625 $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";
1626 $sql .= " WHERE fk_bom_child = ".((int) $bom_id);
1627
1628 $resql = $this->db->query($sql);
1629 if (!empty($resql)) {
1630 while ($res = $this->db->fetch_object($resql)) {
1631 $TParentBom[(int) $res->fk_bom] = (int) $res->fk_bom;
1632 $this->getParentBomTreeRecursive($TParentBom, $res->fk_bom, $level + 1);
1633 }
1634 }
1635 }
1636
1644 public function getKanbanView($option = '', $arraydata = null)
1645 {
1646 global $langs;
1647
1648 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1649
1650 $return = '<div class="box-flex-item box-flex-grow-zero">';
1651 $return .= '<div class="info-box info-box-sm">';
1652 $return .= '<span class="info-box-icon bg-infobox-action">';
1653 $return .= img_picto('', $this->picto);
1654 $return .= '</span>';
1655 $return .= '<div class="info-box-content">';
1656 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl() . '</span>';
1657 if ($selected >= 0) {
1658 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1659 }
1660 $arrayofkeyval = $this->fields['bomtype']['arrayofkeyval'] ?? null;
1661 if (!empty($arrayofkeyval)) {
1662 $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Type").' : </span>';
1663 if ($this->bomtype == 0) {
1664 $return .= '<span class="info-box-label">'.$arrayofkeyval[0].'</span>';
1665 } else {
1666 $return .= '<span class="info-box-label">'.$arrayofkeyval[1].'</span>';
1667 }
1668 }
1669 if (!empty($arraydata['prod'])) {
1670 $prod = $arraydata['prod'];
1671 $return .= '<br><span class="info-box-label">'.$prod->getNomUrl(1).'</span>';
1672 }
1673 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1674
1675 $return .= '</div>';
1676 $return .= '</div>';
1677 $return .= '</div>';
1678
1679 return $return;
1680 }
1681}
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:168
convertDurationtoHour($duration_value, $duration_unit)
Convert duration to hour.
Definition date.lib.php:342
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
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 '.
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
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.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
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.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php