dolibarr 21.0.0-beta
bom.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2019 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2023 Benjamin Falière <benjamin.faliere@altairis.fr>
4 * Copyright (C) 2023 Charlene Benke <charlene@patas-monkey.com>
5 * Copyright (C) 2024 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
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 = 1)
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
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
438 public function fetchLinesbytypeproduct($typeproduct = 0)
439 {
440 $this->lines = array();
441
442 $objectlineclassname = get_class($this).'Line';
443 if (!class_exists($objectlineclassname)) {
444 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
445 return -1;
446 }
447
448 $objectline = new $objectlineclassname($this->db);
449
450 '@phan-var-force BOMLine $objectline';
451
452 $sql = "SELECT ".$objectline->getFieldList('l');
453 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
454 $sql .= " LEFT JOIN ".$this->db->prefix()."product as p ON p.rowid = l.fk_product";
455 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
456 $sql .= " AND p.fk_product_type = ". ((int) $typeproduct);
457 if (isset($objectline->fields['position'])) {
458 $sql .= $this->db->order('position', 'ASC');
459 }
460
461 $resql = $this->db->query($sql);
462 if ($resql) {
463 $num_rows = $this->db->num_rows($resql);
464 $i = 0;
465 while ($i < $num_rows) {
466 $obj = $this->db->fetch_object($resql);
467 if ($obj) {
468 $newline = new $objectlineclassname($this->db);
469 '@phan-var-force BOMLine $newline';
470 $newline->setVarsFromFetchObj($obj);
471
472 $this->lines[] = $newline;
473 }
474 $i++;
475 }
476
477 return $num_rows;
478 } else {
479 $this->error = $this->db->lasterror();
480 $this->errors[] = $this->error;
481 return -1;
482 }
483 }
484
485
497 public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, $filter = '', $filtermode = 'AND')
498 {
499 dol_syslog(__METHOD__, LOG_DEBUG);
500
501 $records = array();
502
503 $sql = 'SELECT ';
504 $sql .= $this->getFieldList();
505 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
506 if ($this->ismultientitymanaged) {
507 $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
508 } else {
509 $sql .= ' WHERE 1 = 1';
510 }
511
512 // Manage filter
513 $errormessage = '';
514 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
515 if ($errormessage) {
516 $this->errors[] = $errormessage;
517 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
518 return -1;
519 }
520
521 if (!empty($sortfield)) {
522 $sql .= $this->db->order($sortfield, $sortorder);
523 }
524 if (!empty($limit)) {
525 $sql .= $this->db->plimit($limit, $offset);
526 }
527
528 $resql = $this->db->query($sql);
529 if ($resql) {
530 $num = $this->db->num_rows($resql);
531
532 while ($obj = $this->db->fetch_object($resql)) {
533 $record = new self($this->db);
534 $record->setVarsFromFetchObj($obj);
535
536 $records[$record->id] = $record;
537 }
538 $this->db->free($resql);
539
540 return $records;
541 } else {
542 $this->errors[] = 'Error '.$this->db->lasterror();
543 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
544
545 return -1;
546 }
547 }
548
556 public function update(User $user, $notrigger = 1)
557 {
558 if ($this->efficiency <= 0 || $this->efficiency > 1) {
559 $this->efficiency = 1;
560 }
561
562 return $this->updateCommon($user, $notrigger);
563 }
564
572 public function delete(User $user, $notrigger = 1)
573 {
574 return $this->deleteCommon($user, $notrigger);
575 //return $this->deleteCommon($user, $notrigger, 1);
576 }
577
594 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)
595 {
596 global $mysoc, $conf, $langs, $user;
597
598 $logtext = "::addLine bomid=$this->id, qty=$qty, fk_product=$fk_product, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
599 $logtext .= ", fk_bom_child=$fk_bom_child, import_key=$import_key";
600 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
601
602 if ($this->statut == self::STATUS_DRAFT) {
603 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
604
605 // Clean parameters
606 if (empty($qty)) {
607 $qty = 0;
608 }
609 if (empty($qty_frozen)) {
610 $qty_frozen = 0;
611 }
612 if (empty($disable_stock_change)) {
613 $disable_stock_change = 0;
614 }
615 if (empty($efficiency)) {
616 $efficiency = 1.0;
617 }
618 if (empty($fk_bom_child)) {
619 $fk_bom_child = null;
620 }
621 if (empty($import_key)) {
622 $import_key = '';
623 }
624 if (empty($position)) {
625 $position = -1;
626 }
627
628 $qty = (float) price2num($qty);
629 $efficiency = (float) price2num($efficiency);
630 $position = (float) price2num($position);
631
632 $this->db->begin();
633
634 // Rank to use
635 $rangMax = $this->line_max();
636 $rankToUse = $position;
637 if ($rankToUse <= 0 or $rankToUse > $rangMax) { // New line after existing lines
638 $rankToUse = $rangMax + 1;
639 } else { // New line between the existing lines
640 foreach ($this->lines as $bl) {
641 if ($bl->position >= $rankToUse) {
642 $bl->position++;
643 $bl->update($user);
644 }
645 }
646 }
647
648 // Insert line
649 $line = new BOMLine($this->db);
650
651 $line->context = $this->context;
652
653 $line->fk_bom = $this->id;
654 $line->fk_product = $fk_product;
655 $line->qty = $qty;
656 $line->qty_frozen = $qty_frozen;
657 $line->disable_stock_change = $disable_stock_change;
658 $line->efficiency = $efficiency;
659 $line->fk_bom_child = $fk_bom_child;
660 $line->import_key = $import_key;
661 $line->position = $rankToUse;
662 $line->fk_unit = $fk_unit;
663 $line->fk_default_workstation = $fk_default_workstation;
664
665 if (is_array($array_options) && count($array_options) > 0) {
666 $line->array_options = $array_options;
667 }
668
669 $result = $line->create($user);
670
671 if ($result > 0) {
672 $this->calculateCosts();
673 $this->db->commit();
674 return $result;
675 } else {
676 $this->setErrorsFromObject($line);
677 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
678 $this->db->rollback();
679 return -2;
680 }
681 } else {
682 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
683 return -3;
684 }
685 }
686
702 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)
703 {
704 global $user;
705
706 $logtext = "::updateLine bomid=$this->id, qty=$qty, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
707 $logtext .= ", import_key=$import_key";
708 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
709
710 if ($this->statut == self::STATUS_DRAFT) {
711 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
712
713 // Clean parameters
714 if (empty($qty)) {
715 $qty = 0;
716 }
717 if (empty($qty_frozen)) {
718 $qty_frozen = 0;
719 }
720 if (empty($disable_stock_change)) {
721 $disable_stock_change = 0;
722 }
723 if (empty($efficiency)) {
724 $efficiency = 1.0;
725 }
726 if (empty($import_key)) {
727 $import_key = '';
728 }
729 if (empty($position)) {
730 $position = -1;
731 }
732
733 $qty = (float) price2num($qty);
734 $efficiency = (float) price2num($efficiency);
735 $position = (float) price2num($position);
736
737 $this->db->begin();
738
739 // Fetch current line from the database and then clone the object and set it in $oldline property
740 $line = new BOMLine($this->db);
741 $line->fetch($rowid);
742 $line->fetch_optionals();
743
744 $staticLine = clone $line;
745 $line->oldcopy = $staticLine;
746 $line->context = $this->context;
747
748 // Rank to use
749 $rankToUse = (int) $position;
750 if ($rankToUse != $line->oldcopy->position) { // check if position have a new value
751 foreach ($this->lines as $bl) {
752 if ($bl->position >= $rankToUse and $bl->position < ($line->oldcopy->position + 1)) { // move rank up
753 $bl->position++;
754 $bl->update($user);
755 }
756 if ($bl->position <= $rankToUse and $bl->position > ($line->oldcopy->position)) { // move rank down
757 $bl->position--;
758 $bl->update($user);
759 }
760 }
761 }
762
763
764 $line->fk_bom = $this->id;
765 $line->qty = $qty;
766 $line->qty_frozen = $qty_frozen;
767 $line->disable_stock_change = $disable_stock_change;
768 $line->efficiency = $efficiency;
769 $line->import_key = $import_key;
770 $line->position = $rankToUse;
771
772
773 if (!empty($fk_unit)) {
774 $line->fk_unit = $fk_unit;
775 }
776
777
778 if (is_array($array_options) && count($array_options) > 0) {
779 // We replace values in this->line->array_options only for entries defined into $array_options
780 foreach ($array_options as $key => $value) {
781 $line->array_options[$key] = $array_options[$key];
782 }
783 }
784 if ($line->fk_default_workstation != $fk_default_workstation) {
785 $line->fk_default_workstation = ($fk_default_workstation > 0 ? $fk_default_workstation : 0);
786 }
787
788 $result = $line->update($user);
789
790 if ($result > 0) {
791 $this->calculateCosts();
792 $this->db->commit();
793 return $result;
794 } else {
795 $this->setErrorsFromObject($line);
796 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
797 $this->db->rollback();
798 return -2;
799 }
800 } else {
801 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
802 return -3;
803 }
804 }
805
814 public function deleteLine(User $user, $idline, $notrigger = 0)
815 {
816 if ($this->status < 0) {
817 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
818 return -2;
819 }
820
821 $this->db->begin();
822
823 // Fetch current line from the database and then clone the object and set it in $oldline property
824 $line = new BOMLine($this->db);
825 $line->fetch($idline);
826 $line->fetch_optionals();
827
828 $staticLine = clone $line;
829 $line->oldcopy = $staticLine;
830 $line->context = $this->context;
831
832 $result = $line->delete($user, $notrigger);
833
834 //Positions (rank) reordering
835 foreach ($this->lines as $bl) {
836 if ($bl->position > ($line->oldcopy->position)) { // move rank down
837 $bl->position--;
838 $bl->update($user);
839 }
840 }
841
842 if ($result > 0) {
843 $this->calculateCosts();
844 $this->db->commit();
845 return $result;
846 } else {
847 $this->setErrorsFromObject($line);
848 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
849 $this->db->rollback();
850 return -2;
851 }
852 }
853
861 public function getNextNumRef($prod)
862 {
863 global $langs, $conf;
864 $langs->load("mrp");
865
866 if (getDolGlobalString('BOM_ADDON')) {
867 $mybool = false;
868
869 $file = getDolGlobalString('BOM_ADDON') . ".php";
870 $classname = getDolGlobalString('BOM_ADDON');
871
872 // Include file with class
873 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
874 foreach ($dirmodels as $reldir) {
875 $dir = dol_buildpath($reldir."core/modules/bom/");
876
877 // Load file with numbering class (if found)
878 $mybool = ((bool) @include_once $dir.$file) || $mybool;
879 }
880
881 if (!$mybool) {
882 dol_print_error(null, "Failed to include file ".$file);
883 return '';
884 }
885
886 $obj = new $classname();
887 '@phan-var-force ModeleNumRefBoms $obj';
888 $numref = $obj->getNextValue($prod, $this);
889
890 if ($numref != "") {
891 return $numref;
892 } else {
893 $this->error = $obj->error;
894 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
895 return "";
896 }
897 } else {
898 print $langs->trans("Error")." ".$langs->trans("Error_BOM_ADDON_NotDefined");
899 return "";
900 }
901 }
902
910 public function validate($user, $notrigger = 0)
911 {
912 global $conf;
913
914 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
915
916 $error = 0;
917
918 // Protection
919 if ($this->status == self::STATUS_VALIDATED) {
920 dol_syslog(get_class($this)."::validate action abandoned: already validated", LOG_WARNING);
921 return 0;
922 }
923
924 $now = dol_now();
925
926 $this->db->begin();
927
928 // Define new ref
929 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
930 $this->fetch_product();
931 $num = $this->getNextNumRef($this->product);
932 } else {
933 $num = $this->ref;
934 }
935 $this->newref = dol_sanitizeFileName($num);
936
937 // Validate
938 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
939 $sql .= " SET ref = '".$this->db->escape($num)."',";
940 $sql .= " status = ".self::STATUS_VALIDATED.",";
941 $sql .= " date_valid='".$this->db->idate($now)."',";
942 $sql .= " fk_user_valid = ".((int) $user->id);
943 $sql .= " WHERE rowid = ".((int) $this->id);
944
945 dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
946 $resql = $this->db->query($sql);
947 if (!$resql) {
948 dol_print_error($this->db);
949 $this->error = $this->db->lasterror();
950 $error++;
951 }
952
953 if (!$error && !$notrigger) {
954 // Call trigger
955 $result = $this->call_trigger('BOM_VALIDATE', $user);
956 if ($result < 0) {
957 $error++;
958 }
959 // End call triggers
960 }
961
962 if (!$error) {
963 $this->oldref = $this->ref;
964
965 // Rename directory if dir was a temporary ref
966 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
967 // Now we rename also files into index
968 $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)."'";
969 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
970 $resql = $this->db->query($sql);
971 if (!$resql) {
972 $error++;
973 $this->error = $this->db->lasterror();
974 }
975 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'bom/".$this->db->escape($this->newref)."'";
976 $sql .= " WHERE filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
977 $resql = $this->db->query($sql);
978 if (!$resql) {
979 $error++;
980 $this->error = $this->db->lasterror();
981 }
982
983 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
984 $oldref = dol_sanitizeFileName($this->ref);
985 $newref = dol_sanitizeFileName($num);
986 $dirsource = $conf->bom->dir_output.'/'.$oldref;
987 $dirdest = $conf->bom->dir_output.'/'.$newref;
988 if (!$error && file_exists($dirsource)) {
989 dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
990
991 if (@rename($dirsource, $dirdest)) {
992 dol_syslog("Rename ok");
993 // Rename docs starting with $oldref with $newref
994 $listoffiles = dol_dir_list($conf->bom->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
995 foreach ($listoffiles as $fileentry) {
996 $dirsource = $fileentry['name'];
997 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
998 $dirsource = $fileentry['path'].'/'.$dirsource;
999 $dirdest = $fileentry['path'].'/'.$dirdest;
1000 @rename($dirsource, $dirdest);
1001 }
1002 }
1003 }
1004 }
1005 }
1006
1007 // Set new ref and current status
1008 if (!$error) {
1009 $this->ref = $num;
1010 $this->status = self::STATUS_VALIDATED;
1011 }
1012
1013 if (!$error) {
1014 $this->db->commit();
1015 return 1;
1016 } else {
1017 $this->db->rollback();
1018 return -1;
1019 }
1020 }
1021
1029 public function setDraft($user, $notrigger = 0)
1030 {
1031 // Protection
1032 if ($this->status <= self::STATUS_DRAFT) {
1033 return 0;
1034 }
1035
1036 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'BOM_UNVALIDATE');
1037 }
1038
1046 public function cancel($user, $notrigger = 0)
1047 {
1048 // Protection
1049 if ($this->status != self::STATUS_VALIDATED) {
1050 return 0;
1051 }
1052
1053 return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'BOM_CLOSE');
1054 }
1055
1063 public function reopen($user, $notrigger = 0)
1064 {
1065 // Protection
1066 if ($this->status != self::STATUS_CANCELED) {
1067 return 0;
1068 }
1069
1070 return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'BOM_REOPEN');
1071 }
1072
1079 public function getTooltipContentArray($params)
1080 {
1081 global $conf, $langs, $user;
1082
1083 $langs->loadLangs(['product', 'mrp']);
1084
1085 $datas = [];
1086
1087 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1088 return ['optimize' => $langs->trans("ShowBillOfMaterials")];
1089 }
1090 $picto = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("BillOfMaterials").'</u>';
1091 if (isset($this->status)) {
1092 $picto .= ' '.$this->getLibStatut(5);
1093 }
1094 $datas['picto'] = $picto;
1095 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1096 if (isset($this->label)) {
1097 $datas['label'] = '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
1098 }
1099 if (!empty($this->fk_product) && $this->fk_product > 0) {
1100 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1101 $product = new Product($this->db);
1102 $resultFetch = $product->fetch($this->fk_product);
1103 if ($resultFetch > 0) {
1104 $datas['product'] = "<br><b>".$langs->trans("Product").'</b>: '.$product->ref.' - '.$product->label;
1105 }
1106 }
1107
1108 return $datas;
1109 }
1110
1121 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1122 {
1123 global $db, $conf, $langs, $hookmanager;
1124
1125 if (!empty($conf->dol_no_mouse_hover)) {
1126 $notooltip = 1; // Force disable tooltips
1127 }
1128
1129 $result = '';
1130 $params = [
1131 'id' => $this->id,
1132 'objecttype' => $this->element,
1133 'option' => $option,
1134 ];
1135 $classfortooltip = 'classfortooltip';
1136 $dataparams = '';
1137 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1138 $classfortooltip = 'classforajaxtooltip';
1139 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1140 $label = '';
1141 } else {
1142 $label = implode($this->getTooltipContentArray($params));
1143 }
1144
1145 $url = DOL_URL_ROOT.'/bom/bom_card.php?id='.$this->id;
1146
1147 if ($option != 'nolink') {
1148 // Add param to save lastsearch_values or not
1149 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1150 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1151 $add_save_lastsearch_values = 1;
1152 }
1153 if ($add_save_lastsearch_values) {
1154 $url .= '&save_lastsearch_values=1';
1155 }
1156 }
1157
1158 $linkclose = '';
1159 if (empty($notooltip)) {
1160 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1161 $label = $langs->trans("ShowBillOfMaterials");
1162 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1163 }
1164 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1165 $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1166 } else {
1167 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1168 }
1169
1170 $linkstart = '<a href="'.$url.'"';
1171 $linkstart .= $linkclose.'>';
1172 $linkend = '</a>';
1173
1174 $result .= $linkstart;
1175 if ($withpicto) {
1176 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1177 }
1178 if ($withpicto != 2) {
1179 $result .= $this->ref;
1180 }
1181 $result .= $linkend;
1182 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1183
1184 global $action, $hookmanager;
1185 $hookmanager->initHooks(array('bomdao'));
1186 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1187 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1188 if ($reshook > 0) {
1189 $result = $hookmanager->resPrint;
1190 } else {
1191 $result .= $hookmanager->resPrint;
1192 }
1193
1194 return $result;
1195 }
1196
1203 public function getLibStatut($mode = 0)
1204 {
1205 return $this->LibStatut($this->status, $mode);
1206 }
1207
1208 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1216 public function LibStatut($status, $mode = 0)
1217 {
1218 // phpcs:enable
1219 if (empty($this->labelStatus)) {
1220 global $langs;
1221 //$langs->load("mrp");
1222 $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1223 $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Enabled');
1224 $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Disabled');
1225 }
1226
1227 $statusType = 'status'.$status;
1228 if ($status == self::STATUS_VALIDATED) {
1229 $statusType = 'status4';
1230 }
1231 if ($status == self::STATUS_CANCELED) {
1232 $statusType = 'status6';
1233 }
1234
1235 return dolGetStatus($this->labelStatus[$status], $this->labelStatus[$status], '', $statusType, $mode);
1236 }
1237
1244 public function info($id)
1245 {
1246 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1247 $sql .= ' fk_user_creat, fk_user_modif';
1248 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1249 $sql .= ' WHERE t.rowid = '.((int) $id);
1250 $result = $this->db->query($sql);
1251 if ($result) {
1252 if ($this->db->num_rows($result)) {
1253 $obj = $this->db->fetch_object($result);
1254
1255 $this->id = $obj->rowid;
1256
1257 $this->user_creation_id = $obj->fk_user_creat;
1258 $this->user_modification_id = $obj->fk_user_modif;
1259 $this->date_creation = $this->db->jdate($obj->datec);
1260 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1261 }
1262
1263 $this->db->free($result);
1264 } else {
1265 dol_print_error($this->db);
1266 }
1267 }
1268
1274 public function getLinesArray()
1275 {
1276 $this->lines = array();
1277
1278 $objectline = new BOMLine($this->db);
1279 $result = $objectline->fetchAll('ASC', 'position', 0, 0, '(fk_bom:=:'.((int) $this->id).')');
1280
1281 if (is_numeric($result)) {
1282 $this->error = $objectline->error;
1283 $this->errors = $objectline->errors;
1284 return $result;
1285 } else {
1286 $this->lines = $result;
1287 return $this->lines;
1288 }
1289 }
1290
1302 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
1303 {
1304 global $conf, $langs;
1305
1306 $langs->load("mrp");
1307 $outputlangs->load("products");
1308
1309 if (!dol_strlen($modele)) {
1310 $modele = '';
1311
1312 if ($this->model_pdf) {
1313 $modele = $this->model_pdf;
1314 } elseif (getDolGlobalString('BOM_ADDON_PDF')) {
1315 $modele = getDolGlobalString('BOM_ADDON_PDF');
1316 }
1317 }
1318
1319 $modelpath = "core/modules/bom/doc/";
1320 if (!empty($modele)) {
1321 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
1322 } else {
1323 return 0;
1324 }
1325 }
1326
1327 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1334 public function is_photo_available($sdir)
1335 {
1336 // phpcs:enable
1337 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1338 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
1339
1340 $sdir .= '/'.get_exdir(0, 0, 0, 0, $this, 'bom');
1341
1342 $dir_osencoded = dol_osencode($sdir);
1343 if (file_exists($dir_osencoded)) {
1344 $handle = opendir($dir_osencoded);
1345 if (is_resource($handle)) {
1346 while (($file = readdir($handle)) !== false) {
1347 if (!utf8_check($file)) {
1348 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
1349 }
1350 if (dol_is_file($sdir.$file) && image_format_supported($file) >= 0) {
1351 return true;
1352 }
1353 }
1354 }
1355 }
1356 return false;
1357 }
1358
1365 public function initAsSpecimen()
1366 {
1367 $this->initAsSpecimenCommon();
1368 $this->ref = 'BOM-123';
1369 $this->date_creation = dol_now() - 20000;
1370
1371 return 1;
1372 }
1373
1374
1381 public function doScheduledJob()
1382 {
1383 global $conf, $langs;
1384
1385 //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
1386
1387 $error = 0;
1388 $this->output = '';
1389 $this->error = '';
1390
1391 dol_syslog(__METHOD__, LOG_DEBUG);
1392
1393 $now = dol_now();
1394
1395 $this->db->begin();
1396
1397 // ...
1398
1399 $this->db->commit();
1400
1401 return $error;
1402 }
1403
1410 public function calculateCosts()
1411 {
1412 global $conf, $hookmanager;
1413
1414 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1415 $this->unit_cost = 0;
1416 $this->total_cost = 0;
1417
1418 $parameters = array();
1419 $reshook = $hookmanager->executeHooks('calculateCostsBom', $parameters, $this); // Note that $action and $object may have been modified by hook
1420
1421 if ($reshook > 0) {
1422 return $hookmanager->resPrint;
1423 }
1424
1425 if (is_array($this->lines) && count($this->lines)) {
1426 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1427 $productFournisseur = new ProductFournisseur($this->db);
1428 $tmpproduct = new Product($this->db);
1429
1430 foreach ($this->lines as &$line) {
1431 $tmpproduct->cost_price = 0;
1432 $tmpproduct->pmp = 0;
1433 $result = $tmpproduct->fetch($line->fk_product, '', '', '', 0, 1, 1); // We discard selling price and language loading
1434
1435 if ($tmpproduct->type == $tmpproduct::TYPE_PRODUCT) {
1436 if (empty($line->fk_bom_child)) {
1437 if ($result < 0) {
1438 $this->error = $tmpproduct->error;
1439 return -1;
1440 }
1441 $unit_cost = (!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp;
1442 $line->unit_cost = (float) price2num($unit_cost);
1443 if (empty($line->unit_cost)) {
1444 if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
1445 if ($productFournisseur->fourn_remise_percent != "0") {
1446 $line->unit_cost = $productFournisseur->fourn_unitprice_with_discount;
1447 } else {
1448 $line->unit_cost = $productFournisseur->fourn_unitprice;
1449 }
1450 }
1451 }
1452
1453 $line->total_cost = (float) price2num($line->qty * $line->unit_cost, 'MT');
1454
1455 $this->total_cost += $line->total_cost;
1456 } else {
1457 $bom_child = new BOM($this->db);
1458 $res = $bom_child->fetch($line->fk_bom_child);
1459 if ($res > 0) {
1460 $bom_child->calculateCosts();
1461 $line->childBom[] = $bom_child;
1462 $this->total_cost += (float) price2num($bom_child->total_cost * $line->qty, 'MT');
1463 $this->total_cost += $line->total_cost;
1464 } else {
1465 $this->error = $bom_child->error;
1466 return -2;
1467 }
1468 }
1469 } else {
1470 // Convert qty of line into hours
1471 require_once DOL_DOCUMENT_ROOT.'/core/class/cunits.class.php';
1472 $measuringUnits = new CUnits($this->db);
1473 $measuringUnits->fetch($line->fk_unit);
1474
1475 // 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
1476 $qtyhourforline = $line->qty * (int) $measuringUnits->scale / 3600;
1477
1478 if (isModEnabled('workstation') && !empty($line->fk_default_workstation)) {
1479 $workstation = new Workstation($this->db);
1480 $res = $workstation->fetch($line->fk_default_workstation);
1481
1482 if ($res > 0) {
1483 $line->total_cost = (float) price2num($qtyhourforline * ($workstation->thm_operator_estimated + $workstation->thm_machine_estimated), 'MT');
1484 } else {
1485 $this->error = $workstation->error;
1486 return -3;
1487 }
1488 } else {
1489 $defaultdurationofservice = $tmpproduct->duration;
1490 $reg = array();
1491 $qtyhourservice = 0;
1492 if (preg_match('/^(\d+)([a-z]+)$/', $defaultdurationofservice, $reg)) {
1493 $qtyhourservice = convertDurationtoHour((float) $reg[1], $reg[2]);
1494 }
1495
1496 if ($qtyhourservice) {
1497 $line->total_cost = (float) price2num($qtyhourforline / $qtyhourservice * $tmpproduct->cost_price, 'MT');
1498 } else {
1499 $line->total_cost = (float) price2num($line->qty * $tmpproduct->cost_price, 'MT');
1500 }
1501 }
1502
1503 $this->total_cost += $line->total_cost;
1504 }
1505 }
1506
1507 $this->total_cost = (float) price2num($this->total_cost, 'MT');
1508
1509 if ($this->qty > 0) {
1510 $this->unit_cost = (float) price2num($this->total_cost / $this->qty, 'MU');
1511 } elseif ($this->qty < 0) {
1512 $this->unit_cost = (float) price2num($this->total_cost * $this->qty, 'MU');
1513 }
1514 }
1515
1516 return 1;
1517 }
1518
1527 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
1528 {
1529 $tables = array(
1530 'bom_bomline'
1531 );
1532
1533 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
1534 }
1535
1543 public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
1544 {
1545 if (!empty($this->lines)) {
1546 foreach ($this->lines as $line) {
1547 if (!empty($line->childBom)) {
1548 foreach ($line->childBom as $childBom) {
1549 $childBom->getNetNeeds($TNetNeeds, $line->qty * $qty);
1550 }
1551 } else {
1552 if (empty($TNetNeeds[$line->fk_product]['qty'])) {
1553 $TNetNeeds[$line->fk_product]['qty'] = 0.0;
1554 }
1555 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1556 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1557 $TNetNeeds[$line->fk_product]['fk_unit'] = $line->fk_unit;
1558 $TNetNeeds[$line->fk_product]['qty'] += $line->qty * $qty;
1559 }
1560 }
1561 }
1562 }
1563
1572 public function getNetNeedsTree(&$TNetNeeds = array(), $qty = 0, $level = 0)
1573 {
1574 if (!empty($this->lines)) {
1575 foreach ($this->lines as $line) {
1576 if (!empty($line->childBom)) {
1577 foreach ($line->childBom as $childBom) {
1578 $TNetNeeds[$childBom->id]['bom'] = $childBom;
1579 $TNetNeeds[$childBom->id]['parentid'] = $this->id;
1580 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1581 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1582 //$TNetNeeds[$childBom->id]['fk_unit'] = $line->fk_unit;
1583 $TNetNeeds[$childBom->id]['qty'] = $line->qty * $qty;
1584 $TNetNeeds[$childBom->id]['level'] = $level;
1585 $childBom->getNetNeedsTree($TNetNeeds, $line->qty * $qty, $level + 1);
1586 }
1587 } else {
1588 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1589 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1590 if (!isset($TNetNeeds[$this->id]['product'])) {
1591 $TNetNeeds[$this->id]['product'] = array();
1592 }
1593 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product])) {
1594 $TNetNeeds[$this->id]['product'][$line->fk_product] = array();
1595 }
1596 $TNetNeeds[$this->id]['product'][$line->fk_product]['fk_unit'] = $line->fk_unit;
1597 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product]['qty'])) {
1598 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] = 0.0;
1599 }
1600 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
1601 $TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
1602 }
1603 }
1604 }
1605 }
1606
1615 public function getParentBomTreeRecursive(&$TParentBom, $bom_id = 0, $level = 1)
1616 {
1617
1618 // Protection against infinite loop
1619 if ($level > 1000) {
1620 return;
1621 }
1622
1623 if (empty($bom_id)) {
1624 $bom_id = $this->id;
1625 }
1626
1627 $sql = 'SELECT l.fk_bom, b.label
1628 FROM '.MAIN_DB_PREFIX.'bom_bomline l
1629 INNER JOIN '.MAIN_DB_PREFIX.$this->table_element.' b ON b.rowid = l.fk_bom
1630 WHERE fk_bom_child = '.((int) $bom_id);
1631
1632 $resql = $this->db->query($sql);
1633 if (!empty($resql)) {
1634 while ($res = $this->db->fetch_object($resql)) {
1635 $TParentBom[$res->fk_bom] = $res->fk_bom;
1636 $this->getParentBomTreeRecursive($TParentBom, $res->fk_bom, $level + 1);
1637 }
1638 }
1639 }
1640
1648 public function getKanbanView($option = '', $arraydata = null)
1649 {
1650 global $db,$langs;
1651
1652 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1653
1654 $return = '<div class="box-flex-item box-flex-grow-zero">';
1655 $return .= '<div class="info-box info-box-sm">';
1656 $return .= '<span class="info-box-icon bg-infobox-action">';
1657 $return .= img_picto('', $this->picto);
1658 $return .= '</span>';
1659 $return .= '<div class="info-box-content">';
1660 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : '').'</span>';
1661 if ($selected >= 0) {
1662 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1663 }
1664 $arrayofkeyval = $this->fields['bomtype']['arrayofkeyval'] ?? null;
1665 if (!empty($arrayofkeyval)) {
1666 $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Type").' : </span>';
1667 if ($this->bomtype == 0) {
1668 $return .= '<span class="info-box-label">'.$arrayofkeyval[0].'</span>';
1669 } else {
1670 $return .= '<span class="info-box-label">'.$arrayofkeyval[1].'</span>';
1671 }
1672 }
1673 if (!empty($arraydata['prod'])) {
1674 $prod = $arraydata['prod'];
1675 $return .= '<br><span class="info-box-label">'.$prod->getNomUrl(1).'</span>';
1676 }
1677 if (method_exists($this, 'getLibStatut')) {
1678 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1679 }
1680
1681 $return .= '</div>';
1682 $return .= '</div>';
1683 $return .= '</div>';
1684 return $return;
1685 }
1686}
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:66
$object ref
Definition info.php:89
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.
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.
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_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.
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_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.
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79