dolibarr 20.0.5
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 = 0)
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 = 0)
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
1434 $unit_cost = is_null($tmpproduct->cost_price) ? $tmpproduct->pmp : $tmpproduct->cost_price;
1435 if (is_null($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 } else {
1443 $line->unit_cost = 0;
1444 }
1445 } else {
1446 $line->unit_cost = (float) price2num($unit_cost);
1447 }
1448
1449 $line->total_cost = (float) price2num($line->qty * $line->unit_cost, 'MT');
1450
1451 $this->total_cost += $line->total_cost;
1452 } else {
1453 $bom_child = new BOM($this->db);
1454 $res = $bom_child->fetch($line->fk_bom_child);
1455 if ($res > 0) {
1456 $bom_child->calculateCosts();
1457 $line->childBom[] = $bom_child;
1458 $this->total_cost += (float) price2num($bom_child->total_cost * $line->qty, 'MT');
1459 $this->total_cost += $line->total_cost;
1460 } else {
1461 $this->error = $bom_child->error;
1462 return -2;
1463 }
1464 }
1465 } else {
1466 // Convert qty of line into hours
1467 $unitforline = measuringUnitString($line->fk_unit, '', '', 1);
1468 $qtyhourforline = convertDurationtoHour($line->qty, $unitforline);
1469
1470 if (isModEnabled('workstation') && !empty($line->fk_default_workstation)) {
1471 $workstation = new Workstation($this->db);
1472 $res = $workstation->fetch($line->fk_default_workstation);
1473
1474 if ($res > 0) {
1475 $line->total_cost = (float) price2num($qtyhourforline * ($workstation->thm_operator_estimated + $workstation->thm_machine_estimated), 'MT');
1476 } else {
1477 $this->error = $workstation->error;
1478 return -3;
1479 }
1480 } else {
1481 $defaultdurationofservice = $tmpproduct->duration;
1482 $reg = array();
1483 $qtyhourservice = 0;
1484 if (preg_match('/^(\d+)([a-z]+)$/', $defaultdurationofservice, $reg)) {
1485 $qtyhourservice = convertDurationtoHour($reg[1], $reg[2]);
1486 }
1487
1488 if ($qtyhourservice) {
1489 $line->total_cost = (float) price2num($qtyhourforline / $qtyhourservice * $tmpproduct->cost_price, 'MT');
1490 } else {
1491 $line->total_cost = (float) price2num($line->qty * $tmpproduct->cost_price, 'MT');
1492 }
1493 }
1494
1495 $this->total_cost += $line->total_cost;
1496 }
1497 }
1498
1499 $this->total_cost = (float) price2num($this->total_cost, 'MT');
1500
1501 if ($this->qty > 0) {
1502 $this->unit_cost = (float) price2num($this->total_cost / $this->qty, 'MU');
1503 } elseif ($this->qty < 0) {
1504 $this->unit_cost = (float) price2num($this->total_cost * $this->qty, 'MU');
1505 }
1506 }
1507
1508 return 1;
1509 }
1510
1519 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
1520 {
1521 $tables = array(
1522 'bom_bomline'
1523 );
1524
1525 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
1526 }
1527
1535 public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
1536 {
1537 if (!empty($this->lines)) {
1538 foreach ($this->lines as $line) {
1539 if (!empty($line->childBom)) {
1540 foreach ($line->childBom as $childBom) {
1541 $childBom->getNetNeeds($TNetNeeds, $line->qty * $qty);
1542 }
1543 } else {
1544 if (empty($TNetNeeds[$line->fk_product]['qty'])) {
1545 $TNetNeeds[$line->fk_product]['qty'] = 0.0;
1546 }
1547 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1548 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1549 $TNetNeeds[$line->fk_product]['fk_unit'] = $line->fk_unit;
1550 $TNetNeeds[$line->fk_product]['qty'] += $line->qty * $qty;
1551 }
1552 }
1553 }
1554 }
1555
1564 public function getNetNeedsTree(&$TNetNeeds = array(), $qty = 0, $level = 0)
1565 {
1566 if (!empty($this->lines)) {
1567 foreach ($this->lines as $line) {
1568 if (!empty($line->childBom)) {
1569 foreach ($line->childBom as $childBom) {
1570 $TNetNeeds[$childBom->id]['bom'] = $childBom;
1571 $TNetNeeds[$childBom->id]['parentid'] = $this->id;
1572 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1573 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1574 //$TNetNeeds[$childBom->id]['fk_unit'] = $line->fk_unit;
1575 $TNetNeeds[$childBom->id]['qty'] = $line->qty * $qty;
1576 $TNetNeeds[$childBom->id]['level'] = $level;
1577 $childBom->getNetNeedsTree($TNetNeeds, $line->qty * $qty, $level + 1);
1578 }
1579 } else {
1580 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1581 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1582 if (!isset($TNetNeeds[$this->id]['product'])) {
1583 $TNetNeeds[$this->id]['product'] = array();
1584 }
1585 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product])) {
1586 $TNetNeeds[$this->id]['product'][$line->fk_product] = array();
1587 }
1588 $TNetNeeds[$this->id]['product'][$line->fk_product]['fk_unit'] = $line->fk_unit;
1589 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product]['qty'])) {
1590 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] = 0.0;
1591 }
1592 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
1593 $TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
1594 }
1595 }
1596 }
1597 }
1598
1607 public function getParentBomTreeRecursive(&$TParentBom, $bom_id = 0, $level = 1)
1608 {
1609
1610 // Protection against infinite loop
1611 if ($level > 1000) {
1612 return;
1613 }
1614
1615 if (empty($bom_id)) {
1616 $bom_id = $this->id;
1617 }
1618
1619 $sql = 'SELECT l.fk_bom, b.label
1620 FROM '.MAIN_DB_PREFIX.'bom_bomline l
1621 INNER JOIN '.MAIN_DB_PREFIX.$this->table_element.' b ON b.rowid = l.fk_bom
1622 WHERE fk_bom_child = '.((int) $bom_id);
1623
1624 $resql = $this->db->query($sql);
1625 if (!empty($resql)) {
1626 while ($res = $this->db->fetch_object($resql)) {
1627 $TParentBom[$res->fk_bom] = $res->fk_bom;
1628 $this->getParentBomTreeRecursive($TParentBom, $res->fk_bom, $level + 1);
1629 }
1630 }
1631 }
1632
1640 public function getKanbanView($option = '', $arraydata = null)
1641 {
1642 global $db,$langs;
1643
1644 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1645
1646 $return = '<div class="box-flex-item box-flex-grow-zero">';
1647 $return .= '<div class="info-box info-box-sm">';
1648 $return .= '<span class="info-box-icon bg-infobox-action">';
1649 $return .= img_picto('', $this->picto);
1650 $return .= '</span>';
1651 $return .= '<div class="info-box-content">';
1652 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : '').'</span>';
1653 if ($selected >= 0) {
1654 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1655 }
1656 if (property_exists($this, 'fields') && !empty($this->fields['bomtype']['arrayofkeyval'])) {
1657 $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Type").' : </span>';
1658 if ($this->bomtype == 0) {
1659 $return .= '<span class="info-box-label">'.$this->fields['bomtype']['arrayofkeyval'][0].'</span>';
1660 } else {
1661 $return .= '<span class="info-box-label">'.$this->fields['bomtype']['arrayofkeyval'][1].'</span>';
1662 }
1663 }
1664 if (!empty($arraydata['prod'])) {
1665 $prod = $arraydata['prod'];
1666 $return .= '<br><span class="info-box-label">'.$prod->getNomUrl(1).'</span>';
1667 }
1668 if (method_exists($this, 'getLibStatut')) {
1669 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1670 }
1671
1672 $return .= '</div>';
1673 $return .= '</div>';
1674 $return .= '</div>';
1675 return $return;
1676 }
1677}
1678
1679
1684{
1688 public $element = 'bomline';
1689
1693 public $table_element = 'bom_bomline';
1694
1698 public $parent_element = 'bom';
1699
1703 public $fk_parent_attribute = 'fk_bom';
1704
1708 public $picto = 'bomline';
1709
1710
1730 // BEGIN MODULEBUILDER PROPERTIES
1734 public $fields = array(
1735 'rowid' => array('type' => 'integer', 'label' => 'LineID', 'enabled' => 1, 'visible' => -1, 'position' => 1, 'notnull' => 1, 'index' => 1, 'comment' => "Id",),
1736 'fk_bom' => array('type' => 'integer:BillOfMaterials:societe/class/bom.class.php', 'label' => 'BillOfMaterials', 'enabled' => 1, 'visible' => 1, 'position' => 10, 'notnull' => 1, 'index' => 1,),
1737 'fk_product' => array('type' => 'integer:Product:product/class/product.class.php', 'label' => 'Product', 'enabled' => 1, 'visible' => 1, 'position' => 20, 'notnull' => 1, 'index' => 1,),
1738 'fk_bom_child' => array('type' => 'integer:BOM:bom/class/bom.class.php', 'label' => 'BillOfMaterials', 'enabled' => 1, 'visible' => -1, 'position' => 40, 'notnull' => -1,),
1739 'description' => array('type' => 'text', 'label' => 'Description', 'enabled' => 1, 'visible' => -1, 'position' => 60, 'notnull' => -1,),
1740 'qty' => array('type' => 'double(24,8)', 'label' => 'Quantity', 'enabled' => 1, 'visible' => 1, 'position' => 100, 'notnull' => 1, 'isameasure' => 1,),
1741 'qty_frozen' => array('type' => 'smallint', 'label' => 'QuantityFrozen', 'enabled' => 1, 'visible' => 1, 'default' => '0', 'position' => 105, 'css' => 'maxwidth50imp', 'help' => 'QuantityConsumedInvariable'),
1742 'disable_stock_change' => array('type' => 'smallint', 'label' => 'DisableStockChange', 'enabled' => 1, 'visible' => 1, 'default' => '0', 'position' => 108, 'css' => 'maxwidth50imp', 'help' => 'DisableStockChangeHelp'),
1743 'efficiency' => array('type' => 'double(24,8)', 'label' => 'ManufacturingEfficiency', 'enabled' => 1, 'visible' => 0, 'default' => '1', 'position' => 110, 'notnull' => 1, 'css' => 'maxwidth50imp', 'help' => 'ValueOfEfficiencyConsumedMeans'),
1744 'fk_unit' => array('type' => 'integer', 'label' => 'Unit', 'enabled' => 1, 'visible' => 1, 'position' => 120, 'notnull' => -1,),
1745 'position' => array('type' => 'integer', 'label' => 'Rank', 'enabled' => 1, 'visible' => 0, 'default' => '0', 'position' => 200, 'notnull' => 1,),
1746 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 1000, 'notnull' => -1,),
1747 'fk_default_workstation' => array('type' => 'integer', 'label' => 'DefaultWorkstation', 'enabled' => 1, 'visible' => 1, 'notnull' => 0, 'position' => 1050)
1748 );
1749
1753 public $rowid;
1754
1758 public $fk_bom;
1759
1763 public $fk_product;
1764
1768 public $fk_bom_child;
1769
1773 public $description;
1774
1778 public $qty;
1779
1783 public $qty_frozen;
1784
1788 public $disable_stock_change;
1789
1793 public $efficiency;
1794
1800 public $fk_unit;
1801
1805 public $fk_default_workstation;
1806
1810 public $position;
1811
1815 public $import_key;
1816 // END MODULEBUILDER PROPERTIES
1817
1821 public $total_cost = 0;
1822
1826 public $unit_cost = 0;
1827
1831 public $childBom = array();
1832
1833
1834
1840 public function __construct(DoliDB $db)
1841 {
1842 global $langs;
1843
1844 $this->db = $db;
1845
1846 $this->ismultientitymanaged = 0;
1847
1848 $this->isextrafieldmanaged = 1;
1849
1850 if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid'])) {
1851 $this->fields['rowid']['visible'] = 0;
1852 }
1853 if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
1854 $this->fields['entity']['enabled'] = 0;
1855 }
1856
1857 // Unset fields that are disabled
1858 foreach ($this->fields as $key => $val) {
1859 if (isset($val['enabled']) && empty($val['enabled'])) {
1860 unset($this->fields[$key]);
1861 }
1862 }
1863
1864 // Translate some data of arrayofkeyval
1865 foreach ($this->fields as $key => $val) {
1866 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
1867 foreach ($val['arrayofkeyval'] as $key2 => $val2) {
1868 $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
1869 }
1870 }
1871 }
1872 }
1873
1881 public function create(User $user, $notrigger = 0)
1882 {
1883 if ($this->efficiency < 0 || $this->efficiency > 1) {
1884 $this->efficiency = 1;
1885 }
1886
1887 return $this->createCommon($user, $notrigger);
1888 }
1889
1897 public function fetch($id, $ref = null)
1898 {
1899 $result = $this->fetchCommon($id, $ref);
1900 //if ($result > 0 && !empty($this->table_element_line)) $this->fetchLines();
1901 return $result;
1902 }
1903
1916 public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, $filter = '', $filtermode = 'AND')
1917 {
1918 dol_syslog(__METHOD__, LOG_DEBUG);
1919
1920 $records = array();
1921
1922 $sql = 'SELECT ';
1923 $sql .= $this->getFieldList();
1924 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1925 if ($this->ismultientitymanaged) {
1926 $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
1927 } else {
1928 $sql .= ' WHERE 1 = 1';
1929 }
1930
1931 // Manage filter
1932 $errormessage = '';
1933 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
1934 if ($errormessage) {
1935 $this->errors[] = $errormessage;
1936 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
1937 return -1;
1938 }
1939
1940 if (!empty($sortfield)) {
1941 $sql .= $this->db->order($sortfield, $sortorder);
1942 }
1943 if (!empty($limit)) {
1944 $sql .= $this->db->plimit($limit, $offset);
1945 }
1946
1947 $resql = $this->db->query($sql);
1948 if ($resql) {
1949 $num = $this->db->num_rows($resql);
1950
1951 while ($obj = $this->db->fetch_object($resql)) {
1952 $record = new self($this->db);
1953 $record->setVarsFromFetchObj($obj);
1954 $record->fetch_optionals();
1955
1956 $records[$record->id] = $record;
1957 }
1958 $this->db->free($resql);
1959
1960 return $records;
1961 } else {
1962 $this->errors[] = 'Error '.$this->db->lasterror();
1963 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
1964
1965 return -1;
1966 }
1967 }
1968
1976 public function update(User $user, $notrigger = 0)
1977 {
1978 if ($this->efficiency < 0 || $this->efficiency > 1) {
1979 $this->efficiency = 1;
1980 }
1981
1982 return $this->updateCommon($user, $notrigger);
1983 }
1984
1992 public function delete(User $user, $notrigger = 0)
1993 {
1994 return $this->deleteCommon($user, $notrigger);
1995 //return $this->deleteCommon($user, $notrigger, 1);
1996 }
1997
2008 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
2009 {
2010 global $db, $conf, $langs, $hookmanager;
2011
2012 if (!empty($conf->dol_no_mouse_hover)) {
2013 $notooltip = 1; // Force disable tooltips
2014 }
2015
2016 $result = '';
2017
2018 $label = '<u>'.$langs->trans("BillOfMaterialsLine").'</u>';
2019 $label .= '<br>';
2020 $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref;
2021
2022 $url = DOL_URL_ROOT.'/bom/bomline_card.php?id='.$this->id;
2023
2024 if ($option != 'nolink') {
2025 // Add param to save lastsearch_values or not
2026 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2027 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2028 $add_save_lastsearch_values = 1;
2029 }
2030 if ($add_save_lastsearch_values) {
2031 $url .= '&save_lastsearch_values=1';
2032 }
2033 }
2034
2035 $linkclose = '';
2036 if (empty($notooltip)) {
2037 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2038 $label = $langs->trans("ShowBillOfMaterialsLine");
2039 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
2040 }
2041 $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
2042 $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
2043 } else {
2044 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
2045 }
2046
2047 $linkstart = '<a href="'.$url.'"';
2048 $linkstart .= $linkclose.'>';
2049 $linkend = '</a>';
2050
2051 $result .= $linkstart;
2052 if ($withpicto) {
2053 $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);
2054 }
2055 if ($withpicto != 2) {
2056 $result .= $this->ref;
2057 }
2058 $result .= $linkend;
2059 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
2060
2061 global $action, $hookmanager;
2062 $hookmanager->initHooks(array('bomlinedao'));
2063 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2064 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2065 if ($reshook > 0) {
2066 $result = $hookmanager->resPrint;
2067 } else {
2068 $result .= $hookmanager->resPrint;
2069 }
2070
2071 return $result;
2072 }
2073
2080 public function getLibStatut($mode = 0)
2081 {
2082 return $this->LibStatut($this->status, $mode);
2083 }
2084
2085 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2093 public function LibStatut($status, $mode = 0)
2094 {
2095 // phpcs:enable
2096 return '';
2097 }
2098
2105 public function info($id)
2106 {
2107 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
2108 $sql .= ' fk_user_creat, fk_user_modif';
2109 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
2110 $sql .= ' WHERE t.rowid = '.((int) $id);
2111 $result = $this->db->query($sql);
2112 if ($result) {
2113 if ($this->db->num_rows($result)) {
2114 $obj = $this->db->fetch_object($result);
2115
2116 $this->id = $obj->rowid;
2117
2118 $this->user_creation_id = $obj->fk_user_creat;
2119 $this->user_modification_id = $obj->fk_user_modif;
2120 $this->date_creation = $this->db->jdate($obj->datec);
2121 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
2122 }
2123 $this->db->free($result);
2124 } else {
2125 dol_print_error($this->db);
2126 }
2127 }
2128
2135 public function initAsSpecimen()
2136 {
2137 return $this->initAsSpecimenCommon();
2138 }
2139}
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.
create(User $user, $notrigger=0)
Create object into database.
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.
update(User $user, $notrigger=0)
Update 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)
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:2015