dolibarr 21.0.0-alpha
bom.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2019 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2023 Benjamin Falière <benjamin.faliere@altairis.fr>
4 * Copyright (C) 2023 Charlene Benke <charlene@patas-monkey.com>
5 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
6 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
28// Put here all includes required by your class file
29require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
30require_once DOL_DOCUMENT_ROOT.'/core/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_valid;
161
165 public $fk_user_creat;
166
170 public $fk_user_modif;
171
175 public $fk_user_valid;
176
180 public $fk_warehouse;
181
185 public $import_key;
186
190 public $status;
191
195 public $fk_product;
196 public $qty;
197 public $duration;
198 public $efficiency;
199 // END MODULEBUILDER PROPERTIES
200
201
202 // If this object has a subtable with lines
203
207 public $table_element_line = 'bom_bomline';
208
212 public $fk_element = 'fk_bom';
213
217 public $class_element_line = 'BOMLine';
218
219 // /**
220 // * @var array List of child tables. To test if we can delete object.
221 // */
222 // protected $childtables=array();
223
227 protected $childtablesoncascade = array('bom_bomline');
228
232 public $lines = array();
233
237 public $total_cost = 0;
238
242 public $unit_cost = 0;
243
244
250 public function __construct(DoliDB $db)
251 {
252 global $conf, $langs;
253
254 $this->db = $db;
255
256 $this->ismultientitymanaged = 1;
257 $this->isextrafieldmanaged = 1;
258
259 if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid'])) {
260 $this->fields['rowid']['visible'] = 0;
261 }
262 if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
263 $this->fields['entity']['enabled'] = 0;
264 }
265
266 // Unset fields that are disabled
267 foreach ($this->fields as $key => $val) {
268 if (isset($val['enabled']) && empty($val['enabled'])) {
269 unset($this->fields[$key]);
270 }
271 }
272
273 // Translate some data of arrayofkeyval
274 foreach ($this->fields as $key => $val) {
275 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
276 foreach ($val['arrayofkeyval'] as $key2 => $val2) {
277 $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
278 }
279 }
280 }
281 }
282
290 public function create(User $user, $notrigger = 1)
291 {
292 if ($this->efficiency <= 0 || $this->efficiency > 1) {
293 $this->efficiency = 1;
294 }
295
296 return $this->createCommon($user, $notrigger);
297 }
298
306 public function createFromClone(User $user, $fromid)
307 {
308 global $langs, $hookmanager, $extrafields;
309 $error = 0;
310
311 dol_syslog(__METHOD__, LOG_DEBUG);
312
313 $object = new self($this->db);
314
315 $this->db->begin();
316
317 // Load source object
318 $result = $object->fetchCommon($fromid);
319 if ($result > 0 && !empty($object->table_element_line)) {
320 $object->fetchLines();
321 }
322
323 // Get lines so they will be clone
324 //foreach ($object->lines as $line)
325 // $line->fetch_optionals();
326
327 // Reset some properties
328 unset($object->id);
329 unset($object->fk_user_creat);
330 unset($object->import_key);
331
332 // Clear fields
333 $object->ref = empty($this->fields['ref']['default']) ? $langs->trans("copy_of_").$object->ref : $this->fields['ref']['default'];
334 $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf")." ".$object->label : $this->fields['label']['default'];
335 $object->status = self::STATUS_DRAFT;
336 // ...
337 // Clear extrafields that are unique
338 if (is_array($object->array_options) && count($object->array_options) > 0) {
339 $extrafields->fetch_name_optionals_label($object->table_element);
340 foreach ($object->array_options as $key => $option) {
341 $shortkey = preg_replace('/options_/', '', $key);
342 if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) {
343 //var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
344 unset($object->array_options[$key]);
345 }
346 }
347 }
348
349 // Create clone
350 $object->context['createfromclone'] = 'createfromclone';
351 $result = $object->createCommon($user);
352 if ($result < 0) {
353 $error++;
354 $this->error = $object->error;
355 $this->errors = $object->errors;
356 }
357
358 if (!$error) {
359 // copy internal contacts
360 if ($this->copy_linked_contact($object, 'internal') < 0) {
361 $error++;
362 }
363 }
364
365 if (!$error) {
366 // copy external contacts if same company
367 if (property_exists($this, 'socid') && $this->socid == $object->socid) {
368 if ($this->copy_linked_contact($object, 'external') < 0) {
369 $error++;
370 }
371 }
372 }
373
374 // If there is lines, create lines too
375
376
377
378 unset($object->context['createfromclone']);
379
380 // End
381 if (!$error) {
382 $this->db->commit();
383 return $object;
384 } else {
385 $this->db->rollback();
386 return -1;
387 }
388 }
389
397 public function fetch($id, $ref = null)
398 {
399 $result = $this->fetchCommon($id, $ref);
400
401 if ($result > 0 && !empty($this->table_element_line)) {
402 $this->fetchLines();
403 }
404 //$this->calculateCosts(); // This consume a high number of subrequests. Do not call it into fetch but when you need it.
405
406 return $result;
407 }
408
414 public function fetchLines()
415 {
416 $this->lines = array();
417
418 $result = $this->fetchLinesCommon();
419 return $result;
420 }
421
429 public function fetchLinesbytypeproduct($typeproduct = 0)
430 {
431 $this->lines = array();
432
433 $objectlineclassname = get_class($this).'Line';
434 if (!class_exists($objectlineclassname)) {
435 $this->error = 'Error, class '.$objectlineclassname.' not found during call of fetchLinesCommon';
436 return -1;
437 }
438
439 $objectline = new $objectlineclassname($this->db);
440
441 $sql = "SELECT ".$objectline->getFieldList('l');
442 $sql .= " FROM ".$this->db->prefix().$objectline->table_element." as l";
443 $sql .= " LEFT JOIN ".$this->db->prefix()."product as p ON p.rowid = l.fk_product";
444 $sql .= " WHERE l.fk_".$this->db->escape($this->element)." = ".((int) $this->id);
445 $sql .= " AND p.fk_product_type = ". ((int) $typeproduct);
446 if (isset($objectline->fields['position'])) {
447 $sql .= $this->db->order('position', 'ASC');
448 }
449
450 $resql = $this->db->query($sql);
451 if ($resql) {
452 $num_rows = $this->db->num_rows($resql);
453 $i = 0;
454 while ($i < $num_rows) {
455 $obj = $this->db->fetch_object($resql);
456 if ($obj) {
457 $newline = new $objectlineclassname($this->db);
458 $newline->setVarsFromFetchObj($obj);
459
460 $this->lines[] = $newline;
461 }
462 $i++;
463 }
464
465 return $num_rows;
466 } else {
467 $this->error = $this->db->lasterror();
468 $this->errors[] = $this->error;
469 return -1;
470 }
471 }
472
473
485 public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, $filter = '', $filtermode = 'AND')
486 {
487 dol_syslog(__METHOD__, LOG_DEBUG);
488
489 $records = array();
490
491 $sql = 'SELECT ';
492 $sql .= $this->getFieldList();
493 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
494 if ($this->ismultientitymanaged) {
495 $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
496 } else {
497 $sql .= ' WHERE 1 = 1';
498 }
499
500 // Manage filter
501 $errormessage = '';
502 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
503 if ($errormessage) {
504 $this->errors[] = $errormessage;
505 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
506 return -1;
507 }
508
509 if (!empty($sortfield)) {
510 $sql .= $this->db->order($sortfield, $sortorder);
511 }
512 if (!empty($limit)) {
513 $sql .= $this->db->plimit($limit, $offset);
514 }
515
516 $resql = $this->db->query($sql);
517 if ($resql) {
518 $num = $this->db->num_rows($resql);
519
520 while ($obj = $this->db->fetch_object($resql)) {
521 $record = new self($this->db);
522 $record->setVarsFromFetchObj($obj);
523
524 $records[$record->id] = $record;
525 }
526 $this->db->free($resql);
527
528 return $records;
529 } else {
530 $this->errors[] = 'Error '.$this->db->lasterror();
531 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
532
533 return -1;
534 }
535 }
536
544 public function update(User $user, $notrigger = 1)
545 {
546 if ($this->efficiency <= 0 || $this->efficiency > 1) {
547 $this->efficiency = 1;
548 }
549
550 return $this->updateCommon($user, $notrigger);
551 }
552
560 public function delete(User $user, $notrigger = 1)
561 {
562 return $this->deleteCommon($user, $notrigger);
563 //return $this->deleteCommon($user, $notrigger, 1);
564 }
565
582 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)
583 {
584 global $mysoc, $conf, $langs, $user;
585
586 $logtext = "::addLine bomid=$this->id, qty=$qty, fk_product=$fk_product, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
587 $logtext .= ", fk_bom_child=$fk_bom_child, import_key=$import_key";
588 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
589
590 if ($this->statut == self::STATUS_DRAFT) {
591 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
592
593 // Clean parameters
594 if (empty($qty)) {
595 $qty = 0;
596 }
597 if (empty($qty_frozen)) {
598 $qty_frozen = 0;
599 }
600 if (empty($disable_stock_change)) {
601 $disable_stock_change = 0;
602 }
603 if (empty($efficiency)) {
604 $efficiency = 1.0;
605 }
606 if (empty($fk_bom_child)) {
607 $fk_bom_child = null;
608 }
609 if (empty($import_key)) {
610 $import_key = null;
611 }
612 if (empty($position)) {
613 $position = -1;
614 }
615
616 $qty = (float) price2num($qty);
617 $efficiency = (float) price2num($efficiency);
618 $position = (float) price2num($position);
619
620 $this->db->begin();
621
622 // Rank to use
623 $rangMax = $this->line_max();
624 $rankToUse = $position;
625 if ($rankToUse <= 0 or $rankToUse > $rangMax) { // New line after existing lines
626 $rankToUse = $rangMax + 1;
627 } else { // New line between the existing lines
628 foreach ($this->lines as $bl) {
629 if ($bl->position >= $rankToUse) {
630 $bl->position++;
631 $bl->update($user);
632 }
633 }
634 }
635
636 // Insert line
637 $line = new BOMLine($this->db);
638
639 $line->context = $this->context;
640
641 $line->fk_bom = $this->id;
642 $line->fk_product = $fk_product;
643 $line->qty = $qty;
644 $line->qty_frozen = $qty_frozen;
645 $line->disable_stock_change = $disable_stock_change;
646 $line->efficiency = $efficiency;
647 $line->fk_bom_child = $fk_bom_child;
648 $line->import_key = $import_key;
649 $line->position = $rankToUse;
650 $line->fk_unit = $fk_unit;
651 $line->fk_default_workstation = $fk_default_workstation;
652
653 if (is_array($array_options) && count($array_options) > 0) {
654 $line->array_options = $array_options;
655 }
656
657 $result = $line->create($user);
658
659 if ($result > 0) {
660 $this->calculateCosts();
661 $this->db->commit();
662 return $result;
663 } else {
664 $this->setErrorsFromObject($line);
665 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
666 $this->db->rollback();
667 return -2;
668 }
669 } else {
670 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
671 return -3;
672 }
673 }
674
690 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)
691 {
692 global $user;
693
694 $logtext = "::updateLine bomid=$this->id, qty=$qty, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
695 $logtext .= ", import_key=$import_key";
696 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
697
698 if ($this->statut == self::STATUS_DRAFT) {
699 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
700
701 // Clean parameters
702 if (empty($qty)) {
703 $qty = 0;
704 }
705 if (empty($qty_frozen)) {
706 $qty_frozen = 0;
707 }
708 if (empty($disable_stock_change)) {
709 $disable_stock_change = 0;
710 }
711 if (empty($efficiency)) {
712 $efficiency = 1.0;
713 }
714 if (empty($import_key)) {
715 $import_key = null;
716 }
717 if (empty($position)) {
718 $position = -1;
719 }
720
721 $qty = (float) price2num($qty);
722 $efficiency = (float) price2num($efficiency);
723 $position = (float) price2num($position);
724
725 $this->db->begin();
726
727 //Fetch current line from the database and then clone the object and set it in $oldline property
728 $line = new BOMLine($this->db);
729 $line->fetch($rowid);
730 $line->fetch_optionals();
731
732 $staticLine = clone $line;
733 $line->oldcopy = $staticLine;
734 $line->context = $this->context;
735
736 // Rank to use
737 $rankToUse = (int) $position;
738 if ($rankToUse != $line->oldcopy->position) { // check if position have a new value
739 foreach ($this->lines as $bl) {
740 if ($bl->position >= $rankToUse and $bl->position < ($line->oldcopy->position + 1)) { // move rank up
741 $bl->position++;
742 $bl->update($user);
743 }
744 if ($bl->position <= $rankToUse and $bl->position > ($line->oldcopy->position)) { // move rank down
745 $bl->position--;
746 $bl->update($user);
747 }
748 }
749 }
750
751
752 $line->fk_bom = $this->id;
753 $line->qty = $qty;
754 $line->qty_frozen = $qty_frozen;
755 $line->disable_stock_change = $disable_stock_change;
756 $line->efficiency = $efficiency;
757 $line->import_key = $import_key;
758 $line->position = $rankToUse;
759
760
761 if (!empty($fk_unit)) {
762 $line->fk_unit = $fk_unit;
763 }
764
765
766 if (is_array($array_options) && count($array_options) > 0) {
767 // We replace values in this->line->array_options only for entries defined into $array_options
768 foreach ($array_options as $key => $value) {
769 $line->array_options[$key] = $array_options[$key];
770 }
771 }
772 if ($line->fk_default_workstation != $fk_default_workstation) {
773 $line->fk_default_workstation = ($fk_default_workstation > 0 ? $fk_default_workstation : 0);
774 }
775
776 $result = $line->update($user);
777
778 if ($result > 0) {
779 $this->calculateCosts();
780 $this->db->commit();
781 return $result;
782 } else {
783 $this->setErrorsFromObject($line);
784 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
785 $this->db->rollback();
786 return -2;
787 }
788 } else {
789 dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
790 return -3;
791 }
792 }
793
802 public function deleteLine(User $user, $idline, $notrigger = 0)
803 {
804 if ($this->status < 0) {
805 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
806 return -2;
807 }
808
809 $this->db->begin();
810
811 //Fetch current line from the database and then clone the object and set it in $oldline property
812 $line = new BOMLine($this->db);
813 $line->fetch($idline);
814 $line->fetch_optionals();
815
816 $staticLine = clone $line;
817 $line->oldcopy = $staticLine;
818 $line->context = $this->context;
819
820 $result = $line->delete($user, $notrigger);
821
822 //Positions (rank) reordering
823 foreach ($this->lines as $bl) {
824 if ($bl->position > ($line->oldcopy->position)) { // move rank down
825 $bl->position--;
826 $bl->update($user);
827 }
828 }
829
830 if ($result > 0) {
831 $this->calculateCosts();
832 $this->db->commit();
833 return $result;
834 } else {
835 $this->setErrorsFromObject($line);
836 dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
837 $this->db->rollback();
838 return -2;
839 }
840 }
841
849 public function getNextNumRef($prod)
850 {
851 global $langs, $conf;
852 $langs->load("mrp");
853
854 if (getDolGlobalString('BOM_ADDON')) {
855 $mybool = false;
856
857 $file = getDolGlobalString('BOM_ADDON') . ".php";
858 $classname = getDolGlobalString('BOM_ADDON');
859
860 // Include file with class
861 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
862 foreach ($dirmodels as $reldir) {
863 $dir = dol_buildpath($reldir."core/modules/bom/");
864
865 // Load file with numbering class (if found)
866 $mybool = ((bool) @include_once $dir.$file) || $mybool;
867 }
868
869 if ($mybool === false) {
870 dol_print_error(null, "Failed to include file ".$file);
871 return '';
872 }
873
874 $obj = new $classname();
875 $numref = $obj->getNextValue($prod, $this);
876
877 if ($numref != "") {
878 return $numref;
879 } else {
880 $this->error = $obj->error;
881 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
882 return "";
883 }
884 } else {
885 print $langs->trans("Error")." ".$langs->trans("Error_BOM_ADDON_NotDefined");
886 return "";
887 }
888 }
889
897 public function validate($user, $notrigger = 0)
898 {
899 global $conf;
900
901 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
902
903 $error = 0;
904
905 // Protection
906 if ($this->status == self::STATUS_VALIDATED) {
907 dol_syslog(get_class($this)."::validate action abandoned: already validated", LOG_WARNING);
908 return 0;
909 }
910
911 $now = dol_now();
912
913 $this->db->begin();
914
915 // Define new ref
916 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
917 $this->fetch_product();
918 $num = $this->getNextNumRef($this->product);
919 } else {
920 $num = $this->ref;
921 }
922 $this->newref = dol_sanitizeFileName($num);
923
924 // Validate
925 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
926 $sql .= " SET ref = '".$this->db->escape($num)."',";
927 $sql .= " status = ".self::STATUS_VALIDATED.",";
928 $sql .= " date_valid='".$this->db->idate($now)."',";
929 $sql .= " fk_user_valid = ".((int) $user->id);
930 $sql .= " WHERE rowid = ".((int) $this->id);
931
932 dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
933 $resql = $this->db->query($sql);
934 if (!$resql) {
935 dol_print_error($this->db);
936 $this->error = $this->db->lasterror();
937 $error++;
938 }
939
940 if (!$error && !$notrigger) {
941 // Call trigger
942 $result = $this->call_trigger('BOM_VALIDATE', $user);
943 if ($result < 0) {
944 $error++;
945 }
946 // End call triggers
947 }
948
949 if (!$error) {
950 $this->oldref = $this->ref;
951
952 // Rename directory if dir was a temporary ref
953 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
954 // Now we rename also files into index
955 $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)."'";
956 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
957 $resql = $this->db->query($sql);
958 if (!$resql) {
959 $error++;
960 $this->error = $this->db->lasterror();
961 }
962 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'bom/".$this->db->escape($this->newref)."'";
963 $sql .= " WHERE filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
964 $resql = $this->db->query($sql);
965 if (!$resql) {
966 $error++;
967 $this->error = $this->db->lasterror();
968 }
969
970 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
971 $oldref = dol_sanitizeFileName($this->ref);
972 $newref = dol_sanitizeFileName($num);
973 $dirsource = $conf->bom->dir_output.'/'.$oldref;
974 $dirdest = $conf->bom->dir_output.'/'.$newref;
975 if (!$error && file_exists($dirsource)) {
976 dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
977
978 if (@rename($dirsource, $dirdest)) {
979 dol_syslog("Rename ok");
980 // Rename docs starting with $oldref with $newref
981 $listoffiles = dol_dir_list($conf->bom->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
982 foreach ($listoffiles as $fileentry) {
983 $dirsource = $fileentry['name'];
984 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
985 $dirsource = $fileentry['path'].'/'.$dirsource;
986 $dirdest = $fileentry['path'].'/'.$dirdest;
987 @rename($dirsource, $dirdest);
988 }
989 }
990 }
991 }
992 }
993
994 // Set new ref and current status
995 if (!$error) {
996 $this->ref = $num;
997 $this->status = self::STATUS_VALIDATED;
998 }
999
1000 if (!$error) {
1001 $this->db->commit();
1002 return 1;
1003 } else {
1004 $this->db->rollback();
1005 return -1;
1006 }
1007 }
1008
1016 public function setDraft($user, $notrigger = 0)
1017 {
1018 // Protection
1019 if ($this->status <= self::STATUS_DRAFT) {
1020 return 0;
1021 }
1022
1023 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'BOM_UNVALIDATE');
1024 }
1025
1033 public function cancel($user, $notrigger = 0)
1034 {
1035 // Protection
1036 if ($this->status != self::STATUS_VALIDATED) {
1037 return 0;
1038 }
1039
1040 return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'BOM_CLOSE');
1041 }
1042
1050 public function reopen($user, $notrigger = 0)
1051 {
1052 // Protection
1053 if ($this->status != self::STATUS_CANCELED) {
1054 return 0;
1055 }
1056
1057 return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'BOM_REOPEN');
1058 }
1059
1066 public function getTooltipContentArray($params)
1067 {
1068 global $conf, $langs, $user;
1069
1070 $langs->loadLangs(['product', 'mrp']);
1071
1072 $datas = [];
1073
1074 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1075 return ['optimize' => $langs->trans("ShowBillOfMaterials")];
1076 }
1077 $picto = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("BillOfMaterials").'</u>';
1078 if (isset($this->status)) {
1079 $picto .= ' '.$this->getLibStatut(5);
1080 }
1081 $datas['picto'] = $picto;
1082 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1083 if (isset($this->label)) {
1084 $datas['label'] = '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
1085 }
1086 if (!empty($this->fk_product) && $this->fk_product > 0) {
1087 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1088 $product = new Product($this->db);
1089 $resultFetch = $product->fetch($this->fk_product);
1090 if ($resultFetch > 0) {
1091 $datas['product'] = "<br><b>".$langs->trans("Product").'</b>: '.$product->ref.' - '.$product->label;
1092 }
1093 }
1094
1095 return $datas;
1096 }
1097
1108 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1109 {
1110 global $db, $conf, $langs, $hookmanager;
1111
1112 if (!empty($conf->dol_no_mouse_hover)) {
1113 $notooltip = 1; // Force disable tooltips
1114 }
1115
1116 $result = '';
1117 $params = [
1118 'id' => $this->id,
1119 'objecttype' => $this->element,
1120 'option' => $option,
1121 ];
1122 $classfortooltip = 'classfortooltip';
1123 $dataparams = '';
1124 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1125 $classfortooltip = 'classforajaxtooltip';
1126 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1127 $label = '';
1128 } else {
1129 $label = implode($this->getTooltipContentArray($params));
1130 }
1131
1132 $url = DOL_URL_ROOT.'/bom/bom_card.php?id='.$this->id;
1133
1134 if ($option != 'nolink') {
1135 // Add param to save lastsearch_values or not
1136 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1137 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1138 $add_save_lastsearch_values = 1;
1139 }
1140 if ($add_save_lastsearch_values) {
1141 $url .= '&save_lastsearch_values=1';
1142 }
1143 }
1144
1145 $linkclose = '';
1146 if (empty($notooltip)) {
1147 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1148 $label = $langs->trans("ShowBillOfMaterials");
1149 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1150 }
1151 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1152 $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1153 } else {
1154 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1155 }
1156
1157 $linkstart = '<a href="'.$url.'"';
1158 $linkstart .= $linkclose.'>';
1159 $linkend = '</a>';
1160
1161 $result .= $linkstart;
1162 if ($withpicto) {
1163 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1164 }
1165 if ($withpicto != 2) {
1166 $result .= $this->ref;
1167 }
1168 $result .= $linkend;
1169 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1170
1171 global $action, $hookmanager;
1172 $hookmanager->initHooks(array('bomdao'));
1173 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1174 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1175 if ($reshook > 0) {
1176 $result = $hookmanager->resPrint;
1177 } else {
1178 $result .= $hookmanager->resPrint;
1179 }
1180
1181 return $result;
1182 }
1183
1190 public function getLibStatut($mode = 0)
1191 {
1192 return $this->LibStatut($this->status, $mode);
1193 }
1194
1195 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1203 public function LibStatut($status, $mode = 0)
1204 {
1205 // phpcs:enable
1206 if (empty($this->labelStatus)) {
1207 global $langs;
1208 //$langs->load("mrp");
1209 $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1210 $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Enabled');
1211 $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Disabled');
1212 }
1213
1214 $statusType = 'status'.$status;
1215 if ($status == self::STATUS_VALIDATED) {
1216 $statusType = 'status4';
1217 }
1218 if ($status == self::STATUS_CANCELED) {
1219 $statusType = 'status6';
1220 }
1221
1222 return dolGetStatus($this->labelStatus[$status], $this->labelStatus[$status], '', $statusType, $mode);
1223 }
1224
1231 public function info($id)
1232 {
1233 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1234 $sql .= ' fk_user_creat, fk_user_modif';
1235 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1236 $sql .= ' WHERE t.rowid = '.((int) $id);
1237 $result = $this->db->query($sql);
1238 if ($result) {
1239 if ($this->db->num_rows($result)) {
1240 $obj = $this->db->fetch_object($result);
1241
1242 $this->id = $obj->rowid;
1243
1244 $this->user_creation_id = $obj->fk_user_creat;
1245 $this->user_modification_id = $obj->fk_user_modif;
1246 $this->date_creation = $this->db->jdate($obj->datec);
1247 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1248 }
1249
1250 $this->db->free($result);
1251 } else {
1252 dol_print_error($this->db);
1253 }
1254 }
1255
1261 public function getLinesArray()
1262 {
1263 $this->lines = array();
1264
1265 $objectline = new BOMLine($this->db);
1266 $result = $objectline->fetchAll('ASC', 'position', 0, 0, '(fk_bom:=:'.((int) $this->id).')');
1267
1268 if (is_numeric($result)) {
1269 $this->error = $objectline->error;
1270 $this->errors = $objectline->errors;
1271 return $result;
1272 } else {
1273 $this->lines = $result;
1274 return $this->lines;
1275 }
1276 }
1277
1289 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
1290 {
1291 global $conf, $langs;
1292
1293 $langs->load("mrp");
1294 $outputlangs->load("products");
1295
1296 if (!dol_strlen($modele)) {
1297 $modele = '';
1298
1299 if ($this->model_pdf) {
1300 $modele = $this->model_pdf;
1301 } elseif (getDolGlobalString('BOM_ADDON_PDF')) {
1302 $modele = getDolGlobalString('BOM_ADDON_PDF');
1303 }
1304 }
1305
1306 $modelpath = "core/modules/bom/doc/";
1307 if (!empty($modele)) {
1308 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
1309 } else {
1310 return 0;
1311 }
1312 }
1313
1314 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1321 public function is_photo_available($sdir)
1322 {
1323 // phpcs:enable
1324 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1325 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
1326
1327 $sdir .= '/'.get_exdir(0, 0, 0, 0, $this, 'bom');
1328
1329 $dir_osencoded = dol_osencode($sdir);
1330 if (file_exists($dir_osencoded)) {
1331 $handle = opendir($dir_osencoded);
1332 if (is_resource($handle)) {
1333 while (($file = readdir($handle)) !== false) {
1334 if (!utf8_check($file)) {
1335 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
1336 }
1337 if (dol_is_file($sdir.$file) && image_format_supported($file) >= 0) {
1338 return true;
1339 }
1340 }
1341 }
1342 }
1343 return false;
1344 }
1345
1352 public function initAsSpecimen()
1353 {
1354 $this->initAsSpecimenCommon();
1355 $this->ref = 'BOM-123';
1356 $this->date_creation = dol_now() - 20000;
1357
1358 return 1;
1359 }
1360
1361
1368 public function doScheduledJob()
1369 {
1370 global $conf, $langs;
1371
1372 //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
1373
1374 $error = 0;
1375 $this->output = '';
1376 $this->error = '';
1377
1378 dol_syslog(__METHOD__, LOG_DEBUG);
1379
1380 $now = dol_now();
1381
1382 $this->db->begin();
1383
1384 // ...
1385
1386 $this->db->commit();
1387
1388 return $error;
1389 }
1390
1397 public function calculateCosts()
1398 {
1399 global $conf, $hookmanager;
1400
1401 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
1402 $this->unit_cost = 0;
1403 $this->total_cost = 0;
1404
1405 $parameters = array();
1406 $reshook = $hookmanager->executeHooks('calculateCostsBom', $parameters, $this); // Note that $action and $object may have been modified by hook
1407
1408 if ($reshook > 0) {
1409 return $hookmanager->resPrint;
1410 }
1411
1412 if (is_array($this->lines) && count($this->lines)) {
1413 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1414 $productFournisseur = new ProductFournisseur($this->db);
1415 $tmpproduct = new Product($this->db);
1416
1417 foreach ($this->lines as &$line) {
1418 $tmpproduct->cost_price = 0;
1419 $tmpproduct->pmp = 0;
1420 $result = $tmpproduct->fetch($line->fk_product, '', '', '', 0, 1, 1); // We discard selling price and language loading
1421
1422 if ($tmpproduct->type == $tmpproduct::TYPE_PRODUCT) {
1423 if (empty($line->fk_bom_child)) {
1424 if ($result < 0) {
1425 $this->error = $tmpproduct->error;
1426 return -1;
1427 }
1428 $unit_cost = (!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp;
1429 $line->unit_cost = (float) price2num($unit_cost);
1430 if (empty($line->unit_cost)) {
1431 if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
1432 if ($productFournisseur->fourn_remise_percent != "0") {
1433 $line->unit_cost = $productFournisseur->fourn_unitprice_with_discount;
1434 } else {
1435 $line->unit_cost = $productFournisseur->fourn_unitprice;
1436 }
1437 }
1438 }
1439
1440 $line->total_cost = (float) price2num($line->qty * $line->unit_cost, 'MT');
1441
1442 $this->total_cost += $line->total_cost;
1443 } else {
1444 $bom_child = new BOM($this->db);
1445 $res = $bom_child->fetch($line->fk_bom_child);
1446 if ($res > 0) {
1447 $bom_child->calculateCosts();
1448 $line->childBom[] = $bom_child;
1449 $this->total_cost += (float) price2num($bom_child->total_cost * $line->qty, 'MT');
1450 $this->total_cost += $line->total_cost;
1451 } else {
1452 $this->error = $bom_child->error;
1453 return -2;
1454 }
1455 }
1456 } else {
1457 // Convert qty of line into hours
1458 $unitforline = measuringUnitString($line->fk_unit, '', '', 1);
1459 $qtyhourforline = convertDurationtoHour($line->qty, $unitforline);
1460
1461 if (isModEnabled('workstation') && !empty($line->fk_default_workstation)) {
1462 $workstation = new Workstation($this->db);
1463 $res = $workstation->fetch($line->fk_default_workstation);
1464
1465 if ($res > 0) {
1466 $line->total_cost = (float) price2num($qtyhourforline * ($workstation->thm_operator_estimated + $workstation->thm_machine_estimated), 'MT');
1467 } else {
1468 $this->error = $workstation->error;
1469 return -3;
1470 }
1471 } else {
1472 $defaultdurationofservice = $tmpproduct->duration;
1473 $reg = array();
1474 $qtyhourservice = 0;
1475 if (preg_match('/^(\d+)([a-z]+)$/', $defaultdurationofservice, $reg)) {
1476 $qtyhourservice = convertDurationtoHour($reg[1], $reg[2]);
1477 }
1478
1479 if ($qtyhourservice) {
1480 $line->total_cost = (float) price2num($qtyhourforline / $qtyhourservice * $tmpproduct->cost_price, 'MT');
1481 } else {
1482 $line->total_cost = (float) price2num($line->qty * $tmpproduct->cost_price, 'MT');
1483 }
1484 }
1485
1486 $this->total_cost += $line->total_cost;
1487 }
1488 }
1489
1490 $this->total_cost = (float) price2num($this->total_cost, 'MT');
1491
1492 if ($this->qty > 0) {
1493 $this->unit_cost = (float) price2num($this->total_cost / $this->qty, 'MU');
1494 } elseif ($this->qty < 0) {
1495 $this->unit_cost = (float) price2num($this->total_cost * $this->qty, 'MU');
1496 }
1497 }
1498
1499 return 1;
1500 }
1501
1510 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
1511 {
1512 $tables = array(
1513 'bom_bomline'
1514 );
1515
1516 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
1517 }
1518
1526 public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
1527 {
1528 if (!empty($this->lines)) {
1529 foreach ($this->lines as $line) {
1530 if (!empty($line->childBom)) {
1531 foreach ($line->childBom as $childBom) {
1532 $childBom->getNetNeeds($TNetNeeds, $line->qty * $qty);
1533 }
1534 } else {
1535 if (empty($TNetNeeds[$line->fk_product]['qty'])) {
1536 $TNetNeeds[$line->fk_product]['qty'] = 0.0;
1537 }
1538 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1539 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1540 $TNetNeeds[$line->fk_product]['fk_unit'] = $line->fk_unit;
1541 $TNetNeeds[$line->fk_product]['qty'] += $line->qty * $qty;
1542 }
1543 }
1544 }
1545 }
1546
1555 public function getNetNeedsTree(&$TNetNeeds = array(), $qty = 0, $level = 0)
1556 {
1557 if (!empty($this->lines)) {
1558 foreach ($this->lines as $line) {
1559 if (!empty($line->childBom)) {
1560 foreach ($line->childBom as $childBom) {
1561 $TNetNeeds[$childBom->id]['bom'] = $childBom;
1562 $TNetNeeds[$childBom->id]['parentid'] = $this->id;
1563 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1564 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1565 //$TNetNeeds[$childBom->id]['fk_unit'] = $line->fk_unit;
1566 $TNetNeeds[$childBom->id]['qty'] = $line->qty * $qty;
1567 $TNetNeeds[$childBom->id]['level'] = $level;
1568 $childBom->getNetNeedsTree($TNetNeeds, $line->qty * $qty, $level + 1);
1569 }
1570 } else {
1571 // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1572 // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1573 if (!isset($TNetNeeds[$this->id]['product'])) {
1574 $TNetNeeds[$this->id]['product'] = array();
1575 }
1576 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product])) {
1577 $TNetNeeds[$this->id]['product'][$line->fk_product] = array();
1578 }
1579 $TNetNeeds[$this->id]['product'][$line->fk_product]['fk_unit'] = $line->fk_unit;
1580 if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product]['qty'])) {
1581 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] = 0.0;
1582 }
1583 $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
1584 $TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
1585 }
1586 }
1587 }
1588 }
1589
1598 public function getParentBomTreeRecursive(&$TParentBom, $bom_id = 0, $level = 1)
1599 {
1600
1601 // Protection against infinite loop
1602 if ($level > 1000) {
1603 return;
1604 }
1605
1606 if (empty($bom_id)) {
1607 $bom_id = $this->id;
1608 }
1609
1610 $sql = 'SELECT l.fk_bom, b.label
1611 FROM '.MAIN_DB_PREFIX.'bom_bomline l
1612 INNER JOIN '.MAIN_DB_PREFIX.$this->table_element.' b ON b.rowid = l.fk_bom
1613 WHERE fk_bom_child = '.((int) $bom_id);
1614
1615 $resql = $this->db->query($sql);
1616 if (!empty($resql)) {
1617 while ($res = $this->db->fetch_object($resql)) {
1618 $TParentBom[$res->fk_bom] = $res->fk_bom;
1619 $this->getParentBomTreeRecursive($TParentBom, $res->fk_bom, $level + 1);
1620 }
1621 }
1622 }
1623
1631 public function getKanbanView($option = '', $arraydata = null)
1632 {
1633 global $db,$langs;
1634
1635 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1636
1637 $return = '<div class="box-flex-item box-flex-grow-zero">';
1638 $return .= '<div class="info-box info-box-sm">';
1639 $return .= '<span class="info-box-icon bg-infobox-action">';
1640 $return .= img_picto('', $this->picto);
1641 $return .= '</span>';
1642 $return .= '<div class="info-box-content">';
1643 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : '').'</span>';
1644 if ($selected >= 0) {
1645 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1646 }
1647 if (property_exists($this, 'fields') && !empty($this->fields['bomtype']['arrayofkeyval'])) {
1648 $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Type").' : </span>';
1649 if ($this->bomtype == 0) {
1650 $return .= '<span class="info-box-label">'.$this->fields['bomtype']['arrayofkeyval'][0].'</span>';
1651 } else {
1652 $return .= '<span class="info-box-label">'.$this->fields['bomtype']['arrayofkeyval'][1].'</span>';
1653 }
1654 }
1655 if (!empty($arraydata['prod'])) {
1656 $prod = $arraydata['prod'];
1657 $return .= '<br><span class="info-box-label">'.$prod->getNomUrl(1).'</span>';
1658 }
1659 if (method_exists($this, 'getLibStatut')) {
1660 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1661 }
1662
1663 $return .= '</div>';
1664 $return .= '</div>';
1665 $return .= '</div>';
1666 return $return;
1667 }
1668}
1669
1670
1675{
1679 public $element = 'bomline';
1680
1684 public $table_element = 'bom_bomline';
1685
1689 public $parent_element = 'bom';
1690
1694 public $fk_parent_attribute = 'fk_bom';
1695
1699 public $picto = 'bomline';
1700
1701
1721 // BEGIN MODULEBUILDER PROPERTIES
1725 public $fields = array(
1726 'rowid' => array('type' => 'integer', 'label' => 'LineID', 'enabled' => 1, 'visible' => -1, 'position' => 1, 'notnull' => 1, 'index' => 1, 'comment' => "Id",),
1727 'fk_bom' => array('type' => 'integer:BillOfMaterials:societe/class/bom.class.php', 'label' => 'BillOfMaterials', 'enabled' => 1, 'visible' => 1, 'position' => 10, 'notnull' => 1, 'index' => 1,),
1728 'fk_product' => array('type' => 'integer:Product:product/class/product.class.php', 'label' => 'Product', 'enabled' => 1, 'visible' => 1, 'position' => 20, 'notnull' => 1, 'index' => 1,),
1729 'fk_bom_child' => array('type' => 'integer:BOM:bom/class/bom.class.php', 'label' => 'BillOfMaterials', 'enabled' => 1, 'visible' => -1, 'position' => 40, 'notnull' => -1,),
1730 'description' => array('type' => 'text', 'label' => 'Description', 'enabled' => 1, 'visible' => -1, 'position' => 60, 'notnull' => -1,),
1731 'qty' => array('type' => 'double(24,8)', 'label' => 'Quantity', 'enabled' => 1, 'visible' => 1, 'position' => 100, 'notnull' => 1, 'isameasure' => 1,),
1732 'qty_frozen' => array('type' => 'smallint', 'label' => 'QuantityFrozen', 'enabled' => 1, 'visible' => 1, 'default' => '0', 'position' => 105, 'css' => 'maxwidth50imp', 'help' => 'QuantityConsumedInvariable'),
1733 'disable_stock_change' => array('type' => 'smallint', 'label' => 'DisableStockChange', 'enabled' => 1, 'visible' => 1, 'default' => '0', 'position' => 108, 'css' => 'maxwidth50imp', 'help' => 'DisableStockChangeHelp'),
1734 'efficiency' => array('type' => 'double(24,8)', 'label' => 'ManufacturingEfficiency', 'enabled' => 1, 'visible' => 0, 'default' => '1', 'position' => 110, 'notnull' => 1, 'css' => 'maxwidth50imp', 'help' => 'ValueOfEfficiencyConsumedMeans'),
1735 'fk_unit' => array('type' => 'integer', 'label' => 'Unit', 'enabled' => 1, 'visible' => 1, 'position' => 120, 'notnull' => -1,),
1736 'position' => array('type' => 'integer', 'label' => 'Rank', 'enabled' => 1, 'visible' => 0, 'default' => '0', 'position' => 200, 'notnull' => 1,),
1737 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 1000, 'notnull' => -1,),
1738 'fk_default_workstation' => array('type' => 'integer', 'label' => 'DefaultWorkstation', 'enabled' => 1, 'visible' => 1, 'notnull' => 0, 'position' => 1050)
1739 );
1740
1744 public $rowid;
1745
1749 public $fk_bom;
1750
1754 public $fk_product;
1755
1759 public $fk_bom_child;
1760
1764 public $description;
1765
1769 public $qty;
1770
1774 public $qty_frozen;
1775
1779 public $disable_stock_change;
1780
1784 public $efficiency;
1785
1791 public $fk_unit;
1792
1796 public $fk_default_workstation;
1797
1801 public $position;
1802
1806 public $import_key;
1807 // END MODULEBUILDER PROPERTIES
1808
1812 public $total_cost = 0;
1813
1817 public $unit_cost = 0;
1818
1822 public $childBom = array();
1823
1824
1825
1831 public function __construct(DoliDB $db)
1832 {
1833 global $langs;
1834
1835 $this->db = $db;
1836
1837 $this->ismultientitymanaged = 0;
1838
1839 $this->isextrafieldmanaged = 1;
1840
1841 if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid'])) {
1842 $this->fields['rowid']['visible'] = 0;
1843 }
1844 if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
1845 $this->fields['entity']['enabled'] = 0;
1846 }
1847
1848 // Unset fields that are disabled
1849 foreach ($this->fields as $key => $val) {
1850 if (isset($val['enabled']) && empty($val['enabled'])) {
1851 unset($this->fields[$key]);
1852 }
1853 }
1854
1855 // Translate some data of arrayofkeyval
1856 foreach ($this->fields as $key => $val) {
1857 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
1858 foreach ($val['arrayofkeyval'] as $key2 => $val2) {
1859 $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
1860 }
1861 }
1862 }
1863 }
1864
1872 public function create(User $user, $notrigger = 0)
1873 {
1874 if ($this->efficiency < 0 || $this->efficiency > 1) {
1875 $this->efficiency = 1;
1876 }
1877
1878 return $this->createCommon($user, $notrigger);
1879 }
1880
1888 public function fetch($id, $ref = null)
1889 {
1890 $result = $this->fetchCommon($id, $ref);
1891 //if ($result > 0 && !empty($this->table_element_line)) $this->fetchLines();
1892 return $result;
1893 }
1894
1907 public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, $filter = '', $filtermode = 'AND')
1908 {
1909 dol_syslog(__METHOD__, LOG_DEBUG);
1910
1911 $records = array();
1912
1913 $sql = 'SELECT ';
1914 $sql .= $this->getFieldList();
1915 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
1916 if ($this->ismultientitymanaged) {
1917 $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
1918 } else {
1919 $sql .= ' WHERE 1 = 1';
1920 }
1921
1922 // Manage filter
1923 $errormessage = '';
1924 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
1925 if ($errormessage) {
1926 $this->errors[] = $errormessage;
1927 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
1928 return -1;
1929 }
1930
1931 if (!empty($sortfield)) {
1932 $sql .= $this->db->order($sortfield, $sortorder);
1933 }
1934 if (!empty($limit)) {
1935 $sql .= $this->db->plimit($limit, $offset);
1936 }
1937
1938 $resql = $this->db->query($sql);
1939 if ($resql) {
1940 $num = $this->db->num_rows($resql);
1941
1942 while ($obj = $this->db->fetch_object($resql)) {
1943 $record = new self($this->db);
1944 $record->setVarsFromFetchObj($obj);
1945 $record->fetch_optionals();
1946
1947 $records[$record->id] = $record;
1948 }
1949 $this->db->free($resql);
1950
1951 return $records;
1952 } else {
1953 $this->errors[] = 'Error '.$this->db->lasterror();
1954 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
1955
1956 return -1;
1957 }
1958 }
1959
1967 public function update(User $user, $notrigger = 0)
1968 {
1969 if ($this->efficiency < 0 || $this->efficiency > 1) {
1970 $this->efficiency = 1;
1971 }
1972
1973 return $this->updateCommon($user, $notrigger);
1974 }
1975
1983 public function delete(User $user, $notrigger = 0)
1984 {
1985 return $this->deleteCommon($user, $notrigger);
1986 //return $this->deleteCommon($user, $notrigger, 1);
1987 }
1988
1999 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
2000 {
2001 global $db, $conf, $langs, $hookmanager;
2002
2003 if (!empty($conf->dol_no_mouse_hover)) {
2004 $notooltip = 1; // Force disable tooltips
2005 }
2006
2007 $result = '';
2008
2009 $label = '<u>'.$langs->trans("BillOfMaterialsLine").'</u>';
2010 $label .= '<br>';
2011 $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref;
2012
2013 $url = DOL_URL_ROOT.'/bom/bomline_card.php?id='.$this->id;
2014
2015 if ($option != 'nolink') {
2016 // Add param to save lastsearch_values or not
2017 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2018 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2019 $add_save_lastsearch_values = 1;
2020 }
2021 if ($add_save_lastsearch_values) {
2022 $url .= '&save_lastsearch_values=1';
2023 }
2024 }
2025
2026 $linkclose = '';
2027 if (empty($notooltip)) {
2028 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2029 $label = $langs->trans("ShowBillOfMaterialsLine");
2030 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
2031 }
2032 $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
2033 $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
2034 } else {
2035 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
2036 }
2037
2038 $linkstart = '<a href="'.$url.'"';
2039 $linkstart .= $linkclose.'>';
2040 $linkend = '</a>';
2041
2042 $result .= $linkstart;
2043 if ($withpicto) {
2044 $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);
2045 }
2046 if ($withpicto != 2) {
2047 $result .= $this->ref;
2048 }
2049 $result .= $linkend;
2050 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
2051
2052 global $action, $hookmanager;
2053 $hookmanager->initHooks(array('bomlinedao'));
2054 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2055 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2056 if ($reshook > 0) {
2057 $result = $hookmanager->resPrint;
2058 } else {
2059 $result .= $hookmanager->resPrint;
2060 }
2061
2062 return $result;
2063 }
2064
2071 public function getLibStatut($mode = 0)
2072 {
2073 return $this->LibStatut($this->status, $mode);
2074 }
2075
2076 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2084 public function LibStatut($status, $mode = 0)
2085 {
2086 // phpcs:enable
2087 return '';
2088 }
2089
2096 public function info($id)
2097 {
2098 $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
2099 $sql .= ' fk_user_creat, fk_user_modif';
2100 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
2101 $sql .= ' WHERE t.rowid = '.((int) $id);
2102 $result = $this->db->query($sql);
2103 if ($result) {
2104 if ($this->db->num_rows($result)) {
2105 $obj = $this->db->fetch_object($result);
2106
2107 $this->id = $obj->rowid;
2108
2109 $this->user_creation_id = $obj->fk_user_creat;
2110 $this->user_modification_id = $obj->fk_user_modif;
2111 $this->date_creation = $this->db->jdate($obj->datec);
2112 $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
2113 }
2114 $this->db->free($result);
2115 } else {
2116 dol_print_error($this->db);
2117 }
2118 }
2119
2126 public function initAsSpecimen()
2127 {
2128 return $this->initAsSpecimenCommon();
2129 }
2130}
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:626
$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:333
dol_is_file($pathoffile)
Return if path is a file.
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:63
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0)
forgeSQLFromUniversalSearchCriteria
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return 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:1929