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