dolibarr 18.0.6
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 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
26// Put here all includes required by your class file
27require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
28require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
29require_once DOL_DOCUMENT_ROOT.'/workstation/class/workstation.class.php';
30require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
31//require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
32//require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
33
34
38class BOM extends CommonObject
39{
40
44 public $module = 'bom';
45
49 public $element = 'bom';
50
54 public $table_element = 'bom_bom';
55
59 public $ismultientitymanaged = 1;
60
64 public $isextrafieldmanaged = 1;
65
69 public $picto = 'bom';
70
71
72 const STATUS_DRAFT = 0;
73 const STATUS_VALIDATED = 1;
74 const STATUS_CANCELED = 9;
75
76
103 // BEGIN MODULEBUILDER PROPERTIES
107 public $fields = array(
108 'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>"Id",),
109 'entity' => array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'notnull'=> 1, 'default'=>1, 'index'=>1, 'position'=>5),
110 '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'),
111 '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'),
112 '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'),
113 //'bomtype' => array('type'=>'integer', 'label'=>'Type', 'enabled'=>1, 'visible'=>-1, 'position'=>32, 'notnull'=>1, 'default'=>'0', 'arrayofkeyval'=>array(0=>'Manufacturing')),
114 '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'),
115 'description' => array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>-1, 'position'=>60, 'notnull'=>-1,),
116 'qty' => array('type'=>'real', 'label'=>'Quantity', 'enabled'=>1, 'visible'=>1, 'default'=>1, 'position'=>55, 'notnull'=>1, 'isameasure'=>'1', 'css'=>'maxwidth50imp right'),
117 //'efficiency' => array('type'=>'real', 'label'=>'ManufacturingEfficiency', 'enabled'=>1, 'visible'=>-1, 'default'=>1, 'position'=>100, 'notnull'=>0, 'css'=>'maxwidth50imp', 'help'=>'ValueOfMeansLossForProductProduced'),
118 'duration' => array('type'=>'duration', 'label'=>'EstimatedDuration', 'enabled'=>1, 'visible'=>-1, 'position'=>101, 'notnull'=>-1, 'css'=>'maxwidth50imp', 'help'=>'EstimatedDurationDesc'),
119 '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'),
120 'note_public' => array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>-2, 'position'=>161, 'notnull'=>-1,),
121 'note_private' => array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>-2, 'position'=>162, 'notnull'=>-1,),
122 'date_creation' => array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>300, 'notnull'=>1,),
123 'tms' => array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-2, 'position'=>501, 'notnull'=>1,),
124 'date_valid' => array('type'=>'datetime', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-2, 'position'=>502, 'notnull'=>0,),
125 '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'),
126 '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'),
127 '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'),
128 'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>1000, 'notnull'=>-1,),
129 'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>1, 'visible'=>0, 'position'=>1010),
130 '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')),
131 );
132
136 public $rowid;
137
141 public $ref;
142
146 public $label;
147
151 public $bomtype;
152
156 public $description;
157
161 public $date_creation;
162
166 public $date_valid;
167
168 public $tms;
169
173 public $fk_user_creat;
174
178 public $fk_user_modif;
179
183 public $fk_user_valid;
184
188 public $fk_warehouse;
189
193 public $import_key;
194
198 public $status;
199
203 public $fk_product;
204 public $qty;
205 public $duration;
206 public $efficiency;
207 // END MODULEBUILDER PROPERTIES
208
209
210 // If this object has a subtable with lines
211
215 public $table_element_line = 'bom_bomline';
216
220 public $fk_element = 'fk_bom';
221
225 public $class_element_line = 'BOMLine';
226
227 // /**
228 // * @var array List of child tables. To test if we can delete object.
229 // */
230 // protected $childtables=array();
231
235 protected $childtablesoncascade = array('bom_bomline');
236
240 public $lines = array();
241
245 public $total_cost = 0;
246
250 public $unit_cost = 0;
251
252
258 public function __construct(DoliDB $db)
259 {
260 global $conf, $langs;
261
262 $this->db = $db;
263
264 if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && isset($this->fields['rowid'])) {
265 $this->fields['rowid']['visible'] = 0;
266 }
267 if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
268 $this->fields['entity']['enabled'] = 0;
269 }
270
271 // Unset fields that are disabled
272 foreach ($this->fields as $key => $val) {
273 if (isset($val['enabled']) && empty($val['enabled'])) {
274 unset($this->fields[$key]);
275 }
276 }
277
278 // Translate some data of arrayofkeyval
279 foreach ($this->fields as $key => $val) {
280 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
281 foreach ($val['arrayofkeyval'] as $key2 => $val2) {
282 $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
283 }
284 }
285 }
286 }
287
295 public function create(User $user, $notrigger = false)
296 {
297 if ($this->efficiency <= 0 || $this->efficiency > 1) {
298 $this->efficiency = 1;
299 }
300
301 return $this->createCommon($user, $notrigger);
302 }
303
311 public function createFromClone(User $user, $fromid)
312 {
313 global $langs, $hookmanager, $extrafields;
314 $error = 0;
315
316 dol_syslog(__METHOD__, LOG_DEBUG);
317
318 $object = new self($this->db);
319
320 $this->db->begin();
321
322 // Load source object
323 $result = $object->fetchCommon($fromid);
324 if ($result > 0 && !empty($object->table_element_line)) {
325 $object->fetchLines();
326 }
327
328 // Get lines so they will be clone
329 //foreach ($object->lines as $line)
330 // $line->fetch_optionals();
331
332 // Reset some properties
333 unset($object->id);
334 unset($object->fk_user_creat);
335 unset($object->import_key);
336
337 // Clear fields
338 $object->ref = empty($this->fields['ref']['default']) ? $langs->trans("copy_of_").$object->ref : $this->fields['ref']['default'];
339 $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf")." ".$object->label : $this->fields['label']['default'];
340 $object->status = self::STATUS_DRAFT;
341 // ...
342 // Clear extrafields that are unique
343 if (is_array($object->array_options) && count($object->array_options) > 0) {
344 $extrafields->fetch_name_optionals_label($object->table_element);
345 foreach ($object->array_options as $key => $option) {
346 $shortkey = preg_replace('/options_/', '', $key);
347 if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) {
348 //var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
349 unset($object->array_options[$key]);
350 }
351 }
352 }
353
354 // Create clone
355 $object->context['createfromclone'] = 'createfromclone';
356 $result = $object->createCommon($user);
357 if ($result < 0) {
358 $error++;
359 $this->error = $object->error;
360 $this->errors = $object->errors;
361 }
362
363 if (!$error) {
364 // copy internal contacts
365 if ($this->copy_linked_contact($object, 'internal') < 0) {
366 $error++;
367 }
368 }
369
370 if (!$error) {
371 // copy external contacts if same company
372 if (property_exists($this, 'socid') && $this->socid == $object->socid) {
373 if ($this->copy_linked_contact($object, 'external') < 0) {
374 $error++;
375 }
376 }
377 }
378
379 // If there is lines, create lines too
380
381
382
383 unset($object->context['createfromclone']);
384
385 // End
386 if (!$error) {
387 $this->db->commit();
388 return $object;
389 } else {
390 $this->db->rollback();
391 return -1;
392 }
393 }
394
402 public function fetch($id, $ref = null)
403 {
404 $result = $this->fetchCommon($id, $ref);
405
406 if ($result > 0 && !empty($this->table_element_line)) {
407 $this->fetchLines();
408 }
409 //$this->calculateCosts(); // This consume a high number of subrequests. Do not call it into fetch but when you need it.
410
411 return $result;
412 }
413
419 public function fetchLines()
420 {
421 $this->lines = array();
422
423 $result = $this->fetchLinesCommon();
424 return $result;
425 }
426
434 public function fetchLinesbytypeproduct($typeproduct = 0)
435 {
436 $this->lines = array();
437
438 $objectlineclassname = get_class($this).'Line';
439 if (!class_exists($objectlineclassname)) {
440 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
441 return -1;
442 }
443
444 $objectline = new $objectlineclassname($this->db);
445
446 $sql = "SELECT ".$objectline->getFieldList('l');
447 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
448 $sql .= " LEFT JOIN ".$this->db->prefix()."product as p ON p.rowid = l.fk_product";
449 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
450 $sql .= " AND p.fk_product_type = ". ((int) $typeproduct);
451 if (isset($objectline->fields['position'])) {
452 $sql .= $this->db->order('position', 'ASC');
453 }
454
455 $resql = $this->db->query($sql);
456 if ($resql) {
457 $num_rows = $this->db->num_rows($resql);
458 $i = 0;
459 while ($i < $num_rows) {
460 $obj = $this->db->fetch_object($resql);
461 if ($obj) {
462 $newline = new $objectlineclassname($this->db);
463 $newline->setVarsFromFetchObj($obj);
464
465 $this->lines[] = $newline;
466 }
467 $i++;
468 }
469
470 return $num_rows;
471 } else {
472 $this->error = $this->db->lasterror();
473 $this->errors[] = $this->error;
474 return -1;
475 }
476 }
477
478
490 public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
491 {
492 global $conf;
493
494 dol_syslog(__METHOD__, LOG_DEBUG);
495
496 $records = array();
497
498 $sql = 'SELECT ';
499 $sql .= $this->getFieldList();
500 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
501 if ($this->ismultientitymanaged) {
502 $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
503 } else {
504 $sql .= ' WHERE 1 = 1';
505 }
506 // Manage filter
507 $sqlwhere = array();
508 if (count($filter) > 0) {
509 foreach ($filter as $key => $value) {
510 if ($key == 't.rowid') {
511 $sqlwhere[] = $key." = ".((int) $value);
512 } elseif (strpos($key, 'date') !== false) {
513 $sqlwhere[] = $key." = '".$this->db->idate($value)."'";
514 } elseif ($key == 'customsql') {
515 $sqlwhere[] = $value;
516 } else {
517 $sqlwhere[] = $key." LIKE '%".$this->db->escape($value)."%'";
518 }
519 }
520 }
521 if (count($sqlwhere) > 0) {
522 $sql .= " AND (".implode(" ".$filtermode." ", $sqlwhere).")";
523 }
524
525 if (!empty($sortfield)) {
526 $sql .= $this->db->order($sortfield, $sortorder);
527 }
528 if (!empty($limit)) {
529 $sql .= $this->db->plimit($limit, $offset);
530 }
531
532 $resql = $this->db->query($sql);
533 if ($resql) {
534 $num = $this->db->num_rows($resql);
535
536 while ($obj = $this->db->fetch_object($resql)) {
537 $record = new self($this->db);
538 $record->setVarsFromFetchObj($obj);
539
540 $records[$record->id] = $record;
541 }
542 $this->db->free($resql);
543
544 return $records;
545 } else {
546 $this->errors[] = 'Error '.$this->db->lasterror();
547 dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
548
549 return -1;
550 }
551 }
552
560 public function update(User $user, $notrigger = false)
561 {
562 if ($this->efficiency <= 0 || $this->efficiency > 1) {
563 $this->efficiency = 1;
564 }
565
566 return $this->updateCommon($user, $notrigger);
567 }
568
576 public function delete(User $user, $notrigger = false)
577 {
578 return $this->deleteCommon($user, $notrigger);
579 //return $this->deleteCommon($user, $notrigger, 1);
580 }
581
598 public function addLine($fk_product, $qty, $qty_frozen = 0, $disable_stock_change = 0, $efficiency = 1.0, $position = -1, $fk_bom_child = null, $import_key = null, $fk_unit = '', $array_options = 0, $fk_default_workstation = null)
599 {
600 global $mysoc, $conf, $langs, $user;
601
602 $logtext = "::addLine bomid=$this->id, qty=$qty, fk_product=$fk_product, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
603 $logtext .= ", fk_bom_child=$fk_bom_child, import_key=$import_key";
604 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
605
606 if ($this->statut == self::STATUS_DRAFT) {
607 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
608
609 // Clean parameters
610 if (empty($qty)) {
611 $qty = 0;
612 }
613 if (empty($qty_frozen)) {
614 $qty_frozen = 0;
615 }
616 if (empty($disable_stock_change)) {
617 $disable_stock_change = 0;
618 }
619 if (empty($efficiency)) {
620 $efficiency = 1.0;
621 }
622 if (empty($fk_bom_child)) {
623 $fk_bom_child = null;
624 }
625 if (empty($import_key)) {
626 $import_key = null;
627 }
628 if (empty($position)) {
629 $position = -1;
630 }
631
632 $qty = price2num($qty);
633 $efficiency = price2num($efficiency);
634 $position = price2num($position);
635
636 $this->db->begin();
637
638 // Rank to use
639 $rangMax = $this->line_max();
640 $rankToUse = $position;
641 if ($rankToUse <= 0 or $rankToUse > $rangMax) { // New line after existing lines
642 $rankToUse = $rangMax + 1;
643 } else { // New line between the existing lines
644 foreach ($this->lines as $bl) {
645 if ($bl->position >= $rankToUse) {
646 $bl->position++;
647 $bl->update($user);
648 }
649 }
650 }
651
652 // Insert line
653 $line = new BOMLine($this->db);
654
655 $line->context = $this->context;
656
657 $line->fk_bom = $this->id;
658 $line->fk_product = $fk_product;
659 $line->qty = $qty;
660 $line->qty_frozen = $qty_frozen;
661 $line->disable_stock_change = $disable_stock_change;
662 $line->efficiency = $efficiency;
663 $line->fk_bom_child = $fk_bom_child;
664 $line->import_key = $import_key;
665 $line->position = $rankToUse;
666 $line->fk_unit = $fk_unit;
667 $line->fk_default_workstation = $fk_default_workstation;
668
669 if (is_array($array_options) && count($array_options) > 0) {
670 $line->array_options = $array_options;
671 }
672
673 $result = $line->create($user);
674
675 if ($result > 0) {
676 $this->calculateCosts();
677 $this->db->commit();
678 return $result;
679 } else {
680 $this->setErrorsFromObject($line);
681 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
682 $this->db->rollback();
683 return -2;
684 }
685 } else {
686 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
687 return -3;
688 }
689 }
690
705 public function updateLine($rowid, $qty, $qty_frozen = 0, $disable_stock_change = 0, $efficiency = 1.0, $position = -1, $import_key = null, $fk_unit = 0, $array_options = 0)
706 {
707 global $mysoc, $conf, $langs, $user;
708
709 $logtext = "::updateLine bomid=$this->id, qty=$qty, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
710 $logtext .= ", import_key=$import_key";
711 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
712
713 if ($this->statut == self::STATUS_DRAFT) {
714 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
715
716 // Clean parameters
717 if (empty($qty)) {
718 $qty = 0;
719 }
720 if (empty($qty_frozen)) {
721 $qty_frozen = 0;
722 }
723 if (empty($disable_stock_change)) {
724 $disable_stock_change = 0;
725 }
726 if (empty($efficiency)) {
727 $efficiency = 1.0;
728 }
729 if (empty($import_key)) {
730 $import_key = null;
731 }
732 if (empty($position)) {
733 $position = -1;
734 }
735
736 $qty = price2num($qty);
737 $efficiency = price2num($efficiency);
738 $position = price2num($position);
739
740 $this->db->begin();
741
742 //Fetch current line from the database and then clone the object and set it in $oldline property
743 $line = new BOMLine($this->db);
744 $line->fetch($rowid);
745 $line->fetch_optionals();
746
747 $staticLine = clone $line;
748 $line->oldcopy = $staticLine;
749 $line->context = $this->context;
750
751 // Rank to use
752 $rankToUse = (int) $position;
753 if ($rankToUse != $line->oldcopy->position) { // check if position have a new value
754 foreach ($this->lines as $bl) {
755 if ($bl->position >= $rankToUse AND $bl->position < ($line->oldcopy->position + 1)) { // move rank up
756 $bl->position++;
757 $bl->update($user);
758 }
759 if ($bl->position <= $rankToUse AND $bl->position > ($line->oldcopy->position)) { // move rank down
760 $bl->position--;
761 $bl->update($user);
762 }
763 }
764 }
765
766
767 $line->fk_bom = $this->id;
768 $line->qty = $qty;
769 $line->qty_frozen = $qty_frozen;
770 $line->disable_stock_change = $disable_stock_change;
771 $line->efficiency = $efficiency;
772 $line->import_key = $import_key;
773 $line->position = $rankToUse;
774 if (!empty($fk_unit)) {
775 $line->fk_unit = $fk_unit;
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
785 $result = $line->update($user);
786
787 if ($result > 0) {
788 $this->calculateCosts();
789 $this->db->commit();
790 return $result;
791 } else {
792 $this->setErrorsFromObject($line);
793 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
794 $this->db->rollback();
795 return -2;
796 }
797 } else {
798 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
799 return -3;
800 }
801 }
802
811 public function deleteLine(User $user, $idline, $notrigger = false)
812 {
813 if ($this->status < 0) {
814 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
815 return -2;
816 }
817
818 $this->db->begin();
819
820 //Fetch current line from the database and then clone the object and set it in $oldline property
821 $line = new BOMLine($this->db);
822 $line->fetch($idline);
823 $line->fetch_optionals();
824
825 $staticLine = clone $line;
826 $line->oldcopy = $staticLine;
827 $line->context = $this->context;
828
829 $result = $line->delete($user, $notrigger);
830
831 //Positions (rank) reordering
832 foreach ($this->lines as $bl) {
833 if ($bl->position > ($line->oldcopy->position)) { // move rank down
834 $bl->position--;
835 $bl->update($user);
836 }
837 }
838
839 if ($result > 0) {
840 $this->calculateCosts();
841 $this->db->commit();
842 return $result;
843 } else {
844 $this->setErrorsFromObject($line);
845 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
846 $this->db->rollback();
847 return -2;
848 }
849 }
850
858 public function getNextNumRef($prod)
859 {
860 global $langs, $conf;
861 $langs->load("mrp");
862
863 if (!empty($conf->global->BOM_ADDON)) {
864 $mybool = false;
865
866 $file = $conf->global->BOM_ADDON.".php";
867 $classname = $conf->global->BOM_ADDON;
868
869 // Include file with class
870 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
871 foreach ($dirmodels as $reldir) {
872 $dir = dol_buildpath($reldir."core/modules/bom/");
873
874 // Load file with numbering class (if found)
875 $mybool |= @include_once $dir.$file;
876 }
877
878 if ($mybool === false) {
879 dol_print_error('', "Failed to include file ".$file);
880 return '';
881 }
882
883 $obj = new $classname();
884 $numref = $obj->getNextValue($prod, $this);
885
886 if ($numref != "") {
887 return $numref;
888 } else {
889 $this->error = $obj->error;
890 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
891 return "";
892 }
893 } else {
894 print $langs->trans("Error")." ".$langs->trans("Error_BOM_ADDON_NotDefined");
895 return "";
896 }
897 }
898
906 public function validate($user, $notrigger = 0)
907 {
908 global $conf, $langs;
909
910 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
911
912 $error = 0;
913
914 // Protection
915 if ($this->status == self::STATUS_VALIDATED) {
916 dol_syslog(get_class($this)."::validate action abandonned: already validated", LOG_WARNING);
917 return 0;
918 }
919
920 /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->create))
921 || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
922 {
923 $this->error='NotEnoughPermissions';
924 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
925 return -1;
926 }*/
927
928 $now = dol_now();
929
930 $this->db->begin();
931
932 // Define new ref
933 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
934 $this->fetch_product();
935 $num = $this->getNextNumRef($this->product);
936 } else {
937 $num = $this->ref;
938 }
939 $this->newref = dol_sanitizeFileName($num);
940
941 // Validate
942 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
943 $sql .= " SET ref = '".$this->db->escape($num)."',";
944 $sql .= " status = ".self::STATUS_VALIDATED.",";
945 $sql .= " date_valid='".$this->db->idate($now)."',";
946 $sql .= " fk_user_valid = ".((int) $user->id);
947 $sql .= " WHERE rowid = ".((int) $this->id);
948
949 dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
950 $resql = $this->db->query($sql);
951 if (!$resql) {
952 dol_print_error($this->db);
953 $this->error = $this->db->lasterror();
954 $error++;
955 }
956
957 if (!$error && !$notrigger) {
958 // Call trigger
959 $result = $this->call_trigger('BOM_VALIDATE', $user);
960 if ($result < 0) {
961 $error++;
962 }
963 // End call triggers
964 }
965
966 if (!$error) {
967 $this->oldref = $this->ref;
968
969 // Rename directory if dir was a temporary ref
970 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
971 // Now we rename also files into index
972 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'bom/".$this->db->escape($this->newref)."'";
973 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
974 $resql = $this->db->query($sql);
975 if (!$resql) {
976 $error++; $this->error = $this->db->lasterror();
977 }
978 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'bom/".$this->db->escape($this->newref)."'";
979 $sql .= " WHERE filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
980 $resql = $this->db->query($sql);
981 if (!$resql) {
982 $error++; $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 /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->write))
1039 || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
1040 {
1041 $this->error='Permission denied';
1042 return -1;
1043 }*/
1044
1045 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'BOM_UNVALIDATE');
1046 }
1047
1055 public function cancel($user, $notrigger = 0)
1056 {
1057 // Protection
1058 if ($this->status != self::STATUS_VALIDATED) {
1059 return 0;
1060 }
1061
1062 /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->write))
1063 || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
1064 {
1065 $this->error='Permission denied';
1066 return -1;
1067 }*/
1068
1069 return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'BOM_CLOSE');
1070 }
1071
1079 public function reopen($user, $notrigger = 0)
1080 {
1081 // Protection
1082 if ($this->status != self::STATUS_CANCELED) {
1083 return 0;
1084 }
1085
1086 /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->write))
1087 || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->bom->bom_advance->validate))))
1088 {
1089 $this->error='Permission denied';
1090 return -1;
1091 }*/
1092
1093 return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'BOM_REOPEN');
1094 }
1095
1102 public function getTooltipContentArray($params)
1103 {
1104 global $conf, $langs, $user;
1105
1106 $langs->loadLangs(['product', 'mrp']);
1107
1108 $datas = [];
1109
1110 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1111 return ['optimize' => $langs->trans("ShowBillOfMaterials")];
1112 }
1113 $picto = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("BillOfMaterials").'</u>';
1114 if (isset($this->status)) {
1115 $picto .= ' '.$this->getLibStatut(5);
1116 }
1117 $datas['picto'] = $picto;
1118 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1119 if (isset($this->label)) {
1120 $datas['label'] = '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
1121 }
1122 if (!empty($this->fk_product) && $this->fk_product > 0) {
1123 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1124 $product = new Product($this->db);
1125 $resultFetch = $product->fetch($this->fk_product);
1126 if ($resultFetch > 0) {
1127 $datas['product'] = "<br><b>".$langs->trans("Product").'</b>: '.$product->ref.' - '.$product->label;
1128 }
1129 }
1130
1131 return $datas;
1132 }
1133
1144 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1145 {
1146 global $db, $conf, $langs, $hookmanager;
1147
1148 if (!empty($conf->dol_no_mouse_hover)) {
1149 $notooltip = 1; // Force disable tooltips
1150 }
1151
1152 $result = '';
1153 $params = [
1154 'id' => $this->id,
1155 'objecttype' => $this->element,
1156 'option' => $option,
1157 ];
1158 $classfortooltip = 'classfortooltip';
1159 $dataparams = '';
1160 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1161 $classfortooltip = 'classforajaxtooltip';
1162 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1163 $label = '';
1164 } else {
1165 $label = implode($this->getTooltipContentArray($params));
1166 }
1167
1168 $url = DOL_URL_ROOT.'/bom/bom_card.php?id='.$this->id;
1169
1170 if ($option != 'nolink') {
1171 // Add param to save lastsearch_values or not
1172 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1173 if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1174 $add_save_lastsearch_values = 1;
1175 }
1176 if ($add_save_lastsearch_values) {
1177 $url .= '&save_lastsearch_values=1';
1178 }
1179 }
1180
1181 $linkclose = '';
1182 if (empty($notooltip)) {
1183 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1184 $label = $langs->trans("ShowBillOfMaterials");
1185 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1186 }
1187 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1188 $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1189 } else {
1190 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1191 }
1192
1193 $linkstart = '<a href="'.$url.'"';
1194 $linkstart .= $linkclose.'>';
1195 $linkend = '</a>';
1196
1197 $result .= $linkstart;
1198 if ($withpicto) {
1199 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1200 }
1201 if ($withpicto != 2) {
1202 $result .= $this->ref;
1203 }
1204 $result .= $linkend;
1205 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1206
1207 global $action, $hookmanager;
1208 $hookmanager->initHooks(array('bomdao'));
1209 $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
1210 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1211 if ($reshook > 0) {
1212 $result = $hookmanager->resPrint;
1213 } else {
1214 $result .= $hookmanager->resPrint;
1215 }
1216
1217 return $result;
1218 }
1219
1226 public function getLibStatut($mode = 0)
1227 {
1228 return $this->LibStatut($this->status, $mode);
1229 }
1230
1231 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1239 public function LibStatut($status, $mode = 0)
1240 {
1241 // phpcs:enable
1242 if (empty($this->labelStatus)) {
1243 global $langs;
1244 //$langs->load("mrp");
1245 $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1246 $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Enabled');
1247 $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Disabled');
1248 }
1249
1250 $statusType = 'status'.$status;
1251 if ($status == self::STATUS_VALIDATED) {
1252 $statusType = 'status4';
1253 }
1254 if ($status == self::STATUS_CANCELED) {
1255 $statusType = 'status6';
1256 }
1257
1258 return dolGetStatus($this->labelStatus[$status], $this->labelStatus[$status], '', $statusType, $mode);
1259 }
1260
1267 public function info($id)
1268 {
1269 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1270 $sql .= ' fk_user_creat, fk_user_modif';
1271 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1272 $sql .= ' WHERE t.rowid = '.((int) $id);
1273 $result = $this->db->query($sql);
1274 if ($result) {
1275 if ($this->db->num_rows($result)) {
1276 $obj = $this->db->fetch_object($result);
1277 $this->id = $obj->rowid;
1278
1279 $this->user_creation_id = $obj->fk_user_creat;
1280 $this->user_modification_id = $obj->fk_user_modif;
1281 $this->date_creation = $this->db->jdate($obj->datec);
1282 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1283 }
1284
1285 $this->db->free($result);
1286 } else {
1287 dol_print_error($this->db);
1288 }
1289 }
1290
1296 public function getLinesArray()
1297 {
1298 $this->lines = array();
1299
1300 $objectline = new BOMLine($this->db);
1301 $result = $objectline->fetchAll('ASC', 'position', 0, 0, array('customsql'=>'fk_bom = '.((int) $this->id)));
1302
1303 if (is_numeric($result)) {
1304 $this->error = $objectline->error;
1305 $this->errors = $objectline->errors;
1306 return $result;
1307 } else {
1308 $this->lines = $result;
1309 return $this->lines;
1310 }
1311 }
1312
1324 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
1325 {
1326 global $conf, $langs;
1327
1328 $langs->load("mrp");
1329 $outputlangs->load("products");
1330
1331 if (!dol_strlen($modele)) {
1332 $modele = '';
1333
1334 if ($this->model_pdf) {
1335 $modele = $this->model_pdf;
1336 } elseif (!empty($conf->global->BOM_ADDON_PDF)) {
1337 $modele = $conf->global->BOM_ADDON_PDF;
1338 }
1339 }
1340
1341 $modelpath = "core/modules/bom/doc/";
1342 if (!empty($modele)) {
1343 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
1344 } else {
1345 return 0;
1346 }
1347 }
1348
1355 public function initAsSpecimen()
1356 {
1357 $this->initAsSpecimenCommon();
1358 $this->ref = 'BOM-123';
1359 $this->date = $this->date_creation;
1360 }
1361
1362
1369 public function doScheduledJob()
1370 {
1371 global $conf, $langs;
1372
1373 //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
1374
1375 $error = 0;
1376 $this->output = '';
1377 $this->error = '';
1378
1379 dol_syslog(__METHOD__, LOG_DEBUG);
1380
1381 $now = dol_now();
1382
1383 $this->db->begin();
1384
1385 // ...
1386
1387 $this->db->commit();
1388
1389 return $error;
1390 }
1391
1398 public function calculateCosts()
1399 {
1400 global $conf, $hookmanager;
1401
1402 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1403 $this->unit_cost = 0;
1404 $this->total_cost = 0;
1405
1406 $parameters=array();
1407 $reshook = $hookmanager->executeHooks('calculateCostsBom', $parameters, $this); // Note that $action and $object may have been modified by hook
1408
1409 if ($reshook > 0) {
1410 return $hookmanager->resPrint;
1411 }
1412
1413 if (is_array($this->lines) && count($this->lines)) {
1414 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1415 $productFournisseur = new ProductFournisseur($this->db);
1416 $tmpproduct = new Product($this->db);
1417
1418 foreach ($this->lines as &$line) {
1419 $tmpproduct->cost_price = 0;
1420 $tmpproduct->pmp = 0;
1421 $result = $tmpproduct->fetch($line->fk_product, '', '', '', 0, 1, 1); // We discard selling price and language loading
1422
1423 if ($tmpproduct->type == $tmpproduct::TYPE_PRODUCT) {
1424 if (empty($line->fk_bom_child)) {
1425 if ($result < 0) {
1426 $this->error = $tmpproduct->error;
1427 return -1;
1428 }
1429 $line->unit_cost = price2num((!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp);
1430 if (empty($line->unit_cost)) {
1431 if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
1432 if ($productFournisseur->fourn_remise_percent != "0") {
1433 $line->unit_cost = $productFournisseur->fourn_unitprice_with_discount;
1434 } else {
1435 $line->unit_cost = $productFournisseur->fourn_unitprice;
1436 }
1437 }
1438 }
1439
1440 $line->total_cost = price2num($line->qty * $line->unit_cost, 'MT');
1441
1442 $this->total_cost += $line->total_cost;
1443 } else {
1444 $bom_child = new BOM($this->db);
1445 $res = $bom_child->fetch($line->fk_bom_child);
1446 if ($res > 0) {
1447 $bom_child->calculateCosts();
1448 $line->childBom[] = $bom_child;
1449 $this->total_cost += price2num($bom_child->total_cost * $line->qty, 'MT');
1450 $this->total_cost += $line->total_cost;
1451 } else {
1452 $this->error = $bom_child->error;
1453 return -2;
1454 }
1455 }
1456 } else {
1457 // Convert qty of line into hours
1458 $unitforline = measuringUnitString($line->fk_unit, '', '', 1);
1459 $qtyhourforline = convertDurationtoHour($line->qty, $unitforline);
1460
1461 if (isModEnabled('workstation') && !empty($tmpproduct->fk_default_workstation)) {
1462 $workstation = new Workstation($this->db);
1463 $res = $workstation->fetch($tmpproduct->fk_default_workstation);
1464
1465 if ($res > 0) $line->total_cost = price2num($qtyhourforline * ($workstation->thm_operator_estimated + $workstation->thm_machine_estimated), 'MT');
1466 else {
1467 $this->error = $workstation->error;
1468 return -3;
1469 }
1470 } else {
1471 $defaultdurationofservice = $tmpproduct->duration;
1472 $reg = array();
1473 $qtyhourservice = 0;
1474 if (preg_match('/^(\d+)([a-z]+)$/', $defaultdurationofservice, $reg)) {
1475 $qtyhourservice = convertDurationtoHour($reg[1], $reg[2]);
1476 }
1477
1478 if ($qtyhourservice) {
1479 $line->total_cost = price2num($qtyhourforline / $qtyhourservice * $tmpproduct->cost_price, 'MT');
1480 } else {
1481 $line->total_cost = price2num($line->qty * $tmpproduct->cost_price, 'MT');
1482 }
1483 }
1484
1485 $this->total_cost += $line->total_cost;
1486 }
1487 }
1488
1489 $this->total_cost = price2num($this->total_cost, 'MT');
1490
1491 if ($this->qty > 0) {
1492 $this->unit_cost = price2num($this->total_cost / $this->qty, 'MU');
1493 } elseif ($this->qty < 0) {
1494 $this->unit_cost = price2num($this->total_cost * $this->qty, 'MU');
1495 }
1496 }
1497
1498 return 1;
1499 }
1500
1509 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
1510 {
1511 $tables = array(
1512 'bom_bomline'
1513 );
1514
1515 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
1516 }
1517
1525 public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
1526 {
1527 if (!empty($this->lines)) {
1528 foreach ($this->lines as $line) {
1529 if (!empty($line->childBom)) {
1530 foreach ($line->childBom as $childBom) $childBom->getNetNeeds($TNetNeeds, $line->qty*$qty);
1531 } else {
1532 if (empty($TNetNeeds[$line->fk_product])) {
1533 $TNetNeeds[$line->fk_product] = 0;
1534 }
1535 $TNetNeeds[$line->fk_product] += $line->qty*$qty;
1536 }
1537 }
1538 }
1539 }
1540
1549 public function getNetNeedsTree(&$TNetNeeds = array(), $qty = 0, $level = 0)
1550 {
1551 if (!empty($this->lines)) {
1552 foreach ($this->lines as $line) {
1553 if (!empty($line->childBom)) {
1554 foreach ($line->childBom as $childBom) {
1555 $TNetNeeds[$childBom->id]['bom'] = $childBom;
1556 $TNetNeeds[$childBom->id]['parentid'] = $this->id;
1557 $TNetNeeds[$childBom->id]['qty'] = $line->qty*$qty;
1558 $TNetNeeds[$childBom->id]['level'] = $level;
1559 $childBom->getNetNeedsTree($TNetNeeds, $line->qty*$qty, $level+1);
1560 }
1561 } else {
1562 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
1563 $TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
1564 }
1565 }
1566 }
1567 }
1568
1577 public function getParentBomTreeRecursive(&$TParentBom, $bom_id = '', $level = 1)
1578 {
1579
1580 // Protection against infinite loop
1581 if ($level > 1000) {
1582 return;
1583 }
1584
1585 if (empty($bom_id)) $bom_id=$this->id;
1586
1587 $sql = 'SELECT l.fk_bom, b.label
1588 FROM '.MAIN_DB_PREFIX.'bom_bomline l
1589 INNER JOIN '.MAIN_DB_PREFIX.$this->table_element.' b ON b.rowid = l.fk_bom
1590 WHERE fk_bom_child = '.((int) $bom_id);
1591
1592 $resql = $this->db->query($sql);
1593 if (!empty($resql)) {
1594 while ($res = $this->db->fetch_object($resql)) {
1595 $TParentBom[$res->fk_bom] = $res->fk_bom;
1596 $this->getParentBomTreeRecursive($TParentBom, $res->fk_bom, $level+1);
1597 }
1598 }
1599 }
1600
1608 public function getKanbanView($option = '', $arraydata = null)
1609 {
1610 global $db,$langs;
1611
1612 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1613
1614 $prod = new Product($db);
1615 $prod->fetch($this->fk_product);
1616
1617 $return = '<div class="box-flex-item box-flex-grow-zero">';
1618 $return .= '<div class="info-box info-box-sm">';
1619 $return .= '<span class="info-box-icon bg-infobox-action">';
1620 $return .= img_picto('', $this->picto);
1621 $return .= '</span>';
1622 $return .= '<div class="info-box-content">';
1623 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : '').'</span>';
1624 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1625 if (property_exists($this, 'fields') && !empty($this->fields['bomtype']['arrayofkeyval'])) {
1626 $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Type").' : </span>';
1627 if ($this->bomtype == 0) {
1628 $return .= '<span class="info-box-label">'.$this->fields['bomtype']['arrayofkeyval'][0].'</span>';
1629 } else {
1630 $return .= '<span class="info-box-label">'.$this->fields['bomtype']['arrayofkeyval'][1].'</span>';
1631 }
1632 }
1633 if (property_exists($this, 'fk_product') && !is_null($this->fk_product)) {
1634 $return .= '<br><span class="info-box-label">'.$prod->getNomUrl(1).'</span>';
1635 }
1636 if (method_exists($this, 'getLibStatut')) {
1637 $return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(3).'</div>';
1638 }
1639
1640 $return .= '</div>';
1641 $return .= '</div>';
1642 $return .= '</div>';
1643 return $return;
1644 }
1645}
1646
1647
1652{
1656 public $element = 'bomline';
1657
1661 public $table_element = 'bom_bomline';
1662
1666 public $ismultientitymanaged = 0;
1667
1671 public $isextrafieldmanaged = 1;
1672
1676 public $picto = 'bomline';
1677
1678
1698 // BEGIN MODULEBUILDER PROPERTIES
1702 public $fields = array(
1703 'rowid' => array('type'=>'integer', 'label'=>'LineID', 'enabled'=>1, 'visible'=>-1, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>"Id",),
1704 'fk_bom' => array('type'=>'integer:BillOfMaterials:societe/class/bom.class.php', 'label'=>'BillOfMaterials', 'enabled'=>1, 'visible'=>1, 'position'=>10, 'notnull'=>1, 'index'=>1,),
1705 'fk_product' => array('type'=>'integer:Product:product/class/product.class.php', 'label'=>'Product', 'enabled'=>1, 'visible'=>1, 'position'=>20, 'notnull'=>1, 'index'=>1,),
1706 'fk_bom_child' => array('type'=>'integer:BOM:bom/class/bom.class.php', 'label'=>'BillOfMaterials', 'enabled'=>1, 'visible'=>-1, 'position'=>40, 'notnull'=>-1,),
1707 'description' => array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>-1, 'position'=>60, 'notnull'=>-1,),
1708 'qty' => array('type'=>'double(24,8)', 'label'=>'Quantity', 'enabled'=>1, 'visible'=>1, 'position'=>100, 'notnull'=>1, 'isameasure'=>'1',),
1709 'qty_frozen' => array('type'=>'smallint', 'label'=>'QuantityFrozen', 'enabled'=>1, 'visible'=>1, 'default'=>0, 'position'=>105, 'css'=>'maxwidth50imp', 'help'=>'QuantityConsumedInvariable'),
1710 'disable_stock_change' => array('type'=>'smallint', 'label'=>'DisableStockChange', 'enabled'=>1, 'visible'=>1, 'default'=>0, 'position'=>108, 'css'=>'maxwidth50imp', 'help'=>'DisableStockChangeHelp'),
1711 'efficiency' => array('type'=>'double(24,8)', 'label'=>'ManufacturingEfficiency', 'enabled'=>1, 'visible'=>0, 'default'=>1, 'position'=>110, 'notnull'=>1, 'css'=>'maxwidth50imp', 'help'=>'ValueOfEfficiencyConsumedMeans'),
1712 'fk_unit' => array('type'=>'integer', 'label'=>'Unit', 'enabled'=>1, 'visible'=>1, 'position'=>120, 'notnull'=>-1,),
1713 'position' => array('type'=>'integer', 'label'=>'Rank', 'enabled'=>1, 'visible'=>0, 'default'=>0, 'position'=>200, 'notnull'=>1,),
1714 'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>1000, 'notnull'=>-1,),
1715 'fk_default_workstation' =>array('type'=>'integer', 'label'=>'DefaultWorkstation', 'enabled'=>1, 'visible'=>1, 'notnull'=>0, 'position'=>1050)
1716 );
1717
1721 public $rowid;
1722
1726 public $fk_bom;
1727
1731 public $fk_product;
1732
1736 public $fk_bom_child;
1737
1741 public $description;
1742
1746 public $qty;
1747
1751 public $qty_frozen;
1752
1756 public $disable_stock_change;
1757
1761 public $efficiency;
1762
1766 public $position;
1767
1771 public $import_key;
1772 // END MODULEBUILDER PROPERTIES
1773
1777 public $total_cost = 0;
1778
1782 public $unit_cost = 0;
1783
1787 public $childBom = array();
1788
1792 public $fk_unit;
1793
1797 public $fk_default_workstation;
1798
1799
1800
1806 public function __construct(DoliDB $db)
1807 {
1808 global $conf, $langs;
1809
1810 $this->db = $db;
1811
1812 if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && isset($this->fields['rowid'])) {
1813 $this->fields['rowid']['visible'] = 0;
1814 }
1815 if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
1816 $this->fields['entity']['enabled'] = 0;
1817 }
1818
1819 // Unset fields that are disabled
1820 foreach ($this->fields as $key => $val) {
1821 if (isset($val['enabled']) && empty($val['enabled'])) {
1822 unset($this->fields[$key]);
1823 }
1824 }
1825
1826 // Translate some data of arrayofkeyval
1827 foreach ($this->fields as $key => $val) {
1828 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
1829 foreach ($val['arrayofkeyval'] as $key2 => $val2) {
1830 $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
1831 }
1832 }
1833 }
1834 }
1835
1843 public function create(User $user, $notrigger = false)
1844 {
1845 if ($this->efficiency < 0 || $this->efficiency > 1) {
1846 $this->efficiency = 1;
1847 }
1848
1849 return $this->createCommon($user, $notrigger);
1850 }
1851
1859 public function fetch($id, $ref = null)
1860 {
1861 $result = $this->fetchCommon($id, $ref);
1862 //if ($result > 0 && !empty($this->table_element_line)) $this->fetchLines();
1863 return $result;
1864 }
1865
1877 public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
1878 {
1879 global $conf;
1880
1881 dol_syslog(__METHOD__, LOG_DEBUG);
1882
1883 $records = array();
1884
1885 $sql = 'SELECT ';
1886 $sql .= $this->getFieldList();
1887 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1888 if ($this->ismultientitymanaged) {
1889 $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
1890 } else {
1891 $sql .= ' WHERE 1 = 1';
1892 }
1893 // Manage filter
1894 $sqlwhere = array();
1895 if (count($filter) > 0) {
1896 foreach ($filter as $key => $value) {
1897 if ($key == 't.rowid') {
1898 $sqlwhere[] = $key." = ".((int) $value);
1899 } elseif (strpos($key, 'date') !== false) {
1900 $sqlwhere[] = $key." = '".$this->db->idate($value)."'";
1901 } elseif ($key == 'customsql') {
1902 $sqlwhere[] = $value;
1903 } else {
1904 $sqlwhere[] = $key." LIKE '%".$this->db->escape($value)."%'";
1905 }
1906 }
1907 }
1908 if (count($sqlwhere) > 0) {
1909 $sql .= ' AND ('.implode(' '.$this->db->escape($filtermode).' ', $sqlwhere).')';
1910 }
1911
1912 if (!empty($sortfield)) {
1913 $sql .= $this->db->order($sortfield, $sortorder);
1914 }
1915 if (!empty($limit)) {
1916 $sql .= $this->db->plimit($limit, $offset);
1917 }
1918
1919 $resql = $this->db->query($sql);
1920 if ($resql) {
1921 $num = $this->db->num_rows($resql);
1922
1923 while ($obj = $this->db->fetch_object($resql)) {
1924 $record = new self($this->db);
1925 $record->setVarsFromFetchObj($obj);
1926
1927 $records[$record->id] = $record;
1928 }
1929 $this->db->free($resql);
1930
1931 return $records;
1932 } else {
1933 $this->errors[] = 'Error '.$this->db->lasterror();
1934 dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
1935
1936 return -1;
1937 }
1938 }
1939
1947 public function update(User $user, $notrigger = false)
1948 {
1949 if ($this->efficiency < 0 || $this->efficiency > 1) {
1950 $this->efficiency = 1;
1951 }
1952
1953 return $this->updateCommon($user, $notrigger);
1954 }
1955
1963 public function delete(User $user, $notrigger = false)
1964 {
1965 return $this->deleteCommon($user, $notrigger);
1966 //return $this->deleteCommon($user, $notrigger, 1);
1967 }
1968
1979 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1980 {
1981 global $db, $conf, $langs, $hookmanager;
1982
1983 if (!empty($conf->dol_no_mouse_hover)) {
1984 $notooltip = 1; // Force disable tooltips
1985 }
1986
1987 $result = '';
1988
1989 $label = '<u>'.$langs->trans("BillOfMaterialsLine").'</u>';
1990 $label .= '<br>';
1991 $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref;
1992
1993 $url = DOL_URL_ROOT.'/bom/bomline_card.php?id='.$this->id;
1994
1995 if ($option != 'nolink') {
1996 // Add param to save lastsearch_values or not
1997 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1998 if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1999 $add_save_lastsearch_values = 1;
2000 }
2001 if ($add_save_lastsearch_values) {
2002 $url .= '&save_lastsearch_values=1';
2003 }
2004 }
2005
2006 $linkclose = '';
2007 if (empty($notooltip)) {
2008 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
2009 $label = $langs->trans("ShowBillOfMaterialsLine");
2010 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
2011 }
2012 $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
2013 $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
2014 } else {
2015 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
2016 }
2017
2018 $linkstart = '<a href="'.$url.'"';
2019 $linkstart .= $linkclose.'>';
2020 $linkend = '</a>';
2021
2022 $result .= $linkstart;
2023 if ($withpicto) {
2024 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
2025 }
2026 if ($withpicto != 2) {
2027 $result .= $this->ref;
2028 }
2029 $result .= $linkend;
2030 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
2031
2032 global $action, $hookmanager;
2033 $hookmanager->initHooks(array('bomlinedao'));
2034 $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
2035 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2036 if ($reshook > 0) {
2037 $result = $hookmanager->resPrint;
2038 } else {
2039 $result .= $hookmanager->resPrint;
2040 }
2041
2042 return $result;
2043 }
2044
2051 public function getLibStatut($mode = 0)
2052 {
2053 return $this->LibStatut($this->status, $mode);
2054 }
2055
2056 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2064 public function LibStatut($status, $mode = 0)
2065 {
2066 // phpcs:enable
2067 return '';
2068 }
2069
2076 public function info($id)
2077 {
2078 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
2079 $sql .= ' fk_user_creat, fk_user_modif';
2080 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
2081 $sql .= ' WHERE t.rowid = '.((int) $id);
2082 $result = $this->db->query($sql);
2083 if ($result) {
2084 if ($this->db->num_rows($result)) {
2085 $obj = $this->db->fetch_object($result);
2086 $this->id = $obj->rowid;
2087 $this->user_creation_id = $obj->fk_user_creat;
2088 $this->user_modification_id = $obj->fk_user_modif;
2089 $this->date_creation = $this->db->jdate($obj->datec);
2090 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
2091 }
2092 $this->db->free($result);
2093 } else {
2094 dol_print_error($this->db);
2095 }
2096 }
2097
2104 public function initAsSpecimen()
2105 {
2106 $this->initAsSpecimenCommon();
2107 }
2108}
$object ref
Definition info.php:78
Class for BOM.
Definition bom.class.php:39
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.
__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)
Set cancel status.
fetchAll($sortorder='', $sortfield='', $limit=0, $offset=0, array $filter=array(), $filtermode='AND')
Load list of objects in memory from the database.
cancel($user, $notrigger=0)
Set cancel status.
create(User $user, $notrigger=false)
Create object into database.
LibStatut($status, $mode=0)
Return the status.
doScheduledJob()
Action executed by scheduler CAN BE A CRON TASK.
getTooltipContentArray($params)
getTooltipContentArray
fetchLinesbytypeproduct($typeproduct=0)
Load object lines in memory from the database by type of product.
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 optionaly the picto)
getNetNeeds(&$TNetNeeds=array(), $qty=0)
Get Net needs by product.
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='', $array_options=0, $fk_default_workstation=null)
Add an BOM line into database (linked to BOM)
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.
updateLine($rowid, $qty, $qty_frozen=0, $disable_stock_change=0, $efficiency=1.0, $position=-1, $import_key=null, $fk_unit=0, $array_options=0)
Update an BOM line into database.
update(User $user, $notrigger=false)
Update object into database.
getKanbanView($option='', $arraydata=null)
Return clicable link of object (with eventually picto)
deleteLine(User $user, $idline, $notrigger=false)
Delete a line of object in database.
getParentBomTreeRecursive(&$TParentBom, $bom_id='', $level=1)
Recursively retrieves all parent bom in the tree that leads to the $bom_id bom.
getNetNeedsTree(&$TNetNeeds=array(), $qty=0, $level=0)
Get Net needs Tree by product or bom.
Class for BOMLine.
create(User $user, $notrigger=false)
Create object into database.
fetchAll($sortorder='', $sortfield='', $limit=0, $offset=0, array $filter=array(), $filtermode='AND')
Load list of objects in memory from the database.
fetch($id, $ref=null)
Load object in memory from the database.
getLibStatut($mode=0)
Return label of the status.
update(User $user, $notrigger=false)
Update object into database.
__construct(DoliDB $db)
Constructor.
getNomUrl($withpicto=0, $option='', $notooltip=0, $morecss='', $save_lastsearch_value=-1)
Return a link to the object card (with optionaly the picto)
initAsSpecimen()
Initialise object with example values Id must be 0 if object instance is a specimen.
info($id)
Load the info information in the object.
LibStatut($status, $mode=0)
Return the status.
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
fetchCommon($id, $ref=null, $morewhere='')
Load object in memory from the database.
createCommon(User $user, $notrigger=false)
Create object into database.
deleteCommon(User $user, $notrigger=false, $forcechilddeletion=0)
Delete object in database.
getFieldList($alias='', $excludefields=array())
Function to concat keys of fields.
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.
updateCommon(User $user, $notrigger=false)
Update object into 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)
fetchLinesCommon($morewhere='')
Load object in memory from the database.
call_trigger($triggerName, $user)
Call trigger based on this instance.
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
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 m m statut
Definition index.php:152
convertDurationtoHour($duration_value, $duration_unit)
Convert duration to hour.
Definition date.lib.php:333
dol_dir_list($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:62
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return dolibarr global constant int value.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
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_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
measuringUnitString($unit, $measuring_style='', $scale='', $use_short_label=0, $outputlangs=null)
Return translation label of a unit key.