dolibarr 22.0.5
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 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
56 public $table_element = 'bom_bom';
57
61 public $picto = 'bom';
62
66 public $product;
67
68 const STATUS_DRAFT = 0;
69 const STATUS_VALIDATED = 1;
70 const STATUS_CANCELED = 9;
71
72
99 // BEGIN MODULEBUILDER PROPERTIES
103 public $fields = array(
104 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -2, 'position' => 1, 'notnull' => 1, 'index' => 1, 'comment' => "Id",),
105 'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'default' => '1', 'index' => 1, 'position' => 5),
106 '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'),
107 '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'),
108 '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'),
109 //'bomtype' => array('type'=>'integer', 'label'=>'Type', 'enabled'=>1, 'visible'=>-1, 'position'=>32, 'notnull'=>1, 'default'=>'0', 'arrayofkeyval'=>array(0=>'Manufacturing')),
110 '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'),
111 'description' => array('type' => 'text', 'label' => 'Description', 'enabled' => 1, 'visible' => -1, 'position' => 60, 'notnull' => -1,),
112 'qty' => array('type' => 'real', 'label' => 'Quantity', 'enabled' => 1, 'visible' => 1, 'default' => '1', 'position' => 55, 'notnull' => 1, 'isameasure' => 1, 'css' => 'maxwidth50imp right'),
113 //'efficiency' => array('type'=>'real', 'label'=>'ManufacturingEfficiency', 'enabled'=>1, 'visible'=>-1, 'default'=>'1', 'position'=>100, 'notnull'=>0, 'css'=>'maxwidth50imp', 'help'=>'ValueOfMeansLossForProductProduced'),
114 'duration' => array('type' => 'duration', 'label' => 'EstimatedDuration', 'enabled' => 1, 'visible' => -1, 'position' => 101, 'notnull' => -1, 'css' => 'maxwidth50imp', 'help' => 'EstimatedDurationDesc'),
115 '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'),
116 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => -2, 'position' => 161, 'notnull' => -1,),
117 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => -2, 'position' => 162, 'notnull' => -1,),
118 'date_creation' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'position' => 300, 'notnull' => 1,),
119 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -2, 'position' => 501, 'notnull' => 1,),
120 'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -2, 'position' => 502, 'notnull' => 0,),
121 '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'),
122 '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'),
123 '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'),
124 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 1000, 'notnull' => -1,),
125 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'Model pdf', 'enabled' => 1, 'visible' => 0, 'position' => 1010),
126 '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')),
127 );
128
132 public $rowid;
133
137 public $ref;
138
142 public $label;
143
147 public $bomtype;
148
152 public $description;
153
157 public $date_valid;
158
162 public $fk_user_creat;
163
167 public $fk_user_modif;
168
172 public $fk_user_valid;
173
177 public $fk_warehouse;
178
182 public $import_key;
183
187 public $status;
188
192 public $fk_product;
196 public $qty;
200 public $duration;
204 public $efficiency;
205 // END MODULEBUILDER PROPERTIES
206
207
208 // If this object has a subtable with lines
209
213 public $table_element_line = 'bom_bomline';
214
218 public $fk_element = 'fk_bom';
219
223 public $class_element_line = 'BOMLine';
224
225 // /**
226 // * @var array List of child tables. To test if we can delete object.
227 // */
228 // protected $childtables=array();
229
233 protected $childtablesoncascade = array('bom_bomline');
234
238 public $lines = array();
239
243 public $total_cost = 0;
244
248 public $unit_cost = 0;
249
250
256 public function __construct(DoliDB $db)
257 {
258 global $conf, $langs;
259
260 $this->db = $db;
261
262 $this->ismultientitymanaged = 1;
263 $this->isextrafieldmanaged = 1;
264
265 if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid'])) {
266 $this->fields['rowid']['visible'] = 0;
267 }
268 if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
269 $this->fields['entity']['enabled'] = 0;
270 }
271
272 // Unset fields that are disabled
273 foreach ($this->fields as $key => $val) {
274 if (isset($val['enabled']) && empty($val['enabled'])) {
275 unset($this->fields[$key]);
276 }
277 }
278
279 // Translate some data of arrayofkeyval
280 foreach ($this->fields as $key => $val) {
281 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
282 foreach ($val['arrayofkeyval'] as $key2 => $val2) {
283 $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
284 }
285 }
286 }
287 }
288
296 public function create(User $user, $notrigger = 0)
297 {
298 if ($this->efficiency <= 0 || $this->efficiency > 1) {
299 $this->efficiency = 1;
300 }
301
302 return $this->createCommon($user, $notrigger);
303 }
304
312 public function createFromClone(User $user, $fromid)
313 {
314 global $langs, $hookmanager, $extrafields;
315 $error = 0;
316
317 dol_syslog(__METHOD__, LOG_DEBUG);
318
319 $object = new self($this->db);
320
321 $this->db->begin();
322
323 // Load source object
324 $result = $object->fetchCommon($fromid);
325 if ($result > 0 && !empty($object->table_element_line)) {
326 $object->fetchLines();
327 }
328
329 // Get lines so they will be clone
330 //foreach ($object->lines as $line)
331 // $line->fetch_optionals();
332
333 // Reset some properties
334 unset($object->id);
335 unset($object->fk_user_creat);
336 unset($object->import_key);
337 unset($object->date_creation);
338 // Clear fields
339 $default_ref = $this->fields['ref']['default'] ?? null;
340 $object->ref = empty($default_ref) ? $langs->trans("copy_of_").$object->ref : $default_ref;
341 // @phan-suppress-next-line PhanTypeInvalidDimOffset
342 $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf")." ".$object->label : $this->fields['label']['default'];
343 $object->status = self::STATUS_DRAFT;
344 // ...
345 // Clear extrafields that are unique
346 if (is_array($object->array_options) && count($object->array_options) > 0) {
347 $extrafields->fetch_name_optionals_label($object->table_element);
348 foreach ($object->array_options as $key => $option) {
349 $shortkey = preg_replace('/options_/', '', $key);
350 if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) {
351 //var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
352 unset($object->array_options[$key]);
353 }
354 }
355 }
356
357 // Create clone
358 $object->context['createfromclone'] = 'createfromclone';
359 $result = $object->createCommon($user);
360 if ($result < 0) {
361 $error++;
362 $this->error = $object->error;
363 $this->errors = $object->errors;
364 }
365
366 if (!$error) {
367 // copy internal contacts
368 if ($this->copy_linked_contact($object, 'internal') < 0) {
369 $error++;
370 }
371 }
372
373 if (!$error) {
374 // copy external contacts if same company
375 // @phan-suppress-next-line PhanUndeclaredProperty
376 if (property_exists($this, 'socid') && $this->socid == $object->socid) {
377 if ($this->copy_linked_contact($object, 'external') < 0) {
378 $error++;
379 }
380 }
381 }
382
383 // If there is lines, create lines too
384
385
386
387 unset($object->context['createfromclone']);
388
389 // End
390 if (!$error) {
391 $this->db->commit();
392 return $object;
393 } else {
394 $this->db->rollback();
395 return -1;
396 }
397 }
398
406 public function fetch($id, $ref = null)
407 {
408 $result = $this->fetchCommon($id, $ref);
409
410 if ($result > 0 && !empty($this->table_element_line)) {
411 $this->fetchLines();
412 }
413 //$this->calculateCosts(); // This consume a high number of subrequests. Do not call it into fetch but when you need it.
414
415 return $result;
416 }
417
423 public function fetchLines()
424 {
425 $this->lines = array();
426
427 $result = $this->fetchLinesCommon();
428 return $result;
429 }
430
437 public function fetchLinesbytypeproduct($typeproduct = 0)
438 {
439 $this->lines = array();
440
441 $objectlineclassname = get_class($this).'Line';
442 if (!class_exists($objectlineclassname)) {
443 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
444 return -1;
445 }
446
447 $objectline = new $objectlineclassname($this->db);
448
449 '@phan-var-force BOMLine $objectline';
450
451 $sql = "SELECT ".$objectline->getFieldList('l');
452 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
453 $sql .= " LEFT JOIN ".$this->db->prefix()."product as p ON p.rowid = l.fk_product";
454 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
455 $sql .= " AND p.fk_product_type = ". ((int) $typeproduct);
456 if (isset($objectline->fields['position'])) {
457 $sql .= $this->db->order('position', 'ASC');
458 }
459
460 $resql = $this->db->query($sql);
461 if ($resql) {
462 $num_rows = $this->db->num_rows($resql);
463 $i = 0;
464 while ($i < $num_rows) {
465 $obj = $this->db->fetch_object($resql);
466 if ($obj) {
467 $newline = new $objectlineclassname($this->db);
468 '@phan-var-force BOMLine $newline';
469 $newline->setVarsFromFetchObj($obj);
470
471 // Load also extrafields for the line
472 //if (empty($noextrafields)) {
473 $newline->fetch_optionals();
474 //}
475
476 $this->lines[] = $newline;
477 }
478 $i++;
479 }
480
481 return $num_rows;
482 } else {
483 $this->error = $this->db->lasterror();
484 $this->errors[] = $this->error;
485 return -1;
486 }
487 }
488
489
501 public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, $filter = '', $filtermode = 'AND')
502 {
503 dol_syslog(__METHOD__, LOG_DEBUG);
504
505 $records = array();
506
507 $sql = 'SELECT ';
508 $sql .= $this->getFieldList();
509 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
510 if ($this->ismultientitymanaged) {
511 $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
512 } else {
513 $sql .= ' WHERE 1 = 1';
514 }
515
516 // Manage filter
517 $errormessage = '';
518 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
519 if ($errormessage) {
520 $this->errors[] = $errormessage;
521 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
522 return -1;
523 }
524
525 if (!empty($sortfield)) {
526 $sql .= $this->db->order($sortfield, $sortorder);
527 }
528 if (!empty($limit)) {
529 $sql .= $this->db->plimit($limit, $offset);
530 }
531
532 $resql = $this->db->query($sql);
533 if ($resql) {
534 $num = $this->db->num_rows($resql);
535
536 while ($obj = $this->db->fetch_object($resql)) {
537 $record = new self($this->db);
538 $record->setVarsFromFetchObj($obj);
539
540 $records[$record->id] = $record;
541 }
542 $this->db->free($resql);
543
544 return $records;
545 } else {
546 $this->errors[] = 'Error '.$this->db->lasterror();
547 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
548
549 return -1;
550 }
551 }
552
560 public function update(User $user, $notrigger = 0)
561 {
562 if ($this->efficiency <= 0 || $this->efficiency > 1) {
563 $this->efficiency = 1;
564 }
565
566 return $this->updateCommon($user, $notrigger);
567 }
568
576 public function delete(User $user, $notrigger = 1)
577 {
578 return $this->deleteCommon($user, $notrigger);
579 //return $this->deleteCommon($user, $notrigger, 1);
580 }
581
598 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)
599 {
600 global $mysoc, $conf, $langs, $user;
601
602 $logtext = "::addLine bomid=$this->id, qty=$qty, fk_product=$fk_product, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
603 $logtext .= ", fk_bom_child=$fk_bom_child, import_key=$import_key";
604 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
605
606 if ($this->statut == self::STATUS_DRAFT) {
607 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
608
609 // Clean parameters
610 if (empty($qty)) {
611 $qty = 0;
612 }
613 if (empty($qty_frozen)) {
614 $qty_frozen = 0;
615 }
616 if (empty($disable_stock_change)) {
617 $disable_stock_change = 0;
618 }
619 if (empty($efficiency)) {
620 $efficiency = 1.0;
621 }
622 if (empty($fk_bom_child)) {
623 $fk_bom_child = null;
624 }
625 if (empty($import_key)) {
626 $import_key = '';
627 }
628 if (empty($position)) {
629 $position = -1;
630 }
631
632 $qty = (float) price2num($qty);
633 $efficiency = (float) price2num($efficiency);
634 $position = (float) price2num($position);
635
636 $this->db->begin();
637
638 // Rank to use
639 $rangMax = $this->line_max();
640 $rankToUse = $position;
641 if ($rankToUse <= 0 or $rankToUse > $rangMax) { // New line after existing lines
642 $rankToUse = $rangMax + 1;
643 } else { // New line between the existing lines
644 foreach ($this->lines as $bl) {
645 if ($bl->position >= $rankToUse) {
646 $bl->position++;
647 $bl->update($user);
648 }
649 }
650 }
651
652 // Insert line
653 $line = new BOMLine($this->db);
654
655 $line->context = $this->context;
656
657 $line->fk_bom = $this->id;
658 $line->fk_product = $fk_product;
659 $line->qty = $qty;
660 $line->qty_frozen = $qty_frozen;
661 $line->disable_stock_change = $disable_stock_change;
662 $line->efficiency = $efficiency;
663 $line->fk_bom_child = $fk_bom_child;
664 $line->import_key = $import_key;
665 $line->position = $rankToUse;
666 $line->fk_unit = $fk_unit;
667 $line->fk_default_workstation = $fk_default_workstation;
668
669 if (is_array($array_options) && count($array_options) > 0) {
670 $line->array_options = $array_options;
671 }
672
673 $result = $line->create($user);
674
675 if ($result > 0) {
676 $this->calculateCosts();
677 $this->db->commit();
678 return $result;
679 } else {
680 $this->setErrorsFromObject($line);
681 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
682 $this->db->rollback();
683 return -2;
684 }
685 } else {
686 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
687 return -3;
688 }
689 }
690
706 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)
707 {
708 global $user;
709
710 $logtext = "::updateLine bomid=$this->id, qty=$qty, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
711 $logtext .= ", import_key=$import_key";
712 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
713
714 if ($this->statut == self::STATUS_DRAFT) {
715 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
716
717 // Clean parameters
718 if (empty($qty)) {
719 $qty = 0;
720 }
721 if (empty($qty_frozen)) {
722 $qty_frozen = 0;
723 }
724 if (empty($disable_stock_change)) {
725 $disable_stock_change = 0;
726 }
727 if (empty($efficiency)) {
728 $efficiency = 1.0;
729 }
730 if (empty($import_key)) {
731 $import_key = '';
732 }
733 if (empty($position)) {
734 $position = -1;
735 }
736
737 $qty = (float) price2num($qty);
738 $efficiency = (float) price2num($efficiency);
739 $position = (float) price2num($position);
740
741 $this->db->begin();
742
743 // Fetch current line from the database and then clone the object and set it in $oldline property
744 $line = new BOMLine($this->db);
745 $line->fetch($rowid);
746 $line->fetch_optionals();
747
748 $staticLine = clone $line;
749 $line->oldcopy = $staticLine;
750 $line->context = $this->context;
751
752 // Rank to use
753 $rankToUse = (int) $position;
754 if ($rankToUse != $line->oldcopy->position) { // check if position have a new value
755 foreach ($this->lines as $bl) {
756 if ($bl->position >= $rankToUse and $bl->position < ($line->oldcopy->position + 1)) { // move rank up
757 $bl->position++;
758 $bl->update($user);
759 }
760 if ($bl->position <= $rankToUse and $bl->position > ($line->oldcopy->position)) { // move rank down
761 $bl->position--;
762 $bl->update($user);
763 }
764 }
765 }
766
767
768 $line->fk_bom = $this->id;
769 $line->qty = $qty;
770 $line->qty_frozen = $qty_frozen;
771 $line->disable_stock_change = $disable_stock_change;
772 $line->efficiency = $efficiency;
773 $line->import_key = $import_key;
774 $line->position = $rankToUse;
775
776
777 if (!empty($fk_unit)) {
778 $line->fk_unit = $fk_unit;
779 }
780
781
782 if (is_array($array_options) && count($array_options) > 0) {
783 // We replace values in this->line->array_options only for entries defined into $array_options
784 foreach ($array_options as $key => $value) {
785 $line->array_options[$key] = $array_options[$key];
786 }
787 }
788 if ($line->fk_default_workstation != $fk_default_workstation) {
789 $line->fk_default_workstation = ($fk_default_workstation > 0 ? $fk_default_workstation : 0);
790 }
791
792 $result = $line->update($user);
793
794 if ($result > 0) {
795 $this->calculateCosts();
796 $this->db->commit();
797 return $result;
798 } else {
799 $this->setErrorsFromObject($line);
800 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
801 $this->db->rollback();
802 return -2;
803 }
804 } else {
805 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
806 return -3;
807 }
808 }
809
818 public function deleteLine(User $user, $idline, $notrigger = 0)
819 {
820 if ($this->status < 0) {
821 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
822 return -2;
823 }
824
825 $this->db->begin();
826
827 // Fetch current line from the database and then clone the object and set it in $oldline property
828 $line = new BOMLine($this->db);
829 $line->fetch($idline);
830 $line->fetch_optionals();
831
832 $staticLine = clone $line;
833 $line->oldcopy = $staticLine;
834 $line->context = $this->context;
835
836 $result = $line->delete($user, $notrigger);
837
838 //Positions (rank) reordering
839 foreach ($this->lines as $bl) {
840 if ($bl->position > ($line->oldcopy->position)) { // move rank down
841 $bl->position--;
842 $bl->update($user);
843 }
844 }
845
846 if ($result > 0) {
847 $this->calculateCosts();
848 $this->db->commit();
849 return $result;
850 } else {
851 $this->setErrorsFromObject($line);
852 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
853 $this->db->rollback();
854 return -2;
855 }
856 }
857
865 public function getNextNumRef($prod)
866 {
867 global $langs, $conf;
868 $langs->load("mrp");
869
870 if (getDolGlobalString('BOM_ADDON')) {
871 $mybool = false;
872
873 $file = getDolGlobalString('BOM_ADDON') . ".php";
874 $classname = getDolGlobalString('BOM_ADDON');
875
876 // Include file with class
877 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
878 foreach ($dirmodels as $reldir) {
879 $dir = dol_buildpath($reldir."core/modules/bom/");
880
881 // Load file with numbering class (if found)
882 $mybool = ((bool) @include_once $dir.$file) || $mybool;
883 }
884
885 if (!$mybool) {
886 dol_print_error(null, "Failed to include file ".$file);
887 return '';
888 }
889
890 $obj = new $classname();
891 '@phan-var-force ModeleNumRefBoms $obj';
892 $numref = $obj->getNextValue($prod, $this);
893
894 if ($numref != "") {
895 return $numref;
896 } else {
897 $this->error = $obj->error;
898 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
899 return "";
900 }
901 } else {
902 print $langs->trans("Error")." ".$langs->trans("Error_BOM_ADDON_NotDefined");
903 return "";
904 }
905 }
906
914 public function validate($user, $notrigger = 0)
915 {
916 global $conf;
917
918 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
919
920 $error = 0;
921
922 // Protection
923 if ($this->status == self::STATUS_VALIDATED) {
924 dol_syslog(get_class($this)."::validate action abandoned: already validated", LOG_WARNING);
925 return 0;
926 }
927
928 $now = dol_now();
929
930 $this->db->begin();
931
932 // Define new ref
933 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
934 $this->fetch_product();
935 $num = $this->getNextNumRef($this->product);
936 } else {
937 $num = $this->ref;
938 }
939 $this->newref = dol_sanitizeFileName($num);
940
941 // Validate
942 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
943 $sql .= " SET ref = '".$this->db->escape($num)."',";
944 $sql .= " status = ".self::STATUS_VALIDATED.",";
945 $sql .= " date_valid='".$this->db->idate($now)."',";
946 $sql .= " fk_user_valid = ".((int) $user->id);
947 $sql .= " WHERE rowid = ".((int) $this->id);
948
949 dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
950 $resql = $this->db->query($sql);
951 if (!$resql) {
952 dol_print_error($this->db);
953 $this->error = $this->db->lasterror();
954 $error++;
955 }
956
957 if (!$error && !$notrigger) {
958 // Call trigger
959 $result = $this->call_trigger('BOM_VALIDATE', $user);
960 if ($result < 0) {
961 $error++;
962 }
963 // End call triggers
964 }
965
966 if (!$error) {
967 $this->oldref = $this->ref;
968
969 // Rename directory if dir was a temporary ref
970 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
971 // Now we rename also files into index
972 $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)."'";
973 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
974 $resql = $this->db->query($sql);
975 if (!$resql) {
976 $error++;
977 $this->error = $this->db->lasterror();
978 }
979 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'bom/".$this->db->escape($this->newref)."'";
980 $sql .= " WHERE filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
981 $resql = $this->db->query($sql);
982 if (!$resql) {
983 $error++;
984 $this->error = $this->db->lasterror();
985 }
986
987 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
988 $oldref = dol_sanitizeFileName($this->ref);
989 $newref = dol_sanitizeFileName($num);
990 $dirsource = $conf->bom->dir_output.'/'.$oldref;
991 $dirdest = $conf->bom->dir_output.'/'.$newref;
992 if (!$error && file_exists($dirsource)) {
993 dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
994
995 if (@rename($dirsource, $dirdest)) {
996 dol_syslog("Rename ok");
997 // Rename docs starting with $oldref with $newref
998 $listoffiles = dol_dir_list($conf->bom->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
999 foreach ($listoffiles as $fileentry) {
1000 $dirsource = $fileentry['name'];
1001 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
1002 $dirsource = $fileentry['path'].'/'.$dirsource;
1003 $dirdest = $fileentry['path'].'/'.$dirdest;
1004 @rename($dirsource, $dirdest);
1005 }
1006 }
1007 }
1008 }
1009 }
1010
1011 // Set new ref and current status
1012 if (!$error) {
1013 $this->ref = $num;
1014 $this->status = self::STATUS_VALIDATED;
1015 }
1016
1017 if (!$error) {
1018 $this->db->commit();
1019 return 1;
1020 } else {
1021 $this->db->rollback();
1022 return -1;
1023 }
1024 }
1025
1033 public function setDraft($user, $notrigger = 0)
1034 {
1035 // Protection
1036 if ($this->status <= self::STATUS_DRAFT) {
1037 return 0;
1038 }
1039
1040 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'BOM_UNVALIDATE');
1041 }
1042
1050 public function cancel($user, $notrigger = 0)
1051 {
1052 // Protection
1053 if ($this->status != self::STATUS_VALIDATED) {
1054 return 0;
1055 }
1056
1057 return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'BOM_CLOSE');
1058 }
1059
1067 public function reopen($user, $notrigger = 0)
1068 {
1069 // Protection
1070 if ($this->status != self::STATUS_CANCELED) {
1071 return 0;
1072 }
1073
1074 return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'BOM_REOPEN');
1075 }
1076
1083 public function getTooltipContentArray($params)
1084 {
1085 global $conf, $langs, $user;
1086
1087 $langs->loadLangs(['product', 'mrp']);
1088
1089 $datas = [];
1090
1091 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1092 return ['optimize' => $langs->trans("ShowBillOfMaterials")];
1093 }
1094 $picto = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("BillOfMaterials").'</u>';
1095 if (isset($this->status)) {
1096 $picto .= ' '.$this->getLibStatut(5);
1097 }
1098 $datas['picto'] = $picto;
1099 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1100 if (isset($this->label)) {
1101 $datas['label'] = '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
1102 }
1103 if (!empty($this->fk_product) && $this->fk_product > 0) {
1104 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1105 $product = new Product($this->db);
1106 $resultFetch = $product->fetch($this->fk_product);
1107 if ($resultFetch > 0) {
1108 $datas['product'] = "<br><b>".$langs->trans("Product").'</b>: '.$product->ref.' - '.$product->label;
1109 }
1110 }
1111
1112 return $datas;
1113 }
1114
1125 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1126 {
1127 global $db, $conf, $langs, $hookmanager;
1128
1129 if (!empty($conf->dol_no_mouse_hover)) {
1130 $notooltip = 1; // Force disable tooltips
1131 }
1132
1133 $result = '';
1134 $params = [
1135 'id' => $this->id,
1136 'objecttype' => $this->element,
1137 'option' => $option,
1138 ];
1139 $classfortooltip = 'classfortooltip';
1140 $dataparams = '';
1141 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1142 $classfortooltip = 'classforajaxtooltip';
1143 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1144 $label = '';
1145 } else {
1146 $label = implode($this->getTooltipContentArray($params));
1147 }
1148
1149 $url = DOL_URL_ROOT.'/bom/bom_card.php?id='.$this->id;
1150
1151 if ($option != 'nolink') {
1152 // Add param to save lastsearch_values or not
1153 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1154 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1155 $add_save_lastsearch_values = 1;
1156 }
1157 if ($add_save_lastsearch_values) {
1158 $url .= '&save_lastsearch_values=1';
1159 }
1160 }
1161
1162 $linkclose = '';
1163 if (empty($notooltip)) {
1164 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1165 $label = $langs->trans("ShowBillOfMaterials");
1166 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1167 }
1168 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1169 $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1170 } else {
1171 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1172 }
1173
1174 $linkstart = '<a href="'.$url.'"';
1175 $linkstart .= $linkclose.'>';
1176 $linkend = '</a>';
1177
1178 $result .= $linkstart;
1179 if ($withpicto) {
1180 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1181 }
1182 if ($withpicto != 2) {
1183 $result .= $this->ref;
1184 }
1185 $result .= $linkend;
1186 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1187
1188 global $action, $hookmanager;
1189 $hookmanager->initHooks(array('bomdao'));
1190 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1191 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1192 if ($reshook > 0) {
1193 $result = $hookmanager->resPrint;
1194 } else {
1195 $result .= $hookmanager->resPrint;
1196 }
1197
1198 return $result;
1199 }
1200
1207 public function getLibStatut($mode = 0)
1208 {
1209 return $this->LibStatut($this->status, $mode);
1210 }
1211
1212 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1220 public function LibStatut($status, $mode = 0)
1221 {
1222 // phpcs:enable
1223 if (empty($this->labelStatus)) {
1224 global $langs;
1225 //$langs->load("mrp");
1226 $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1227 $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Enabled');
1228 $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Disabled');
1229 }
1230
1231 $statusType = 'status'.$status;
1232 if ($status == self::STATUS_VALIDATED) {
1233 $statusType = 'status4';
1234 }
1235 if ($status == self::STATUS_CANCELED) {
1236 $statusType = 'status6';
1237 }
1238
1239 return dolGetStatus($this->labelStatus[$status], $this->labelStatus[$status], '', $statusType, $mode);
1240 }
1241
1248 public function info($id)
1249 {
1250 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1251 $sql .= ' fk_user_creat, fk_user_modif';
1252 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1253 $sql .= ' WHERE t.rowid = '.((int) $id);
1254 $result = $this->db->query($sql);
1255 if ($result) {
1256 if ($this->db->num_rows($result)) {
1257 $obj = $this->db->fetch_object($result);
1258
1259 $this->id = $obj->rowid;
1260
1261 $this->user_creation_id = $obj->fk_user_creat;
1262 $this->user_modification_id = $obj->fk_user_modif;
1263 $this->date_creation = $this->db->jdate($obj->datec);
1264 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1265 }
1266
1267 $this->db->free($result);
1268 } else {
1269 dol_print_error($this->db);
1270 }
1271 }
1272
1278 public function getLinesArray()
1279 {
1280 $this->lines = array();
1281
1282 $objectline = new BOMLine($this->db);
1283 $result = $objectline->fetchAll('ASC', 'position', 0, 0, '(fk_bom:=:'.((int) $this->id).')');
1284
1285 if (is_numeric($result)) {
1286 $this->error = $objectline->error;
1287 $this->errors = $objectline->errors;
1288 return $result;
1289 } else {
1290 $this->lines = $result;
1291 return $this->lines;
1292 }
1293 }
1294
1306 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
1307 {
1308 global $conf, $langs;
1309
1310 $langs->load("mrp");
1311 $outputlangs->load("products");
1312
1313 if (!dol_strlen($modele)) {
1314 $modele = '';
1315
1316 if ($this->model_pdf) {
1317 $modele = $this->model_pdf;
1318 } elseif (getDolGlobalString('BOM_ADDON_PDF')) {
1319 $modele = getDolGlobalString('BOM_ADDON_PDF');
1320 }
1321 }
1322
1323 $modelpath = "core/modules/bom/doc/";
1324 if (!empty($modele)) {
1325 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
1326 } else {
1327 return 0;
1328 }
1329 }
1330
1331 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1338 public function is_photo_available($sdir)
1339 {
1340 // phpcs:enable
1341 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1342 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
1343
1344 $sdir .= '/'.get_exdir(0, 0, 0, 0, $this, 'bom');
1345
1346 $dir_osencoded = dol_osencode($sdir);
1347 if (file_exists($dir_osencoded)) {
1348 $handle = opendir($dir_osencoded);
1349 if (is_resource($handle)) {
1350 while (($file = readdir($handle)) !== false) {
1351 if (!utf8_check($file)) {
1352 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
1353 }
1354 if (dol_is_file($sdir.$file) && image_format_supported($file) >= 0) {
1355 return true;
1356 }
1357 }
1358 }
1359 }
1360 return false;
1361 }
1362
1369 public function initAsSpecimen()
1370 {
1371 $this->initAsSpecimenCommon();
1372 $this->ref = 'BOM-123';
1373 $this->date_creation = dol_now() - 20000;
1374
1375 return 1;
1376 }
1377
1378
1385 public function doScheduledJob()
1386 {
1387 global $conf, $langs;
1388
1389 //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
1390
1391 $error = 0;
1392 $this->output = '';
1393 $this->error = '';
1394
1395 dol_syslog(__METHOD__, LOG_DEBUG);
1396
1397 $now = dol_now();
1398
1399 $this->db->begin();
1400
1401 // ...
1402
1403 $this->db->commit();
1404
1405 return $error;
1406 }
1407
1414 public function calculateCosts()
1415 {
1416 global $hookmanager;
1417
1418 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1419 $this->unit_cost = 0;
1420 $this->total_cost = 0;
1421
1422 $parameters = array();
1423 $reshook = $hookmanager->executeHooks('calculateCostsBom', $parameters, $this); // Note that $action and $object may have been modified by hook
1424
1425 if ($reshook > 0) {
1426 return $hookmanager->resPrint;
1427 }
1428
1429 if (is_array($this->lines) && count($this->lines)) {
1430 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1431 $productFournisseur = new ProductFournisseur($this->db);
1432 $tmpproduct = new Product($this->db);
1433
1434 foreach ($this->lines as &$line) {
1435 $tmpproduct->cost_price = 0;
1436 $tmpproduct->pmp = 0;
1437 $result = $tmpproduct->fetch($line->fk_product, '', '', '', 0, 1, 1); // We discard selling price and language loading
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 $unit_cost = (float) (is_null($tmpproduct->cost_price) ? $tmpproduct->pmp : $tmpproduct->cost_price);
1447 if (empty($unit_cost)) { // @phpstan-ignore-line phpstan thinks this is always false. No,if unit_cost is 0, it is not.
1448 if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
1449 if ($productFournisseur->fourn_remise_percent != "0") {
1450 $line->unit_cost = $productFournisseur->fourn_unitprice_with_discount;
1451 } else {
1452 $line->unit_cost = $productFournisseur->fourn_unitprice;
1453 }
1454 } else {
1455 $line->unit_cost = 0;
1456 }
1457 } else {
1458 $line->unit_cost = (float) $unit_cost;
1459 }
1460
1461 $line->total_cost = (float) $line->qty * $line->unit_cost;
1462
1463 $this->total_cost += $line->total_cost;
1464 } else {
1465 $bom_child = new BOM($this->db);
1466 $res = $bom_child->fetch((int) $line->fk_bom_child);
1467 if ($res > 0) {
1468 $bom_child->calculateCosts();
1469 $line->childBom[] = $bom_child;
1470 $this->total_cost += (float) $bom_child->total_cost * $line->qty;
1471 $this->total_cost += $line->total_cost;
1472 } else {
1473 $this->error = $bom_child->error;
1474 return -2;
1475 }
1476 }
1477 } else {
1478 // Convert qty of line into hours
1479 require_once DOL_DOCUMENT_ROOT.'/core/class/cunits.class.php';
1480 $measuringUnits = new CUnits($this->db);
1481 $measuringUnits->fetch($line->fk_unit);
1482
1483 // 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
1484 $qtyhourforline = $line->qty * (int) $measuringUnits->scale / 3600;
1485
1486 if (isModEnabled('workstation') && !empty($line->fk_default_workstation)) {
1487 $workstation = new Workstation($this->db);
1488 $res = $workstation->fetch($line->fk_default_workstation);
1489
1490 if ($res > 0) {
1491 $line->total_cost = (float) $qtyhourforline * ($workstation->thm_operator_estimated + $workstation->thm_machine_estimated);
1492 } else {
1493 $this->error = $workstation->error;
1494 return -3;
1495 }
1496 } else {
1497 $defaultdurationofservice = $tmpproduct->duration;
1498 $reg = array();
1499 $qtyhourservice = 0;
1500 if (preg_match('/^(\d+)([a-z]+)$/', $defaultdurationofservice, $reg)) {
1501 $qtyhourservice = convertDurationtoHour((float) $reg[1], $reg[2]);
1502 }
1503
1504 if ($qtyhourservice) {
1505 $line->total_cost = (float) $qtyhourforline / $qtyhourservice * $tmpproduct->cost_price;
1506 } else {
1507 $line->total_cost = (float) $line->qty * $tmpproduct->cost_price;
1508 }
1509 }
1510
1511 $this->total_cost += $line->total_cost;
1512 }
1513 }
1514
1515 $this->total_cost = (float) price2num($this->total_cost, 'MT');
1516
1517 if ($this->qty > 0) {
1518 $this->unit_cost = (float) price2num($this->total_cost / $this->qty, 'MU');
1519 } elseif ($this->qty < 0) {
1520 $this->unit_cost = (float) price2num($this->total_cost * $this->qty, 'MU');
1521 }
1522 }
1523
1524 return 1;
1525 }
1526
1535 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
1536 {
1537 $tables = array(
1538 'bom_bomline'
1539 );
1540
1541 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
1542 }
1543
1551 public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
1552 {
1553 if (!empty($this->lines)) {
1554 foreach ($this->lines as $line) {
1555 if (!empty($line->childBom)) {
1556 foreach ($line->childBom as $childBom) {
1557 $childBom->getNetNeeds($TNetNeeds, $line->qty * $qty);
1558 }
1559 } else {
1560 if (empty($TNetNeeds[$line->fk_product]['qty'])) {
1561 $TNetNeeds[$line->fk_product]['qty'] = 0.0;
1562 }
1563 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1564 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1565 $TNetNeeds[$line->fk_product]['fk_unit'] = $line->fk_unit;
1566 $TNetNeeds[$line->fk_product]['qty'] += $line->qty * $qty;
1567 }
1568 }
1569 }
1570 }
1571
1580 public function getNetNeedsTree(&$TNetNeeds = array(), $qty = 0, $level = 0)
1581 {
1582 if (!empty($this->lines)) {
1583 foreach ($this->lines as $line) {
1584 if (!empty($line->childBom)) {
1585 foreach ($line->childBom as $childBom) {
1586 $TNetNeeds[$childBom->id]['bom'] = $childBom;
1587 $TNetNeeds[$childBom->id]['parentid'] = $this->id;
1588 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1589 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1590 //$TNetNeeds[$childBom->id]['fk_unit'] = $line->fk_unit;
1591 $TNetNeeds[$childBom->id]['qty'] = $line->qty * $qty;
1592 $TNetNeeds[$childBom->id]['level'] = $level;
1593 $childBom->getNetNeedsTree($TNetNeeds, $line->qty * $qty, $level + 1);
1594 }
1595 } else {
1596 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1597 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1598 if (!isset($TNetNeeds[$this->id]['product'])) {
1599 $TNetNeeds[$this->id]['product'] = array();
1600 }
1601 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product])) {
1602 $TNetNeeds[$this->id]['product'][$line->fk_product] = array();
1603 }
1604 $TNetNeeds[$this->id]['product'][$line->fk_product]['fk_unit'] = $line->fk_unit;
1605 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product]['qty'])) {
1606 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] = 0.0;
1607 }
1608 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
1609 $TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
1610 }
1611 }
1612 }
1613 }
1614
1623 public function getParentBomTreeRecursive(&$TParentBom, $bom_id = 0, $level = 1)
1624 {
1625
1626 // Protection against infinite loop
1627 if ($level > 1000) {
1628 return;
1629 }
1630
1631 if (empty($bom_id)) {
1632 $bom_id = $this->id;
1633 }
1634
1635 $sql = 'SELECT l.fk_bom, b.label
1636 FROM '.MAIN_DB_PREFIX.'bom_bomline l
1637 INNER JOIN '.MAIN_DB_PREFIX.$this->table_element.' b ON b.rowid = l.fk_bom
1638 WHERE fk_bom_child = '.((int) $bom_id);
1639
1640 $resql = $this->db->query($sql);
1641 if (!empty($resql)) {
1642 while ($res = $this->db->fetch_object($resql)) {
1643 $TParentBom[$res->fk_bom] = $res->fk_bom;
1644 $this->getParentBomTreeRecursive($TParentBom, $res->fk_bom, $level + 1);
1645 }
1646 }
1647 }
1648
1656 public function getKanbanView($option = '', $arraydata = null)
1657 {
1658 global $db,$langs;
1659
1660 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1661
1662 $return = '<div class="box-flex-item box-flex-grow-zero">';
1663 $return .= '<div class="info-box info-box-sm">';
1664 $return .= '<span class="info-box-icon bg-infobox-action">';
1665 $return .= img_picto('', $this->picto);
1666 $return .= '</span>';
1667 $return .= '<div class="info-box-content">';
1668 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : '').'</span>';
1669 if ($selected >= 0) {
1670 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1671 }
1672 $arrayofkeyval = $this->fields['bomtype']['arrayofkeyval'] ?? null;
1673 if (!empty($arrayofkeyval)) {
1674 $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Type").' : </span>';
1675 if ($this->bomtype == 0) {
1676 $return .= '<span class="info-box-label">'.$arrayofkeyval[0].'</span>';
1677 } else {
1678 $return .= '<span class="info-box-label">'.$arrayofkeyval[1].'</span>';
1679 }
1680 }
1681 if (!empty($arraydata['prod'])) {
1682 $prod = $arraydata['prod'];
1683 $return .= '<br><span class="info-box-label">'.$prod->getNomUrl(1).'</span>';
1684 }
1685 if (method_exists($this, 'getLibStatut')) {
1686 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1687 }
1688
1689 $return .= '</div>';
1690 $return .= '</div>';
1691 $return .= '</div>';
1692 return $return;
1693 }
1694}
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:67
$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.
doScheduledJob()
Action executed by scheduler CAN BE A CRON TASK.
getParentBomTreeRecursive(&$TParentBom, $bom_id=0, $level=1)
Recursively retrieves all parent bom in the tree that leads to the $bom_id bom.
getTooltipContentArray($params)
getTooltipContentArray
fetchLinesbytypeproduct($typeproduct=0)
Load object lines in memory from the database by type of product.
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.
getNextNumRef($prod)
Returns the reference to the following non used BOM depending on the active numbering module defined ...
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.
call_trigger($triggerName, $user)
Call trigger based on this instance.
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:338
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:63
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_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0)
forgeSQLFromUniversalSearchCriteria
dol_now($mode='auto')
Return date for now.
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.
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...
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0)
Clean a string to use it as a file name.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
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.
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79