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