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 // Fetch current line from the database and then clone the object and set it in $oldline property
826 $line = new BOMLine($this->db);
827 $line->fetch($idline);
828 if ($this->id > 0 && (int) $line->fk_bom !== (int) $this->id) {
829 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
830 return -1;
831 }
832
833 $this->db->begin();
834 $line->fetch_optionals();
835
836 $staticLine = clone $line;
837 $line->oldcopy = $staticLine;
838 $line->context = $this->context;
839
840 $result = $line->delete($user, $notrigger);
841
842 //Positions (rank) reordering
843 foreach ($this->lines as $bl) {
844 if ($bl->position > ($line->oldcopy->position)) { // move rank down
845 $bl->position--;
846 $bl->update($user);
847 }
848 }
849
850 if ($result > 0) {
851 $this->calculateCosts();
852 $this->db->commit();
853 return $result;
854 } else {
855 $this->setErrorsFromObject($line);
856 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
857 $this->db->rollback();
858 return -2;
859 }
860 }
861
869 public function getNextNumRef($prod)
870 {
871 global $langs, $conf;
872 $langs->load("mrp");
873
874 if (getDolGlobalString('BOM_ADDON')) {
875 $mybool = false;
876
877 $file = getDolGlobalString('BOM_ADDON') . ".php";
878 $classname = getDolGlobalString('BOM_ADDON');
879
880 // Include file with class
881 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
882 foreach ($dirmodels as $reldir) {
883 $dir = dol_buildpath($reldir."core/modules/bom/");
884
885 // Load file with numbering class (if found)
886 $mybool = ((bool) @include_once $dir.$file) || $mybool;
887 }
888
889 if (!$mybool) {
890 dol_print_error(null, "Failed to include file ".$file);
891 return '';
892 }
893
894 $obj = new $classname();
895 '@phan-var-force ModeleNumRefBoms $obj';
896 $numref = $obj->getNextValue($prod, $this);
897
898 if ($numref != "") {
899 return $numref;
900 } else {
901 $this->error = $obj->error;
902 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
903 return "";
904 }
905 } else {
906 print $langs->trans("Error")." ".$langs->trans("Error_BOM_ADDON_NotDefined");
907 return "";
908 }
909 }
910
918 public function validate($user, $notrigger = 0)
919 {
920 global $conf;
921
922 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
923
924 $error = 0;
925
926 // Protection
927 if ($this->status == self::STATUS_VALIDATED) {
928 dol_syslog(get_class($this)."::validate action abandoned: already validated", LOG_WARNING);
929 return 0;
930 }
931
932 $now = dol_now();
933
934 $this->db->begin();
935
936 // Define new ref
937 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
938 $this->fetch_product();
939 $num = $this->getNextNumRef($this->product);
940 } else {
941 $num = $this->ref;
942 }
943 $this->newref = dol_sanitizeFileName($num);
944
945 // Validate
946 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
947 $sql .= " SET ref = '".$this->db->escape($num)."',";
948 $sql .= " status = ".self::STATUS_VALIDATED.",";
949 $sql .= " date_valid='".$this->db->idate($now)."',";
950 $sql .= " fk_user_valid = ".((int) $user->id);
951 $sql .= " WHERE rowid = ".((int) $this->id);
952
953 dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
954 $resql = $this->db->query($sql);
955 if (!$resql) {
956 dol_print_error($this->db);
957 $this->error = $this->db->lasterror();
958 $error++;
959 }
960
961 if (!$error && !$notrigger) {
962 // Call trigger
963 $result = $this->call_trigger('BOM_VALIDATE', $user);
964 if ($result < 0) {
965 $error++;
966 }
967 // End call triggers
968 }
969
970 if (!$error) {
971 $this->oldref = $this->ref;
972
973 // Rename directory if dir was a temporary ref
974 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
975 // Now we rename also files into index
976 $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)."'";
977 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
978 $resql = $this->db->query($sql);
979 if (!$resql) {
980 $error++;
981 $this->error = $this->db->lasterror();
982 }
983 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'bom/".$this->db->escape($this->newref)."'";
984 $sql .= " WHERE filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
985 $resql = $this->db->query($sql);
986 if (!$resql) {
987 $error++;
988 $this->error = $this->db->lasterror();
989 }
990
991 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
992 $oldref = dol_sanitizeFileName($this->ref);
993 $newref = dol_sanitizeFileName($num);
994 $dirsource = $conf->bom->dir_output.'/'.$oldref;
995 $dirdest = $conf->bom->dir_output.'/'.$newref;
996 if (!$error && file_exists($dirsource)) {
997 dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
998
999 if (@rename($dirsource, $dirdest)) {
1000 dol_syslog("Rename ok");
1001 // Rename docs starting with $oldref with $newref
1002 $listoffiles = dol_dir_list($conf->bom->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
1003 foreach ($listoffiles as $fileentry) {
1004 $dirsource = $fileentry['name'];
1005 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
1006 $dirsource = $fileentry['path'].'/'.$dirsource;
1007 $dirdest = $fileentry['path'].'/'.$dirdest;
1008 @rename($dirsource, $dirdest);
1009 }
1010 }
1011 }
1012 }
1013 }
1014
1015 // Set new ref and current status
1016 if (!$error) {
1017 $this->ref = $num;
1018 $this->status = self::STATUS_VALIDATED;
1019 }
1020
1021 if (!$error) {
1022 $this->db->commit();
1023 return 1;
1024 } else {
1025 $this->db->rollback();
1026 return -1;
1027 }
1028 }
1029
1037 public function setDraft($user, $notrigger = 0)
1038 {
1039 // Protection
1040 if ($this->status <= self::STATUS_DRAFT) {
1041 return 0;
1042 }
1043
1044 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'BOM_UNVALIDATE');
1045 }
1046
1054 public function cancel($user, $notrigger = 0)
1055 {
1056 // Protection
1057 if ($this->status != self::STATUS_VALIDATED) {
1058 return 0;
1059 }
1060
1061 return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'BOM_CLOSE');
1062 }
1063
1071 public function reopen($user, $notrigger = 0)
1072 {
1073 // Protection
1074 if ($this->status != self::STATUS_CANCELED) {
1075 return 0;
1076 }
1077
1078 return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'BOM_REOPEN');
1079 }
1080
1087 public function getTooltipContentArray($params)
1088 {
1089 global $conf, $langs, $user;
1090
1091 $langs->loadLangs(['product', 'mrp']);
1092
1093 $datas = [];
1094
1095 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1096 return ['optimize' => $langs->trans("ShowBillOfMaterials")];
1097 }
1098 $picto = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("BillOfMaterials").'</u>';
1099 if (isset($this->status)) {
1100 $picto .= ' '.$this->getLibStatut(5);
1101 }
1102 $datas['picto'] = $picto;
1103 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1104 if (isset($this->label)) {
1105 $datas['label'] = '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
1106 }
1107 if (!empty($this->fk_product) && $this->fk_product > 0) {
1108 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1109 $product = new Product($this->db);
1110 $resultFetch = $product->fetch($this->fk_product);
1111 if ($resultFetch > 0) {
1112 $datas['product'] = "<br><b>".$langs->trans("Product").'</b>: '.$product->ref.' - '.$product->label;
1113 }
1114 }
1115
1116 return $datas;
1117 }
1118
1129 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1130 {
1131 global $db, $conf, $langs, $hookmanager;
1132
1133 if (!empty($conf->dol_no_mouse_hover)) {
1134 $notooltip = 1; // Force disable tooltips
1135 }
1136
1137 $result = '';
1138 $params = [
1139 'id' => $this->id,
1140 'objecttype' => $this->element,
1141 'option' => $option,
1142 ];
1143 $classfortooltip = 'classfortooltip';
1144 $dataparams = '';
1145 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1146 $classfortooltip = 'classforajaxtooltip';
1147 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1148 $label = '';
1149 } else {
1150 $label = implode($this->getTooltipContentArray($params));
1151 }
1152
1153 $url = DOL_URL_ROOT.'/bom/bom_card.php?id='.$this->id;
1154
1155 if ($option != 'nolink') {
1156 // Add param to save lastsearch_values or not
1157 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1158 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1159 $add_save_lastsearch_values = 1;
1160 }
1161 if ($add_save_lastsearch_values) {
1162 $url .= '&save_lastsearch_values=1';
1163 }
1164 }
1165
1166 $linkclose = '';
1167 if (empty($notooltip)) {
1168 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1169 $label = $langs->trans("ShowBillOfMaterials");
1170 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1171 }
1172 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1173 $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1174 } else {
1175 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1176 }
1177
1178 $linkstart = '<a href="'.$url.'"';
1179 $linkstart .= $linkclose.'>';
1180 $linkend = '</a>';
1181
1182 $result .= $linkstart;
1183 if ($withpicto) {
1184 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1185 }
1186 if ($withpicto != 2) {
1187 $result .= $this->ref;
1188 }
1189 $result .= $linkend;
1190 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1191
1192 global $action, $hookmanager;
1193 $hookmanager->initHooks(array('bomdao'));
1194 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1195 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1196 if ($reshook > 0) {
1197 $result = $hookmanager->resPrint;
1198 } else {
1199 $result .= $hookmanager->resPrint;
1200 }
1201
1202 return $result;
1203 }
1204
1211 public function getLibStatut($mode = 0)
1212 {
1213 return $this->LibStatut($this->status, $mode);
1214 }
1215
1216 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1224 public function LibStatut($status, $mode = 0)
1225 {
1226 // phpcs:enable
1227 if (empty($this->labelStatus)) {
1228 global $langs;
1229 //$langs->load("mrp");
1230 $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1231 $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Enabled');
1232 $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Disabled');
1233 }
1234
1235 $statusType = 'status'.$status;
1236 if ($status == self::STATUS_VALIDATED) {
1237 $statusType = 'status4';
1238 }
1239 if ($status == self::STATUS_CANCELED) {
1240 $statusType = 'status6';
1241 }
1242
1243 return dolGetStatus($this->labelStatus[$status], $this->labelStatus[$status], '', $statusType, $mode);
1244 }
1245
1252 public function info($id)
1253 {
1254 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1255 $sql .= ' fk_user_creat, fk_user_modif';
1256 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1257 $sql .= ' WHERE t.rowid = '.((int) $id);
1258 $result = $this->db->query($sql);
1259 if ($result) {
1260 if ($this->db->num_rows($result)) {
1261 $obj = $this->db->fetch_object($result);
1262
1263 $this->id = $obj->rowid;
1264
1265 $this->user_creation_id = $obj->fk_user_creat;
1266 $this->user_modification_id = $obj->fk_user_modif;
1267 $this->date_creation = $this->db->jdate($obj->datec);
1268 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1269 }
1270
1271 $this->db->free($result);
1272 } else {
1273 dol_print_error($this->db);
1274 }
1275 }
1276
1282 public function getLinesArray()
1283 {
1284 $this->lines = array();
1285
1286 $objectline = new BOMLine($this->db);
1287 $result = $objectline->fetchAll('ASC', 'position', 0, 0, '(fk_bom:=:'.((int) $this->id).')');
1288
1289 if (is_numeric($result)) {
1290 $this->error = $objectline->error;
1291 $this->errors = $objectline->errors;
1292 return $result;
1293 } else {
1294 $this->lines = $result;
1295 return $this->lines;
1296 }
1297 }
1298
1310 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
1311 {
1312 global $conf, $langs;
1313
1314 $langs->load("mrp");
1315 $outputlangs->load("products");
1316
1317 if (!dol_strlen($modele)) {
1318 $modele = '';
1319
1320 if ($this->model_pdf) {
1321 $modele = $this->model_pdf;
1322 } elseif (getDolGlobalString('BOM_ADDON_PDF')) {
1323 $modele = getDolGlobalString('BOM_ADDON_PDF');
1324 }
1325 }
1326
1327 $modelpath = "core/modules/bom/doc/";
1328 if (!empty($modele)) {
1329 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
1330 } else {
1331 return 0;
1332 }
1333 }
1334
1335 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1342 public function is_photo_available($sdir)
1343 {
1344 // phpcs:enable
1345 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1346 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
1347
1348 $sdir .= '/'.get_exdir(0, 0, 0, 0, $this, 'bom');
1349
1350 $dir_osencoded = dol_osencode($sdir);
1351 if (file_exists($dir_osencoded)) {
1352 $handle = opendir($dir_osencoded);
1353 if (is_resource($handle)) {
1354 while (($file = readdir($handle)) !== false) {
1355 if (!utf8_check($file)) {
1356 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
1357 }
1358 if (dol_is_file($sdir.$file) && image_format_supported($file) >= 0) {
1359 return true;
1360 }
1361 }
1362 }
1363 }
1364 return false;
1365 }
1366
1373 public function initAsSpecimen()
1374 {
1375 $this->initAsSpecimenCommon();
1376 $this->ref = 'BOM-123';
1377 $this->date_creation = dol_now() - 20000;
1378
1379 return 1;
1380 }
1381
1382
1389 public function doScheduledJob()
1390 {
1391 global $conf, $langs;
1392
1393 //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
1394
1395 $error = 0;
1396 $this->output = '';
1397 $this->error = '';
1398
1399 dol_syslog(__METHOD__, LOG_DEBUG);
1400
1401 $now = dol_now();
1402
1403 $this->db->begin();
1404
1405 // ...
1406
1407 $this->db->commit();
1408
1409 return $error;
1410 }
1411
1418 public function calculateCosts()
1419 {
1420 global $hookmanager;
1421
1422 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1423 $this->unit_cost = 0;
1424 $this->total_cost = 0;
1425
1426 $parameters = array();
1427 $reshook = $hookmanager->executeHooks('calculateCostsBom', $parameters, $this); // Note that $action and $object may have been modified by hook
1428
1429 if ($reshook > 0) {
1430 return $hookmanager->resPrint;
1431 }
1432
1433 if (is_array($this->lines) && count($this->lines)) {
1434 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1435 $productFournisseur = new ProductFournisseur($this->db);
1436 $tmpproduct = new Product($this->db);
1437
1438 foreach ($this->lines as &$line) {
1439 $tmpproduct->cost_price = 0;
1440 $tmpproduct->pmp = 0;
1441 $result = $tmpproduct->fetch($line->fk_product, '', '', '', 0, 1, 1); // We discard selling price and language loading
1442
1443 if ($tmpproduct->type == $tmpproduct::TYPE_PRODUCT) {
1444 if (empty($line->fk_bom_child)) {
1445 if ($result < 0) {
1446 $this->error = $tmpproduct->error;
1447 return -1;
1448 }
1449
1450 $unit_cost = (float) (is_null($tmpproduct->cost_price) ? $tmpproduct->pmp : $tmpproduct->cost_price);
1451 if (empty($unit_cost)) { // @phpstan-ignore-line phpstan thinks this is always false. No,if unit_cost is 0, it is not.
1452 if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
1453 if ($productFournisseur->fourn_remise_percent != "0") {
1454 $line->unit_cost = $productFournisseur->fourn_unitprice_with_discount;
1455 } else {
1456 $line->unit_cost = $productFournisseur->fourn_unitprice;
1457 }
1458 } else {
1459 $line->unit_cost = 0;
1460 }
1461 } else {
1462 $line->unit_cost = (float) $unit_cost;
1463 }
1464
1465 $line->total_cost = (float) $line->qty * $line->unit_cost;
1466
1467 $this->total_cost += $line->total_cost;
1468 } else {
1469 $bom_child = new BOM($this->db);
1470 $res = $bom_child->fetch((int) $line->fk_bom_child);
1471 if ($res > 0) {
1472 $bom_child->calculateCosts();
1473 $line->childBom[] = $bom_child;
1474 $this->total_cost += (float) $bom_child->total_cost * $line->qty;
1475 $this->total_cost += $line->total_cost;
1476 } else {
1477 $this->error = $bom_child->error;
1478 return -2;
1479 }
1480 }
1481 } else {
1482 // Convert qty of line into hours
1483 require_once DOL_DOCUMENT_ROOT.'/core/class/cunits.class.php';
1484 $measuringUnits = new CUnits($this->db);
1485 $measuringUnits->fetch($line->fk_unit);
1486
1487 // 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
1488 $qtyhourforline = $line->qty * (int) $measuringUnits->scale / 3600;
1489
1490 if (isModEnabled('workstation') && !empty($line->fk_default_workstation)) {
1491 $workstation = new Workstation($this->db);
1492 $res = $workstation->fetch($line->fk_default_workstation);
1493
1494 if ($res > 0) {
1495 $line->total_cost = (float) $qtyhourforline * ($workstation->thm_operator_estimated + $workstation->thm_machine_estimated);
1496 } else {
1497 $this->error = $workstation->error;
1498 return -3;
1499 }
1500 } else {
1501 $defaultdurationofservice = $tmpproduct->duration;
1502 $reg = array();
1503 $qtyhourservice = 0;
1504 if (preg_match('/^(\d+)([a-z]+)$/', $defaultdurationofservice, $reg)) {
1505 $qtyhourservice = convertDurationtoHour((float) $reg[1], $reg[2]);
1506 }
1507
1508 if ($qtyhourservice) {
1509 $line->total_cost = (float) $qtyhourforline / $qtyhourservice * $tmpproduct->cost_price;
1510 } else {
1511 $line->total_cost = (float) $line->qty * $tmpproduct->cost_price;
1512 }
1513 }
1514
1515 $this->total_cost += $line->total_cost;
1516 }
1517 }
1518
1519 $this->total_cost = (float) price2num($this->total_cost, 'MT');
1520
1521 if ($this->qty > 0) {
1522 $this->unit_cost = (float) price2num($this->total_cost / $this->qty, 'MU');
1523 } elseif ($this->qty < 0) {
1524 $this->unit_cost = (float) price2num($this->total_cost * $this->qty, 'MU');
1525 }
1526 }
1527
1528 return 1;
1529 }
1530
1539 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
1540 {
1541 $tables = array(
1542 'bom_bomline'
1543 );
1544
1545 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
1546 }
1547
1555 public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
1556 {
1557 if (!empty($this->lines)) {
1558 foreach ($this->lines as $line) {
1559 if (!empty($line->childBom)) {
1560 foreach ($line->childBom as $childBom) {
1561 $childBom->getNetNeeds($TNetNeeds, $line->qty * $qty);
1562 }
1563 } else {
1564 if (empty($TNetNeeds[$line->fk_product]['qty'])) {
1565 $TNetNeeds[$line->fk_product]['qty'] = 0.0;
1566 }
1567 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1568 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1569 $TNetNeeds[$line->fk_product]['fk_unit'] = $line->fk_unit;
1570 $TNetNeeds[$line->fk_product]['qty'] += $line->qty * $qty;
1571 }
1572 }
1573 }
1574 }
1575
1584 public function getNetNeedsTree(&$TNetNeeds = array(), $qty = 0, $level = 0)
1585 {
1586 if (!empty($this->lines)) {
1587 foreach ($this->lines as $line) {
1588 if (!empty($line->childBom)) {
1589 foreach ($line->childBom as $childBom) {
1590 $TNetNeeds[$childBom->id]['bom'] = $childBom;
1591 $TNetNeeds[$childBom->id]['parentid'] = $this->id;
1592 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1593 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1594 //$TNetNeeds[$childBom->id]['fk_unit'] = $line->fk_unit;
1595 $TNetNeeds[$childBom->id]['qty'] = $line->qty * $qty;
1596 $TNetNeeds[$childBom->id]['level'] = $level;
1597 $childBom->getNetNeedsTree($TNetNeeds, $line->qty * $qty, $level + 1);
1598 }
1599 } else {
1600 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1601 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1602 if (!isset($TNetNeeds[$this->id]['product'])) {
1603 $TNetNeeds[$this->id]['product'] = array();
1604 }
1605 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product])) {
1606 $TNetNeeds[$this->id]['product'][$line->fk_product] = array();
1607 }
1608 $TNetNeeds[$this->id]['product'][$line->fk_product]['fk_unit'] = $line->fk_unit;
1609 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product]['qty'])) {
1610 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] = 0.0;
1611 }
1612 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
1613 $TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
1614 }
1615 }
1616 }
1617 }
1618
1627 public function getParentBomTreeRecursive(&$TParentBom, $bom_id = 0, $level = 1)
1628 {
1629
1630 // Protection against infinite loop
1631 if ($level > 1000) {
1632 return;
1633 }
1634
1635 if (empty($bom_id)) {
1636 $bom_id = $this->id;
1637 }
1638
1639 $sql = 'SELECT l.fk_bom, b.label
1640 FROM '.MAIN_DB_PREFIX.'bom_bomline l
1641 INNER JOIN '.MAIN_DB_PREFIX.$this->table_element.' b ON b.rowid = l.fk_bom
1642 WHERE fk_bom_child = '.((int) $bom_id);
1643
1644 $resql = $this->db->query($sql);
1645 if (!empty($resql)) {
1646 while ($res = $this->db->fetch_object($resql)) {
1647 $TParentBom[$res->fk_bom] = $res->fk_bom;
1648 $this->getParentBomTreeRecursive($TParentBom, $res->fk_bom, $level + 1);
1649 }
1650 }
1651 }
1652
1660 public function getKanbanView($option = '', $arraydata = null)
1661 {
1662 global $db,$langs;
1663
1664 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1665
1666 $return = '<div class="box-flex-item box-flex-grow-zero">';
1667 $return .= '<div class="info-box info-box-sm">';
1668 $return .= '<span class="info-box-icon bg-infobox-action">';
1669 $return .= img_picto('', $this->picto);
1670 $return .= '</span>';
1671 $return .= '<div class="info-box-content">';
1672 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : '').'</span>';
1673 if ($selected >= 0) {
1674 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1675 }
1676 $arrayofkeyval = $this->fields['bomtype']['arrayofkeyval'] ?? null;
1677 if (!empty($arrayofkeyval)) {
1678 $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Type").' : </span>';
1679 if ($this->bomtype == 0) {
1680 $return .= '<span class="info-box-label">'.$arrayofkeyval[0].'</span>';
1681 } else {
1682 $return .= '<span class="info-box-label">'.$arrayofkeyval[1].'</span>';
1683 }
1684 }
1685 if (!empty($arraydata['prod'])) {
1686 $prod = $arraydata['prod'];
1687 $return .= '<br><span class="info-box-label">'.$prod->getNomUrl(1).'</span>';
1688 }
1689 if (method_exists($this, 'getLibStatut')) {
1690 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1691 }
1692
1693 $return .= '</div>';
1694 $return .= '</div>';
1695 $return .= '</div>';
1696 return $return;
1697 }
1698}
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