dolibarr 20.0.2
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/class/commonobjectline.class.php';
31require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.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_creation;
161
165 public $date_valid;
166
170 public $fk_user_creat;
171
175 public $fk_user_modif;
176
180 public $fk_user_valid;
181
185 public $fk_warehouse;
186
190 public $import_key;
191
195 public $status;
196
200 public $fk_product;
201 public $qty;
202 public $duration;
203 public $efficiency;
204 // END MODULEBUILDER PROPERTIES
205
206
207 // If this object has a subtable with lines
208
212 public $table_element_line = 'bom_bomline';
213
217 public $fk_element = 'fk_bom';
218
222 public $class_element_line = 'BOMLine';
223
224 // /**
225 // * @var array List of child tables. To test if we can delete object.
226 // */
227 // protected $childtables=array();
228
232 protected $childtablesoncascade = array('bom_bomline');
233
237 public $lines = array();
238
242 public $total_cost = 0;
243
247 public $unit_cost = 0;
248
249
255 public function __construct(DoliDB $db)
256 {
257 global $conf, $langs;
258
259 $this->db = $db;
260
261 $this->ismultientitymanaged = 1;
262 $this->isextrafieldmanaged = 1;
263
264 if (!getDolGlobalString('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 = 1)
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, $filter = '', $filtermode = 'AND')
491 {
492 dol_syslog(__METHOD__, LOG_DEBUG);
493
494 $records = array();
495
496 $sql = 'SELECT ';
497 $sql .= $this->getFieldList();
498 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
499 if ($this->ismultientitymanaged) {
500 $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
501 } else {
502 $sql .= ' WHERE 1 = 1';
503 }
504
505 // Manage filter
506 $errormessage = '';
507 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
508 if ($errormessage) {
509 $this->errors[] = $errormessage;
510 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
511 return -1;
512 }
513
514 if (!empty($sortfield)) {
515 $sql .= $this->db->order($sortfield, $sortorder);
516 }
517 if (!empty($limit)) {
518 $sql .= $this->db->plimit($limit, $offset);
519 }
520
521 $resql = $this->db->query($sql);
522 if ($resql) {
523 $num = $this->db->num_rows($resql);
524
525 while ($obj = $this->db->fetch_object($resql)) {
526 $record = new self($this->db);
527 $record->setVarsFromFetchObj($obj);
528
529 $records[$record->id] = $record;
530 }
531 $this->db->free($resql);
532
533 return $records;
534 } else {
535 $this->errors[] = 'Error '.$this->db->lasterror();
536 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
537
538 return -1;
539 }
540 }
541
549 public function update(User $user, $notrigger = 1)
550 {
551 if ($this->efficiency <= 0 || $this->efficiency > 1) {
552 $this->efficiency = 1;
553 }
554
555 return $this->updateCommon($user, $notrigger);
556 }
557
565 public function delete(User $user, $notrigger = 1)
566 {
567 return $this->deleteCommon($user, $notrigger);
568 //return $this->deleteCommon($user, $notrigger, 1);
569 }
570
587 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)
588 {
589 global $mysoc, $conf, $langs, $user;
590
591 $logtext = "::addLine bomid=$this->id, qty=$qty, fk_product=$fk_product, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
592 $logtext .= ", fk_bom_child=$fk_bom_child, import_key=$import_key";
593 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
594
595 if ($this->statut == self::STATUS_DRAFT) {
596 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
597
598 // Clean parameters
599 if (empty($qty)) {
600 $qty = 0;
601 }
602 if (empty($qty_frozen)) {
603 $qty_frozen = 0;
604 }
605 if (empty($disable_stock_change)) {
606 $disable_stock_change = 0;
607 }
608 if (empty($efficiency)) {
609 $efficiency = 1.0;
610 }
611 if (empty($fk_bom_child)) {
612 $fk_bom_child = null;
613 }
614 if (empty($import_key)) {
615 $import_key = null;
616 }
617 if (empty($position)) {
618 $position = -1;
619 }
620
621 $qty = (float) price2num($qty);
622 $efficiency = (float) price2num($efficiency);
623 $position = (float) price2num($position);
624
625 $this->db->begin();
626
627 // Rank to use
628 $rangMax = $this->line_max();
629 $rankToUse = $position;
630 if ($rankToUse <= 0 or $rankToUse > $rangMax) { // New line after existing lines
631 $rankToUse = $rangMax + 1;
632 } else { // New line between the existing lines
633 foreach ($this->lines as $bl) {
634 if ($bl->position >= $rankToUse) {
635 $bl->position++;
636 $bl->update($user);
637 }
638 }
639 }
640
641 // Insert line
642 $line = new BOMLine($this->db);
643
644 $line->context = $this->context;
645
646 $line->fk_bom = $this->id;
647 $line->fk_product = $fk_product;
648 $line->qty = $qty;
649 $line->qty_frozen = $qty_frozen;
650 $line->disable_stock_change = $disable_stock_change;
651 $line->efficiency = $efficiency;
652 $line->fk_bom_child = $fk_bom_child;
653 $line->import_key = $import_key;
654 $line->position = $rankToUse;
655 $line->fk_unit = $fk_unit;
656 $line->fk_default_workstation = $fk_default_workstation;
657
658 if (is_array($array_options) && count($array_options) > 0) {
659 $line->array_options = $array_options;
660 }
661
662 $result = $line->create($user);
663
664 if ($result > 0) {
665 $this->calculateCosts();
666 $this->db->commit();
667 return $result;
668 } else {
669 $this->setErrorsFromObject($line);
670 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
671 $this->db->rollback();
672 return -2;
673 }
674 } else {
675 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
676 return -3;
677 }
678 }
679
695 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)
696 {
697 global $user;
698
699 $logtext = "::updateLine bomid=$this->id, qty=$qty, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
700 $logtext .= ", import_key=$import_key";
701 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
702
703 if ($this->statut == self::STATUS_DRAFT) {
704 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
705
706 // Clean parameters
707 if (empty($qty)) {
708 $qty = 0;
709 }
710 if (empty($qty_frozen)) {
711 $qty_frozen = 0;
712 }
713 if (empty($disable_stock_change)) {
714 $disable_stock_change = 0;
715 }
716 if (empty($efficiency)) {
717 $efficiency = 1.0;
718 }
719 if (empty($import_key)) {
720 $import_key = null;
721 }
722 if (empty($position)) {
723 $position = -1;
724 }
725
726 $qty = (float) price2num($qty);
727 $efficiency = (float) price2num($efficiency);
728 $position = (float) price2num($position);
729
730 $this->db->begin();
731
732 //Fetch current line from the database and then clone the object and set it in $oldline property
733 $line = new BOMLine($this->db);
734 $line->fetch($rowid);
735 $line->fetch_optionals();
736
737 $staticLine = clone $line;
738 $line->oldcopy = $staticLine;
739 $line->context = $this->context;
740
741 // Rank to use
742 $rankToUse = (int) $position;
743 if ($rankToUse != $line->oldcopy->position) { // check if position have a new value
744 foreach ($this->lines as $bl) {
745 if ($bl->position >= $rankToUse and $bl->position < ($line->oldcopy->position + 1)) { // move rank up
746 $bl->position++;
747 $bl->update($user);
748 }
749 if ($bl->position <= $rankToUse and $bl->position > ($line->oldcopy->position)) { // move rank down
750 $bl->position--;
751 $bl->update($user);
752 }
753 }
754 }
755
756
757 $line->fk_bom = $this->id;
758 $line->qty = $qty;
759 $line->qty_frozen = $qty_frozen;
760 $line->disable_stock_change = $disable_stock_change;
761 $line->efficiency = $efficiency;
762 $line->import_key = $import_key;
763 $line->position = $rankToUse;
764
765
766 if (!empty($fk_unit)) {
767 $line->fk_unit = $fk_unit;
768 }
769
770
771 if (is_array($array_options) && count($array_options) > 0) {
772 // We replace values in this->line->array_options only for entries defined into $array_options
773 foreach ($array_options as $key => $value) {
774 $line->array_options[$key] = $array_options[$key];
775 }
776 }
777 if ($line->fk_default_workstation != $fk_default_workstation) {
778 $line->fk_default_workstation = ($fk_default_workstation > 0 ? $fk_default_workstation : 0);
779 }
780
781 $result = $line->update($user);
782
783 if ($result > 0) {
784 $this->calculateCosts();
785 $this->db->commit();
786 return $result;
787 } else {
788 $this->setErrorsFromObject($line);
789 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
790 $this->db->rollback();
791 return -2;
792 }
793 } else {
794 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
795 return -3;
796 }
797 }
798
807 public function deleteLine(User $user, $idline, $notrigger = 0)
808 {
809 if ($this->status < 0) {
810 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
811 return -2;
812 }
813
814 $this->db->begin();
815
816 //Fetch current line from the database and then clone the object and set it in $oldline property
817 $line = new BOMLine($this->db);
818 $line->fetch($idline);
819 $line->fetch_optionals();
820
821 $staticLine = clone $line;
822 $line->oldcopy = $staticLine;
823 $line->context = $this->context;
824
825 $result = $line->delete($user, $notrigger);
826
827 //Positions (rank) reordering
828 foreach ($this->lines as $bl) {
829 if ($bl->position > ($line->oldcopy->position)) { // move rank down
830 $bl->position--;
831 $bl->update($user);
832 }
833 }
834
835 if ($result > 0) {
836 $this->calculateCosts();
837 $this->db->commit();
838 return $result;
839 } else {
840 $this->setErrorsFromObject($line);
841 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
842 $this->db->rollback();
843 return -2;
844 }
845 }
846
854 public function getNextNumRef($prod)
855 {
856 global $langs, $conf;
857 $langs->load("mrp");
858
859 if (getDolGlobalString('BOM_ADDON')) {
860 $mybool = false;
861
862 $file = getDolGlobalString('BOM_ADDON') . ".php";
863 $classname = getDolGlobalString('BOM_ADDON');
864
865 // Include file with class
866 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
867 foreach ($dirmodels as $reldir) {
868 $dir = dol_buildpath($reldir."core/modules/bom/");
869
870 // Load file with numbering class (if found)
871 $mybool = ((bool) @include_once $dir.$file) || $mybool;
872 }
873
874 if ($mybool === false) {
875 dol_print_error(null, "Failed to include file ".$file);
876 return '';
877 }
878
879 $obj = new $classname();
880 $numref = $obj->getNextValue($prod, $this);
881
882 if ($numref != "") {
883 return $numref;
884 } else {
885 $this->error = $obj->error;
886 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
887 return "";
888 }
889 } else {
890 print $langs->trans("Error")." ".$langs->trans("Error_BOM_ADDON_NotDefined");
891 return "";
892 }
893 }
894
902 public function validate($user, $notrigger = 0)
903 {
904 global $conf;
905
906 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
907
908 $error = 0;
909
910 // Protection
911 if ($this->status == self::STATUS_VALIDATED) {
912 dol_syslog(get_class($this)."::validate action abandoned: already validated", LOG_WARNING);
913 return 0;
914 }
915
916 $now = dol_now();
917
918 $this->db->begin();
919
920 // Define new ref
921 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
922 $this->fetch_product();
923 $num = $this->getNextNumRef($this->product);
924 } else {
925 $num = $this->ref;
926 }
927 $this->newref = dol_sanitizeFileName($num);
928
929 // Validate
930 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
931 $sql .= " SET ref = '".$this->db->escape($num)."',";
932 $sql .= " status = ".self::STATUS_VALIDATED.",";
933 $sql .= " date_valid='".$this->db->idate($now)."',";
934 $sql .= " fk_user_valid = ".((int) $user->id);
935 $sql .= " WHERE rowid = ".((int) $this->id);
936
937 dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
938 $resql = $this->db->query($sql);
939 if (!$resql) {
940 dol_print_error($this->db);
941 $this->error = $this->db->lasterror();
942 $error++;
943 }
944
945 if (!$error && !$notrigger) {
946 // Call trigger
947 $result = $this->call_trigger('BOM_VALIDATE', $user);
948 if ($result < 0) {
949 $error++;
950 }
951 // End call triggers
952 }
953
954 if (!$error) {
955 $this->oldref = $this->ref;
956
957 // Rename directory if dir was a temporary ref
958 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
959 // Now we rename also files into index
960 $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)."'";
961 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
962 $resql = $this->db->query($sql);
963 if (!$resql) {
964 $error++;
965 $this->error = $this->db->lasterror();
966 }
967 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'bom/".$this->db->escape($this->newref)."'";
968 $sql .= " WHERE filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
969 $resql = $this->db->query($sql);
970 if (!$resql) {
971 $error++;
972 $this->error = $this->db->lasterror();
973 }
974
975 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
976 $oldref = dol_sanitizeFileName($this->ref);
977 $newref = dol_sanitizeFileName($num);
978 $dirsource = $conf->bom->dir_output.'/'.$oldref;
979 $dirdest = $conf->bom->dir_output.'/'.$newref;
980 if (!$error && file_exists($dirsource)) {
981 dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
982
983 if (@rename($dirsource, $dirdest)) {
984 dol_syslog("Rename ok");
985 // Rename docs starting with $oldref with $newref
986 $listoffiles = dol_dir_list($conf->bom->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
987 foreach ($listoffiles as $fileentry) {
988 $dirsource = $fileentry['name'];
989 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
990 $dirsource = $fileentry['path'].'/'.$dirsource;
991 $dirdest = $fileentry['path'].'/'.$dirdest;
992 @rename($dirsource, $dirdest);
993 }
994 }
995 }
996 }
997 }
998
999 // Set new ref and current status
1000 if (!$error) {
1001 $this->ref = $num;
1002 $this->status = self::STATUS_VALIDATED;
1003 }
1004
1005 if (!$error) {
1006 $this->db->commit();
1007 return 1;
1008 } else {
1009 $this->db->rollback();
1010 return -1;
1011 }
1012 }
1013
1021 public function setDraft($user, $notrigger = 0)
1022 {
1023 // Protection
1024 if ($this->status <= self::STATUS_DRAFT) {
1025 return 0;
1026 }
1027
1028 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'BOM_UNVALIDATE');
1029 }
1030
1038 public function cancel($user, $notrigger = 0)
1039 {
1040 // Protection
1041 if ($this->status != self::STATUS_VALIDATED) {
1042 return 0;
1043 }
1044
1045 return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'BOM_CLOSE');
1046 }
1047
1055 public function reopen($user, $notrigger = 0)
1056 {
1057 // Protection
1058 if ($this->status != self::STATUS_CANCELED) {
1059 return 0;
1060 }
1061
1062 return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'BOM_REOPEN');
1063 }
1064
1071 public function getTooltipContentArray($params)
1072 {
1073 global $conf, $langs, $user;
1074
1075 $langs->loadLangs(['product', 'mrp']);
1076
1077 $datas = [];
1078
1079 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1080 return ['optimize' => $langs->trans("ShowBillOfMaterials")];
1081 }
1082 $picto = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("BillOfMaterials").'</u>';
1083 if (isset($this->status)) {
1084 $picto .= ' '.$this->getLibStatut(5);
1085 }
1086 $datas['picto'] = $picto;
1087 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1088 if (isset($this->label)) {
1089 $datas['label'] = '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
1090 }
1091 if (!empty($this->fk_product) && $this->fk_product > 0) {
1092 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1093 $product = new Product($this->db);
1094 $resultFetch = $product->fetch($this->fk_product);
1095 if ($resultFetch > 0) {
1096 $datas['product'] = "<br><b>".$langs->trans("Product").'</b>: '.$product->ref.' - '.$product->label;
1097 }
1098 }
1099
1100 return $datas;
1101 }
1102
1113 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1114 {
1115 global $db, $conf, $langs, $hookmanager;
1116
1117 if (!empty($conf->dol_no_mouse_hover)) {
1118 $notooltip = 1; // Force disable tooltips
1119 }
1120
1121 $result = '';
1122 $params = [
1123 'id' => $this->id,
1124 'objecttype' => $this->element,
1125 'option' => $option,
1126 ];
1127 $classfortooltip = 'classfortooltip';
1128 $dataparams = '';
1129 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1130 $classfortooltip = 'classforajaxtooltip';
1131 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1132 $label = '';
1133 } else {
1134 $label = implode($this->getTooltipContentArray($params));
1135 }
1136
1137 $url = DOL_URL_ROOT.'/bom/bom_card.php?id='.$this->id;
1138
1139 if ($option != 'nolink') {
1140 // Add param to save lastsearch_values or not
1141 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1142 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1143 $add_save_lastsearch_values = 1;
1144 }
1145 if ($add_save_lastsearch_values) {
1146 $url .= '&save_lastsearch_values=1';
1147 }
1148 }
1149
1150 $linkclose = '';
1151 if (empty($notooltip)) {
1152 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1153 $label = $langs->trans("ShowBillOfMaterials");
1154 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1155 }
1156 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1157 $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1158 } else {
1159 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1160 }
1161
1162 $linkstart = '<a href="'.$url.'"';
1163 $linkstart .= $linkclose.'>';
1164 $linkend = '</a>';
1165
1166 $result .= $linkstart;
1167 if ($withpicto) {
1168 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1169 }
1170 if ($withpicto != 2) {
1171 $result .= $this->ref;
1172 }
1173 $result .= $linkend;
1174 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1175
1176 global $action, $hookmanager;
1177 $hookmanager->initHooks(array('bomdao'));
1178 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1179 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1180 if ($reshook > 0) {
1181 $result = $hookmanager->resPrint;
1182 } else {
1183 $result .= $hookmanager->resPrint;
1184 }
1185
1186 return $result;
1187 }
1188
1195 public function getLibStatut($mode = 0)
1196 {
1197 return $this->LibStatut($this->status, $mode);
1198 }
1199
1200 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1208 public function LibStatut($status, $mode = 0)
1209 {
1210 // phpcs:enable
1211 if (empty($this->labelStatus)) {
1212 global $langs;
1213 //$langs->load("mrp");
1214 $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1215 $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Enabled');
1216 $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Disabled');
1217 }
1218
1219 $statusType = 'status'.$status;
1220 if ($status == self::STATUS_VALIDATED) {
1221 $statusType = 'status4';
1222 }
1223 if ($status == self::STATUS_CANCELED) {
1224 $statusType = 'status6';
1225 }
1226
1227 return dolGetStatus($this->labelStatus[$status], $this->labelStatus[$status], '', $statusType, $mode);
1228 }
1229
1236 public function info($id)
1237 {
1238 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1239 $sql .= ' fk_user_creat, fk_user_modif';
1240 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1241 $sql .= ' WHERE t.rowid = '.((int) $id);
1242 $result = $this->db->query($sql);
1243 if ($result) {
1244 if ($this->db->num_rows($result)) {
1245 $obj = $this->db->fetch_object($result);
1246
1247 $this->id = $obj->rowid;
1248
1249 $this->user_creation_id = $obj->fk_user_creat;
1250 $this->user_modification_id = $obj->fk_user_modif;
1251 $this->date_creation = $this->db->jdate($obj->datec);
1252 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1253 }
1254
1255 $this->db->free($result);
1256 } else {
1257 dol_print_error($this->db);
1258 }
1259 }
1260
1266 public function getLinesArray()
1267 {
1268 $this->lines = array();
1269
1270 $objectline = new BOMLine($this->db);
1271 $result = $objectline->fetchAll('ASC', 'position', 0, 0, '(fk_bom:=:'.((int) $this->id).')');
1272
1273 if (is_numeric($result)) {
1274 $this->error = $objectline->error;
1275 $this->errors = $objectline->errors;
1276 return $result;
1277 } else {
1278 $this->lines = $result;
1279 return $this->lines;
1280 }
1281 }
1282
1294 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
1295 {
1296 global $conf, $langs;
1297
1298 $langs->load("mrp");
1299 $outputlangs->load("products");
1300
1301 if (!dol_strlen($modele)) {
1302 $modele = '';
1303
1304 if ($this->model_pdf) {
1305 $modele = $this->model_pdf;
1306 } elseif (getDolGlobalString('BOM_ADDON_PDF')) {
1307 $modele = getDolGlobalString('BOM_ADDON_PDF');
1308 }
1309 }
1310
1311 $modelpath = "core/modules/bom/doc/";
1312 if (!empty($modele)) {
1313 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
1314 } else {
1315 return 0;
1316 }
1317 }
1318
1319 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1326 public function is_photo_available($sdir)
1327 {
1328 // phpcs:enable
1329 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1330 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
1331
1332 $sdir .= '/'.get_exdir(0, 0, 0, 0, $this, 'bom');
1333
1334 $dir_osencoded = dol_osencode($sdir);
1335 if (file_exists($dir_osencoded)) {
1336 $handle = opendir($dir_osencoded);
1337 if (is_resource($handle)) {
1338 while (($file = readdir($handle)) !== false) {
1339 if (!utf8_check($file)) {
1340 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
1341 }
1342 if (dol_is_file($sdir.$file) && image_format_supported($file) >= 0) {
1343 return true;
1344 }
1345 }
1346 }
1347 }
1348 return false;
1349 }
1350
1357 public function initAsSpecimen()
1358 {
1359 $this->initAsSpecimenCommon();
1360 $this->ref = 'BOM-123';
1361 $this->date_creation = dol_now() - 20000;
1362
1363 return 1;
1364 }
1365
1366
1373 public function doScheduledJob()
1374 {
1375 global $conf, $langs;
1376
1377 //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
1378
1379 $error = 0;
1380 $this->output = '';
1381 $this->error = '';
1382
1383 dol_syslog(__METHOD__, LOG_DEBUG);
1384
1385 $now = dol_now();
1386
1387 $this->db->begin();
1388
1389 // ...
1390
1391 $this->db->commit();
1392
1393 return $error;
1394 }
1395
1402 public function calculateCosts()
1403 {
1404 global $conf, $hookmanager;
1405
1406 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1407 $this->unit_cost = 0;
1408 $this->total_cost = 0;
1409
1410 $parameters = array();
1411 $reshook = $hookmanager->executeHooks('calculateCostsBom', $parameters, $this); // Note that $action and $object may have been modified by hook
1412
1413 if ($reshook > 0) {
1414 return $hookmanager->resPrint;
1415 }
1416
1417 if (is_array($this->lines) && count($this->lines)) {
1418 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1419 $productFournisseur = new ProductFournisseur($this->db);
1420 $tmpproduct = new Product($this->db);
1421
1422 foreach ($this->lines as &$line) {
1423 $tmpproduct->cost_price = 0;
1424 $tmpproduct->pmp = 0;
1425 $result = $tmpproduct->fetch($line->fk_product, '', '', '', 0, 1, 1); // We discard selling price and language loading
1426
1427 if ($tmpproduct->type == $tmpproduct::TYPE_PRODUCT) {
1428 if (empty($line->fk_bom_child)) {
1429 if ($result < 0) {
1430 $this->error = $tmpproduct->error;
1431 return -1;
1432 }
1433 $unit_cost = (!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp;
1434 $line->unit_cost = (float) price2num($unit_cost);
1435 if (empty($line->unit_cost)) {
1436 if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
1437 if ($productFournisseur->fourn_remise_percent != "0") {
1438 $line->unit_cost = $productFournisseur->fourn_unitprice_with_discount;
1439 } else {
1440 $line->unit_cost = $productFournisseur->fourn_unitprice;
1441 }
1442 }
1443 }
1444
1445 $line->total_cost = (float) price2num($line->qty * $line->unit_cost, 'MT');
1446
1447 $this->total_cost += $line->total_cost;
1448 } else {
1449 $bom_child = new BOM($this->db);
1450 $res = $bom_child->fetch($line->fk_bom_child);
1451 if ($res > 0) {
1452 $bom_child->calculateCosts();
1453 $line->childBom[] = $bom_child;
1454 $this->total_cost += (float) price2num($bom_child->total_cost * $line->qty, 'MT');
1455 $this->total_cost += $line->total_cost;
1456 } else {
1457 $this->error = $bom_child->error;
1458 return -2;
1459 }
1460 }
1461 } else {
1462 // Convert qty of line into hours
1463 $unitforline = measuringUnitString($line->fk_unit, '', '', 1);
1464 $qtyhourforline = convertDurationtoHour($line->qty, $unitforline);
1465
1466 if (isModEnabled('workstation') && !empty($line->fk_default_workstation)) {
1467 $workstation = new Workstation($this->db);
1468 $res = $workstation->fetch($line->fk_default_workstation);
1469
1470 if ($res > 0) {
1471 $line->total_cost = (float) price2num($qtyhourforline * ($workstation->thm_operator_estimated + $workstation->thm_machine_estimated), 'MT');
1472 } else {
1473 $this->error = $workstation->error;
1474 return -3;
1475 }
1476 } else {
1477 $defaultdurationofservice = $tmpproduct->duration;
1478 $reg = array();
1479 $qtyhourservice = 0;
1480 if (preg_match('/^(\d+)([a-z]+)$/', $defaultdurationofservice, $reg)) {
1481 $qtyhourservice = convertDurationtoHour($reg[1], $reg[2]);
1482 }
1483
1484 if ($qtyhourservice) {
1485 $line->total_cost = (float) price2num($qtyhourforline / $qtyhourservice * $tmpproduct->cost_price, 'MT');
1486 } else {
1487 $line->total_cost = (float) price2num($line->qty * $tmpproduct->cost_price, 'MT');
1488 }
1489 }
1490
1491 $this->total_cost += $line->total_cost;
1492 }
1493 }
1494
1495 $this->total_cost = (float) price2num($this->total_cost, 'MT');
1496
1497 if ($this->qty > 0) {
1498 $this->unit_cost = (float) price2num($this->total_cost / $this->qty, 'MU');
1499 } elseif ($this->qty < 0) {
1500 $this->unit_cost = (float) price2num($this->total_cost * $this->qty, 'MU');
1501 }
1502 }
1503
1504 return 1;
1505 }
1506
1515 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
1516 {
1517 $tables = array(
1518 'bom_bomline'
1519 );
1520
1521 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
1522 }
1523
1531 public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
1532 {
1533 if (!empty($this->lines)) {
1534 foreach ($this->lines as $line) {
1535 if (!empty($line->childBom)) {
1536 foreach ($line->childBom as $childBom) {
1537 $childBom->getNetNeeds($TNetNeeds, $line->qty * $qty);
1538 }
1539 } else {
1540 if (empty($TNetNeeds[$line->fk_product]['qty'])) {
1541 $TNetNeeds[$line->fk_product]['qty'] = 0.0;
1542 }
1543 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1544 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1545 $TNetNeeds[$line->fk_product]['fk_unit'] = $line->fk_unit;
1546 $TNetNeeds[$line->fk_product]['qty'] += $line->qty * $qty;
1547 }
1548 }
1549 }
1550 }
1551
1560 public function getNetNeedsTree(&$TNetNeeds = array(), $qty = 0, $level = 0)
1561 {
1562 if (!empty($this->lines)) {
1563 foreach ($this->lines as $line) {
1564 if (!empty($line->childBom)) {
1565 foreach ($line->childBom as $childBom) {
1566 $TNetNeeds[$childBom->id]['bom'] = $childBom;
1567 $TNetNeeds[$childBom->id]['parentid'] = $this->id;
1568 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1569 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1570 //$TNetNeeds[$childBom->id]['fk_unit'] = $line->fk_unit;
1571 $TNetNeeds[$childBom->id]['qty'] = $line->qty * $qty;
1572 $TNetNeeds[$childBom->id]['level'] = $level;
1573 $childBom->getNetNeedsTree($TNetNeeds, $line->qty * $qty, $level + 1);
1574 }
1575 } else {
1576 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1577 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1578 if (!isset($TNetNeeds[$this->id]['product'])) {
1579 $TNetNeeds[$this->id]['product'] = array();
1580 }
1581 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product])) {
1582 $TNetNeeds[$this->id]['product'][$line->fk_product] = array();
1583 }
1584 $TNetNeeds[$this->id]['product'][$line->fk_product]['fk_unit'] = $line->fk_unit;
1585 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product]['qty'])) {
1586 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] = 0.0;
1587 }
1588 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
1589 $TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
1590 }
1591 }
1592 }
1593 }
1594
1603 public function getParentBomTreeRecursive(&$TParentBom, $bom_id = 0, $level = 1)
1604 {
1605
1606 // Protection against infinite loop
1607 if ($level > 1000) {
1608 return;
1609 }
1610
1611 if (empty($bom_id)) {
1612 $bom_id = $this->id;
1613 }
1614
1615 $sql = 'SELECT l.fk_bom, b.label
1616 FROM '.MAIN_DB_PREFIX.'bom_bomline l
1617 INNER JOIN '.MAIN_DB_PREFIX.$this->table_element.' b ON b.rowid = l.fk_bom
1618 WHERE fk_bom_child = '.((int) $bom_id);
1619
1620 $resql = $this->db->query($sql);
1621 if (!empty($resql)) {
1622 while ($res = $this->db->fetch_object($resql)) {
1623 $TParentBom[$res->fk_bom] = $res->fk_bom;
1624 $this->getParentBomTreeRecursive($TParentBom, $res->fk_bom, $level + 1);
1625 }
1626 }
1627 }
1628
1636 public function getKanbanView($option = '', $arraydata = null)
1637 {
1638 global $db,$langs;
1639
1640 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1641
1642 $return = '<div class="box-flex-item box-flex-grow-zero">';
1643 $return .= '<div class="info-box info-box-sm">';
1644 $return .= '<span class="info-box-icon bg-infobox-action">';
1645 $return .= img_picto('', $this->picto);
1646 $return .= '</span>';
1647 $return .= '<div class="info-box-content">';
1648 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : '').'</span>';
1649 if ($selected >= 0) {
1650 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1651 }
1652 if (property_exists($this, 'fields') && !empty($this->fields['bomtype']['arrayofkeyval'])) {
1653 $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Type").' : </span>';
1654 if ($this->bomtype == 0) {
1655 $return .= '<span class="info-box-label">'.$this->fields['bomtype']['arrayofkeyval'][0].'</span>';
1656 } else {
1657 $return .= '<span class="info-box-label">'.$this->fields['bomtype']['arrayofkeyval'][1].'</span>';
1658 }
1659 }
1660 if (!empty($arraydata['prod'])) {
1661 $prod = $arraydata['prod'];
1662 $return .= '<br><span class="info-box-label">'.$prod->getNomUrl(1).'</span>';
1663 }
1664 if (method_exists($this, 'getLibStatut')) {
1665 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1666 }
1667
1668 $return .= '</div>';
1669 $return .= '</div>';
1670 $return .= '</div>';
1671 return $return;
1672 }
1673}
1674
1675
1680{
1684 public $element = 'bomline';
1685
1689 public $table_element = 'bom_bomline';
1690
1694 public $parent_element = 'bom';
1695
1699 public $fk_parent_attribute = 'fk_bom';
1700
1704 public $picto = 'bomline';
1705
1706
1726 // BEGIN MODULEBUILDER PROPERTIES
1730 public $fields = array(
1731 'rowid' => array('type' => 'integer', 'label' => 'LineID', 'enabled' => 1, 'visible' => -1, 'position' => 1, 'notnull' => 1, 'index' => 1, 'comment' => "Id",),
1732 'fk_bom' => array('type' => 'integer:BillOfMaterials:societe/class/bom.class.php', 'label' => 'BillOfMaterials', 'enabled' => 1, 'visible' => 1, 'position' => 10, 'notnull' => 1, 'index' => 1,),
1733 'fk_product' => array('type' => 'integer:Product:product/class/product.class.php', 'label' => 'Product', 'enabled' => 1, 'visible' => 1, 'position' => 20, 'notnull' => 1, 'index' => 1,),
1734 'fk_bom_child' => array('type' => 'integer:BOM:bom/class/bom.class.php', 'label' => 'BillOfMaterials', 'enabled' => 1, 'visible' => -1, 'position' => 40, 'notnull' => -1,),
1735 'description' => array('type' => 'text', 'label' => 'Description', 'enabled' => 1, 'visible' => -1, 'position' => 60, 'notnull' => -1,),
1736 'qty' => array('type' => 'double(24,8)', 'label' => 'Quantity', 'enabled' => 1, 'visible' => 1, 'position' => 100, 'notnull' => 1, 'isameasure' => 1,),
1737 'qty_frozen' => array('type' => 'smallint', 'label' => 'QuantityFrozen', 'enabled' => 1, 'visible' => 1, 'default' => '0', 'position' => 105, 'css' => 'maxwidth50imp', 'help' => 'QuantityConsumedInvariable'),
1738 'disable_stock_change' => array('type' => 'smallint', 'label' => 'DisableStockChange', 'enabled' => 1, 'visible' => 1, 'default' => '0', 'position' => 108, 'css' => 'maxwidth50imp', 'help' => 'DisableStockChangeHelp'),
1739 'efficiency' => array('type' => 'double(24,8)', 'label' => 'ManufacturingEfficiency', 'enabled' => 1, 'visible' => 0, 'default' => '1', 'position' => 110, 'notnull' => 1, 'css' => 'maxwidth50imp', 'help' => 'ValueOfEfficiencyConsumedMeans'),
1740 'fk_unit' => array('type' => 'integer', 'label' => 'Unit', 'enabled' => 1, 'visible' => 1, 'position' => 120, 'notnull' => -1,),
1741 'position' => array('type' => 'integer', 'label' => 'Rank', 'enabled' => 1, 'visible' => 0, 'default' => '0', 'position' => 200, 'notnull' => 1,),
1742 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 1000, 'notnull' => -1,),
1743 'fk_default_workstation' => array('type' => 'integer', 'label' => 'DefaultWorkstation', 'enabled' => 1, 'visible' => 1, 'notnull' => 0, 'position' => 1050)
1744 );
1745
1749 public $rowid;
1750
1754 public $fk_bom;
1755
1759 public $fk_product;
1760
1764 public $fk_bom_child;
1765
1769 public $description;
1770
1774 public $qty;
1775
1779 public $qty_frozen;
1780
1784 public $disable_stock_change;
1785
1789 public $efficiency;
1790
1796 public $fk_unit;
1797
1801 public $fk_default_workstation;
1802
1806 public $position;
1807
1811 public $import_key;
1812 // END MODULEBUILDER PROPERTIES
1813
1817 public $total_cost = 0;
1818
1822 public $unit_cost = 0;
1823
1827 public $childBom = array();
1828
1829
1830
1836 public function __construct(DoliDB $db)
1837 {
1838 global $langs;
1839
1840 $this->db = $db;
1841
1842 $this->ismultientitymanaged = 0;
1843
1844 $this->isextrafieldmanaged = 1;
1845
1846 if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid'])) {
1847 $this->fields['rowid']['visible'] = 0;
1848 }
1849 if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
1850 $this->fields['entity']['enabled'] = 0;
1851 }
1852
1853 // Unset fields that are disabled
1854 foreach ($this->fields as $key => $val) {
1855 if (isset($val['enabled']) && empty($val['enabled'])) {
1856 unset($this->fields[$key]);
1857 }
1858 }
1859
1860 // Translate some data of arrayofkeyval
1861 foreach ($this->fields as $key => $val) {
1862 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
1863 foreach ($val['arrayofkeyval'] as $key2 => $val2) {
1864 $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
1865 }
1866 }
1867 }
1868 }
1869
1877 public function create(User $user, $notrigger = 0)
1878 {
1879 if ($this->efficiency < 0 || $this->efficiency > 1) {
1880 $this->efficiency = 1;
1881 }
1882
1883 return $this->createCommon($user, $notrigger);
1884 }
1885
1893 public function fetch($id, $ref = null)
1894 {
1895 $result = $this->fetchCommon($id, $ref);
1896 //if ($result > 0 && !empty($this->table_element_line)) $this->fetchLines();
1897 return $result;
1898 }
1899
1912 public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, $filter = '', $filtermode = 'AND')
1913 {
1914 dol_syslog(__METHOD__, LOG_DEBUG);
1915
1916 $records = array();
1917
1918 $sql = 'SELECT ';
1919 $sql .= $this->getFieldList();
1920 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1921 if ($this->ismultientitymanaged) {
1922 $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
1923 } else {
1924 $sql .= ' WHERE 1 = 1';
1925 }
1926
1927 // Manage filter
1928 $errormessage = '';
1929 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
1930 if ($errormessage) {
1931 $this->errors[] = $errormessage;
1932 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
1933 return -1;
1934 }
1935
1936 if (!empty($sortfield)) {
1937 $sql .= $this->db->order($sortfield, $sortorder);
1938 }
1939 if (!empty($limit)) {
1940 $sql .= $this->db->plimit($limit, $offset);
1941 }
1942
1943 $resql = $this->db->query($sql);
1944 if ($resql) {
1945 $num = $this->db->num_rows($resql);
1946
1947 while ($obj = $this->db->fetch_object($resql)) {
1948 $record = new self($this->db);
1949 $record->setVarsFromFetchObj($obj);
1950 $record->fetch_optionals();
1951
1952 $records[$record->id] = $record;
1953 }
1954 $this->db->free($resql);
1955
1956 return $records;
1957 } else {
1958 $this->errors[] = 'Error '.$this->db->lasterror();
1959 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
1960
1961 return -1;
1962 }
1963 }
1964
1972 public function update(User $user, $notrigger = 0)
1973 {
1974 if ($this->efficiency < 0 || $this->efficiency > 1) {
1975 $this->efficiency = 1;
1976 }
1977
1978 return $this->updateCommon($user, $notrigger);
1979 }
1980
1988 public function delete(User $user, $notrigger = 0)
1989 {
1990 return $this->deleteCommon($user, $notrigger);
1991 //return $this->deleteCommon($user, $notrigger, 1);
1992 }
1993
2004 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
2005 {
2006 global $db, $conf, $langs, $hookmanager;
2007
2008 if (!empty($conf->dol_no_mouse_hover)) {
2009 $notooltip = 1; // Force disable tooltips
2010 }
2011
2012 $result = '';
2013
2014 $label = '<u>'.$langs->trans("BillOfMaterialsLine").'</u>';
2015 $label .= '<br>';
2016 $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref;
2017
2018 $url = DOL_URL_ROOT.'/bom/bomline_card.php?id='.$this->id;
2019
2020 if ($option != 'nolink') {
2021 // Add param to save lastsearch_values or not
2022 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2023 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2024 $add_save_lastsearch_values = 1;
2025 }
2026 if ($add_save_lastsearch_values) {
2027 $url .= '&save_lastsearch_values=1';
2028 }
2029 }
2030
2031 $linkclose = '';
2032 if (empty($notooltip)) {
2033 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2034 $label = $langs->trans("ShowBillOfMaterialsLine");
2035 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
2036 }
2037 $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
2038 $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
2039 } else {
2040 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
2041 }
2042
2043 $linkstart = '<a href="'.$url.'"';
2044 $linkstart .= $linkclose.'>';
2045 $linkend = '</a>';
2046
2047 $result .= $linkstart;
2048 if ($withpicto) {
2049 $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);
2050 }
2051 if ($withpicto != 2) {
2052 $result .= $this->ref;
2053 }
2054 $result .= $linkend;
2055 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
2056
2057 global $action, $hookmanager;
2058 $hookmanager->initHooks(array('bomlinedao'));
2059 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2060 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2061 if ($reshook > 0) {
2062 $result = $hookmanager->resPrint;
2063 } else {
2064 $result .= $hookmanager->resPrint;
2065 }
2066
2067 return $result;
2068 }
2069
2076 public function getLibStatut($mode = 0)
2077 {
2078 return $this->LibStatut($this->status, $mode);
2079 }
2080
2081 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2089 public function LibStatut($status, $mode = 0)
2090 {
2091 // phpcs:enable
2092 return '';
2093 }
2094
2101 public function info($id)
2102 {
2103 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
2104 $sql .= ' fk_user_creat, fk_user_modif';
2105 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
2106 $sql .= ' WHERE t.rowid = '.((int) $id);
2107 $result = $this->db->query($sql);
2108 if ($result) {
2109 if ($this->db->num_rows($result)) {
2110 $obj = $this->db->fetch_object($result);
2111
2112 $this->id = $obj->rowid;
2113
2114 $this->user_creation_id = $obj->fk_user_creat;
2115 $this->user_modification_id = $obj->fk_user_modif;
2116 $this->date_creation = $this->db->jdate($obj->datec);
2117 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
2118 }
2119 $this->db->free($result);
2120 } else {
2121 dol_print_error($this->db);
2122 }
2123 }
2124
2131 public function initAsSpecimen()
2132 {
2133 return $this->initAsSpecimenCommon();
2134 }
2135}
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:637
$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)
Set cancel status.
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 clicable 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.
$fk_parent_attribute
fetch($id, $ref=null)
Load object in memory from the database.
getLibStatut($mode=0)
Return label of the status.
__construct(DoliDB $db)
Constructor.
update(User $user, $notrigger=0)
Update object into database.
fetchAll($sortorder='', $sortfield='', $limit=0, $offset=0, $filter='', $filtermode='AND')
Load list of objects 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)
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.
create(User $user, $notrigger=0)
Create object into database.
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.
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.
convertDurationtoHour($duration_value, $duration_unit)
Convert duration to hour.
Definition date.lib.php:334
dol_is_file($pathoffile)
Return if path is a file.
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:63
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0)
forgeSQLFromUniversalSearchCriteria
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return 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.
publicphonebutton2 phonegreen basiclayout basiclayout TotalHT VATCode TotalVAT TotalLT1 TotalLT2 TotalTTC TotalHT clearboth nowraponall TAKEPOS_SHOW_SUBPRICE right right right takeposterminal SELECT e e e e e statut
Definition invoice.php:2010