1<?php 2/* Copyright (C) 2019 Laurent Destailleur <eldy@users.sourceforge.net> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18/** 19 * \file bom/class/bom.class.php 20 * \ingroup bom 21 * \brief This file is a CRUD class file for BOM (Create/Read/Update/Delete) 22 */ 23 24// Put here all includes required by your class file 25require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; 26//require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php'; 27//require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php'; 28 29 30/** 31 * Class for BOM 32 */ 33class BOM extends CommonObject 34{ 35 /** 36 * @var string ID to identify managed object 37 */ 38 public $element = 'bom'; 39 40 /** 41 * @var string Name of table without prefix where object is stored 42 */ 43 public $table_element = 'bom_bom'; 44 45 /** 46 * @var int Does bom support multicompany module ? 0=No test on entity, 1=Test with field entity, 2=Test with link by societe 47 */ 48 public $ismultientitymanaged = 1; 49 50 /** 51 * @var int Does object support extrafields ? 0=No, 1=Yes 52 */ 53 public $isextrafieldmanaged = 1; 54 55 /** 56 * @var string String with name of icon for bom. Must be the part after the 'object_' into object_bom.png 57 */ 58 public $picto = 'bom'; 59 60 61 const STATUS_DRAFT = 0; 62 const STATUS_VALIDATED = 1; 63 const STATUS_CANCELED = 9; 64 65 66 /** 67 * 'type' field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter]]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'text:none', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password') 68 * Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)" 69 * 'label' the translation key. 70 * 'picto' is code of a picto to show before value in forms 71 * 'enabled' is a condition when the field must be managed (Example: 1 or '$conf->global->MY_SETUP_PARAM) 72 * 'position' is the sort order of field. 73 * 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0). 74 * 'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing) 75 * 'noteditable' says if field is not editable (1 or 0) 76 * 'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created. 77 * 'index' if we want an index in database. 78 * 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...). 79 * 'searchall' is 1 if we want to search in this field when making a search from the quick search button. 80 * 'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8). 81 * 'css' and 'cssview' and 'csslist' is the CSS style to use on field. 'css' is used in creation and update. 'cssview' is used in view mode. 'csslist' is used for columns in lists. For example: 'maxwidth200', 'wordbreak', 'tdoverflowmax200' 82 * 'help' is a 'TranslationString' to use to show a tooltip on field. You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click. 83 * 'showoncombobox' if value of the field must be visible into the label of the combobox that list record 84 * 'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code. 85 * 'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel") 86 * 'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1. 87 * 'comment' is not used. You can store here any text of your choice. It is not used by application. 88 * 89 * Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor. 90 */ 91 92 // BEGIN MODULEBUILDER PROPERTIES 93 /** 94 * @var array Array with all fields and their property. Do not use it as a static var. It may be modified by constructor. 95 */ 96 public $fields = array( 97 'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-1, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>"Id",), 98 'entity' => array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'notnull'=> 1, 'default'=>1, 'index'=>1, 'position'=>5), 99 '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',), 100 'label' => array('type'=>'varchar(255)', 'label'=>'Label', 'enabled'=>1, 'visible'=>1, 'position'=>30, 'notnull'=>1, 'searchall'=>1, 'showoncombobox'=>'2', 'autofocusoncreate'=>1, 'css'=>'maxwidth300', 'csslist'=>'tdoverflowmax200'), 101 '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'), 102 //'bomtype' => array('type'=>'integer', 'label'=>'Type', 'enabled'=>1, 'visible'=>-1, 'position'=>32, 'notnull'=>1, 'default'=>'0', 'arrayofkeyval'=>array(0=>'Manufacturing')), 103 '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'), 104 'description' => array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>-1, 'position'=>60, 'notnull'=>-1,), 105 'qty' => array('type'=>'real', 'label'=>'Quantity', 'enabled'=>1, 'visible'=>1, 'default'=>1, 'position'=>55, 'notnull'=>1, 'isameasure'=>'1', 'css'=>'maxwidth75imp'), 106 //'efficiency' => array('type'=>'real', 'label'=>'ManufacturingEfficiency', 'enabled'=>1, 'visible'=>-1, 'default'=>1, 'position'=>100, 'notnull'=>0, 'css'=>'maxwidth50imp', 'help'=>'ValueOfMeansLossForProductProduced'), 107 'duration' => array('type'=>'duration', 'label'=>'EstimatedDuration', 'enabled'=>1, 'visible'=>-1, 'position'=>101, 'notnull'=>-1, 'css'=>'maxwidth50imp', 'help'=>'EstimatedDurationDesc'), 108 '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'), 109 'note_public' => array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>-2, 'position'=>161, 'notnull'=>-1,), 110 'note_private' => array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>-2, 'position'=>162, 'notnull'=>-1,), 111 'date_creation' => array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>300, 'notnull'=>1,), 112 'tms' => array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-2, 'position'=>501, 'notnull'=>1,), 113 'date_valid' => array('type'=>'datetime', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-2, 'position'=>502, 'notnull'=>0,), 114 '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'), 115 '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'), 116 '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'), 117 'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>1000, 'notnull'=>-1,), 118 'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>1, 'visible'=>0, 'position'=>1010), 119 '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')), 120 ); 121 122 /** 123 * @var int rowid 124 */ 125 public $rowid; 126 127 /** 128 * @var string ref 129 */ 130 public $ref; 131 132 /** 133 * @var string label 134 */ 135 public $label; 136 137 /** 138 * @var int bomtype 139 */ 140 public $bomtype; 141 142 /** 143 * @var string description 144 */ 145 public $description; 146 147 /** 148 * @var integer|string date_creation 149 */ 150 public $date_creation; 151 152 153 public $tms; 154 155 /** 156 * @var int Id User creator 157 */ 158 public $fk_user_creat; 159 160 /** 161 * @var int Id User modifying 162 */ 163 public $fk_user_modif; 164 165 /** 166 * @var string import key 167 */ 168 public $import_key; 169 170 /** 171 * @var int status 172 */ 173 public $status; 174 175 /** 176 * @var int product Id 177 */ 178 public $fk_product; 179 public $qty; 180 public $efficiency; 181 // END MODULEBUILDER PROPERTIES 182 183 184 // If this object has a subtable with lines 185 186 /** 187 * @var int Name of subtable line 188 */ 189 public $table_element_line = 'bom_bomline'; 190 191 /** 192 * @var string Fieldname with ID of parent key if this field has a parent 193 */ 194 public $fk_element = 'fk_bom'; 195 196 /** 197 * @var string Name of subtable class that manage subtable lines 198 */ 199 public $class_element_line = 'BOMLine'; 200 201 // /** 202 // * @var array List of child tables. To test if we can delete object. 203 // */ 204 // protected $childtables=array(); 205 206 /** 207 * @var array List of child tables. To know object to delete on cascade. 208 */ 209 protected $childtablesoncascade = array('bom_bomline'); 210 211 /** 212 * @var BOMLine[] Array of subtable lines 213 */ 214 public $lines = array(); 215 216 /** 217 * @var int Calculated cost for the BOM 218 */ 219 public $total_cost = 0; 220 221 /** 222 * @var int Calculated cost for 1 unit of the product in BOM 223 */ 224 public $unit_cost = 0; 225 226 227 228 /** 229 * Constructor 230 * 231 * @param DoliDb $db Database handler 232 */ 233 public function __construct(DoliDB $db) 234 { 235 global $conf, $langs; 236 237 $this->db = $db; 238 239 if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && isset($this->fields['rowid'])) { 240 $this->fields['rowid']['visible'] = 0; 241 } 242 if (empty($conf->multicompany->enabled) && isset($this->fields['entity'])) { 243 $this->fields['entity']['enabled'] = 0; 244 } 245 246 // Unset fields that are disabled 247 foreach ($this->fields as $key => $val) { 248 if (isset($val['enabled']) && empty($val['enabled'])) { 249 unset($this->fields[$key]); 250 } 251 } 252 253 // Translate some data of arrayofkeyval 254 foreach ($this->fields as $key => $val) { 255 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) { 256 foreach ($val['arrayofkeyval'] as $key2 => $val2) { 257 $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2); 258 } 259 } 260 } 261 } 262 263 /** 264 * Create object into database 265 * 266 * @param User $user User that creates 267 * @param bool $notrigger false=launch triggers after, true=disable triggers 268 * @return int <0 if KO, Id of created object if OK 269 */ 270 public function create(User $user, $notrigger = false) 271 { 272 if ($this->efficiency <= 0 || $this->efficiency > 1) { 273 $this->efficiency = 1; 274 } 275 276 return $this->createCommon($user, $notrigger); 277 } 278 279 /** 280 * Clone an object into another one 281 * 282 * @param User $user User that creates 283 * @param int $fromid Id of object to clone 284 * @return mixed New object created, <0 if KO 285 */ 286 public function createFromClone(User $user, $fromid) 287 { 288 global $langs, $hookmanager, $extrafields; 289 $error = 0; 290 291 dol_syslog(__METHOD__, LOG_DEBUG); 292 293 $object = new self($this->db); 294 295 $this->db->begin(); 296 297 // Load source object 298 $result = $object->fetchCommon($fromid); 299 if ($result > 0 && !empty($object->table_element_line)) { 300 $object->fetchLines(); 301 } 302 303 // Get lines so they will be clone 304 //foreach ($object->lines as $line) 305 // $line->fetch_optionals(); 306 307 // Reset some properties 308 unset($object->id); 309 unset($object->fk_user_creat); 310 unset($object->import_key); 311 312 // Clear fields 313 $object->ref = empty($this->fields['ref']['default']) ? $langs->trans("copy_of_").$object->ref : $this->fields['ref']['default']; 314 $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf")." ".$object->label : $this->fields['label']['default']; 315 $object->status = self::STATUS_DRAFT; 316 // ... 317 // Clear extrafields that are unique 318 if (is_array($object->array_options) && count($object->array_options) > 0) { 319 $extrafields->fetch_name_optionals_label($object->table_element); 320 foreach ($object->array_options as $key => $option) { 321 $shortkey = preg_replace('/options_/', '', $key); 322 if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) { 323 //var_dump($key); var_dump($clonedObj->array_options[$key]); exit; 324 unset($object->array_options[$key]); 325 } 326 } 327 } 328 329 // Create clone 330 $object->context['createfromclone'] = 'createfromclone'; 331 $result = $object->createCommon($user); 332 if ($result < 0) { 333 $error++; 334 $this->error = $object->error; 335 $this->errors = $object->errors; 336 } 337 338 if (!$error) { 339 // copy internal contacts 340 if ($this->copy_linked_contact($object, 'internal') < 0) { 341 $error++; 342 } 343 } 344 345 if (!$error) { 346 // copy external contacts if same company 347 if (property_exists($this, 'socid') && $this->socid == $object->socid) { 348 if ($this->copy_linked_contact($object, 'external') < 0) { 349 $error++; 350 } 351 } 352 } 353 354 // If there is lines, create lines too 355 356 357 358 unset($object->context['createfromclone']); 359 360 // End 361 if (!$error) { 362 $this->db->commit(); 363 return $object; 364 } else { 365 $this->db->rollback(); 366 return -1; 367 } 368 } 369 370 /** 371 * Load object in memory from the database 372 * 373 * @param int $id Id object 374 * @param string $ref Ref 375 * @return int <0 if KO, 0 if not found, >0 if OK 376 */ 377 public function fetch($id, $ref = null) 378 { 379 $result = $this->fetchCommon($id, $ref); 380 381 if ($result > 0 && !empty($this->table_element_line)) { 382 $this->fetchLines(); 383 } 384 //$this->calculateCosts(); // This consume a high number of subrequests. Do not call it into fetch but when you need it. 385 386 return $result; 387 } 388 389 /** 390 * Load object lines in memory from the database 391 * 392 * @return int <0 if KO, 0 if not found, >0 if OK 393 */ 394 public function fetchLines() 395 { 396 $this->lines = array(); 397 398 $result = $this->fetchLinesCommon(); 399 return $result; 400 } 401 402 /** 403 * Load list of objects in memory from the database. 404 * 405 * @param string $sortorder Sort Order 406 * @param string $sortfield Sort field 407 * @param int $limit limit 408 * @param int $offset Offset 409 * @param array $filter Filter array. Example array('field'=>'valueforlike', 'customurl'=>...) 410 * @param string $filtermode Filter mode (AND or OR) 411 * @return array|int int <0 if KO, array of pages if OK 412 */ 413 public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND') 414 { 415 global $conf; 416 417 dol_syslog(__METHOD__, LOG_DEBUG); 418 419 $records = array(); 420 421 $sql = 'SELECT '; 422 $sql .= $this->getFieldList(); 423 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t'; 424 if ($this->ismultientitymanaged) { 425 $sql .= ' WHERE t.entity IN ('.getEntity($this->table_element).')'; 426 } else { 427 $sql .= ' WHERE 1 = 1'; 428 } 429 // Manage filter 430 $sqlwhere = array(); 431 if (count($filter) > 0) { 432 foreach ($filter as $key => $value) { 433 if ($key == 't.rowid') { 434 $sqlwhere[] = $key.' = '.((int) $value); 435 } elseif (strpos($key, 'date') !== false) { 436 $sqlwhere[] = $key." = '".$this->db->idate($value)."'"; 437 } elseif ($key == 'customsql') { 438 $sqlwhere[] = $value; 439 } else { 440 $sqlwhere[] = $key." LIKE '%".$this->db->escape($value)."%'"; 441 } 442 } 443 } 444 if (count($sqlwhere) > 0) { 445 $sql .= ' AND ('.implode(' '.$filtermode.' ', $sqlwhere).')'; 446 } 447 448 if (!empty($sortfield)) { 449 $sql .= $this->db->order($sortfield, $sortorder); 450 } 451 if (!empty($limit)) { 452 $sql .= ' '.$this->db->plimit($limit, $offset); 453 } 454 455 $resql = $this->db->query($sql); 456 if ($resql) { 457 $num = $this->db->num_rows($resql); 458 459 while ($obj = $this->db->fetch_object($resql)) { 460 $record = new self($this->db); 461 $record->setVarsFromFetchObj($obj); 462 463 $records[$record->id] = $record; 464 } 465 $this->db->free($resql); 466 467 return $records; 468 } else { 469 $this->errors[] = 'Error '.$this->db->lasterror(); 470 dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR); 471 472 return -1; 473 } 474 } 475 476 /** 477 * Update object into database 478 * 479 * @param User $user User that modifies 480 * @param bool $notrigger false=launch triggers after, true=disable triggers 481 * @return int <0 if KO, >0 if OK 482 */ 483 public function update(User $user, $notrigger = false) 484 { 485 if ($this->efficiency <= 0 || $this->efficiency > 1) { 486 $this->efficiency = 1; 487 } 488 489 return $this->updateCommon($user, $notrigger); 490 } 491 492 /** 493 * Delete object in database 494 * 495 * @param User $user User that deletes 496 * @param bool $notrigger false=launch triggers after, true=disable triggers 497 * @return int <0 if KO, >0 if OK 498 */ 499 public function delete(User $user, $notrigger = false) 500 { 501 return $this->deleteCommon($user, $notrigger); 502 //return $this->deleteCommon($user, $notrigger, 1); 503 } 504 505 /** 506 * Delete a line of object in database 507 * 508 * @param User $user User that delete 509 * @param int $idline Id of line to delete 510 * @param bool $notrigger false=launch triggers after, true=disable triggers 511 * @return int >0 if OK, <0 if KO 512 */ 513 public function deleteLine(User $user, $idline, $notrigger = false) 514 { 515 if ($this->status < 0) { 516 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus'; 517 return -2; 518 } 519 520 return $this->deleteLineCommon($user, $idline, $notrigger); 521 } 522 523 /** 524 * Returns the reference to the following non used BOM depending on the active numbering module 525 * defined into BOM_ADDON 526 * 527 * @param Product $prod Object product 528 * @return string BOM free reference 529 */ 530 public function getNextNumRef($prod) 531 { 532 global $langs, $conf; 533 $langs->load("mrp"); 534 535 if (!empty($conf->global->BOM_ADDON)) { 536 $mybool = false; 537 538 $file = $conf->global->BOM_ADDON.".php"; 539 $classname = $conf->global->BOM_ADDON; 540 541 // Include file with class 542 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']); 543 foreach ($dirmodels as $reldir) { 544 $dir = dol_buildpath($reldir."core/modules/bom/"); 545 546 // Load file with numbering class (if found) 547 $mybool |= @include_once $dir.$file; 548 } 549 550 if ($mybool === false) { 551 dol_print_error('', "Failed to include file ".$file); 552 return ''; 553 } 554 555 $obj = new $classname(); 556 $numref = $obj->getNextValue($prod, $this); 557 558 if ($numref != "") { 559 return $numref; 560 } else { 561 $this->error = $obj->error; 562 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error); 563 return ""; 564 } 565 } else { 566 print $langs->trans("Error")." ".$langs->trans("Error_BOM_ADDON_NotDefined"); 567 return ""; 568 } 569 } 570 571 /** 572 * Validate bom 573 * 574 * @param User $user User making status change 575 * @param int $notrigger 1=Does not execute triggers, 0= execute triggers 576 * @return int <=0 if OK, 0=Nothing done, >0 if KO 577 */ 578 public function validate($user, $notrigger = 0) 579 { 580 global $conf, $langs; 581 582 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; 583 584 $error = 0; 585 586 // Protection 587 if ($this->status == self::STATUS_VALIDATED) { 588 dol_syslog(get_class($this)."::validate action abandonned: already validated", LOG_WARNING); 589 return 0; 590 } 591 592 /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->bom->create)) 593 || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->bom->bom_advance->validate)))) 594 { 595 $this->error='NotEnoughPermissions'; 596 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR); 597 return -1; 598 }*/ 599 600 $now = dol_now(); 601 602 $this->db->begin(); 603 604 // Define new ref 605 if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life 606 $this->fetch_product(); 607 $num = $this->getNextNumRef($this->product); 608 } else { 609 $num = $this->ref; 610 } 611 $this->newref = dol_sanitizeFileName($num); 612 613 // Validate 614 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element; 615 $sql .= " SET ref = '".$this->db->escape($num)."',"; 616 $sql .= " status = ".self::STATUS_VALIDATED.","; 617 $sql .= " date_valid='".$this->db->idate($now)."',"; 618 $sql .= " fk_user_valid = ".((int) $user->id); 619 $sql .= " WHERE rowid = ".((int) $this->id); 620 621 dol_syslog(get_class($this)."::validate()", LOG_DEBUG); 622 $resql = $this->db->query($sql); 623 if (!$resql) { 624 dol_print_error($this->db); 625 $this->error = $this->db->lasterror(); 626 $error++; 627 } 628 629 if (!$error && !$notrigger) { 630 // Call trigger 631 $result = $this->call_trigger('BOM_VALIDATE', $user); 632 if ($result < 0) { 633 $error++; 634 } 635 // End call triggers 636 } 637 638 if (!$error) { 639 $this->oldref = $this->ref; 640 641 // Rename directory if dir was a temporary ref 642 if (preg_match('/^[\(]?PROV/i', $this->ref)) { 643 // Now we rename also files into index 644 $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)."'"; 645 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'bom/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; 646 $resql = $this->db->query($sql); 647 if (!$resql) { 648 $error++; $this->error = $this->db->lasterror(); 649 } 650 651 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments 652 $oldref = dol_sanitizeFileName($this->ref); 653 $newref = dol_sanitizeFileName($num); 654 $dirsource = $conf->bom->dir_output.'/'.$oldref; 655 $dirdest = $conf->bom->dir_output.'/'.$newref; 656 if (!$error && file_exists($dirsource)) { 657 dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest); 658 659 if (@rename($dirsource, $dirdest)) { 660 dol_syslog("Rename ok"); 661 // Rename docs starting with $oldref with $newref 662 $listoffiles = dol_dir_list($conf->bom->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/')); 663 foreach ($listoffiles as $fileentry) { 664 $dirsource = $fileentry['name']; 665 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource); 666 $dirsource = $fileentry['path'].'/'.$dirsource; 667 $dirdest = $fileentry['path'].'/'.$dirdest; 668 @rename($dirsource, $dirdest); 669 } 670 } 671 } 672 } 673 } 674 675 // Set new ref and current status 676 if (!$error) { 677 $this->ref = $num; 678 $this->status = self::STATUS_VALIDATED; 679 } 680 681 if (!$error) { 682 $this->db->commit(); 683 return 1; 684 } else { 685 $this->db->rollback(); 686 return -1; 687 } 688 } 689 690 /** 691 * Set draft status 692 * 693 * @param User $user Object user that modify 694 * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers 695 * @return int <0 if KO, >0 if OK 696 */ 697 public function setDraft($user, $notrigger = 0) 698 { 699 // Protection 700 if ($this->status <= self::STATUS_DRAFT) { 701 return 0; 702 } 703 704 /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->bom->write)) 705 || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->bom->bom_advance->validate)))) 706 { 707 $this->error='Permission denied'; 708 return -1; 709 }*/ 710 711 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'BOM_UNVALIDATE'); 712 } 713 714 /** 715 * Set cancel status 716 * 717 * @param User $user Object user that modify 718 * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers 719 * @return int <0 if KO, 0=Nothing done, >0 if OK 720 */ 721 public function cancel($user, $notrigger = 0) 722 { 723 // Protection 724 if ($this->status != self::STATUS_VALIDATED) { 725 return 0; 726 } 727 728 /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->bom->write)) 729 || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->bom->bom_advance->validate)))) 730 { 731 $this->error='Permission denied'; 732 return -1; 733 }*/ 734 735 return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'BOM_CLOSE'); 736 } 737 738 /** 739 * Set cancel status 740 * 741 * @param User $user Object user that modify 742 * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers 743 * @return int <0 if KO, 0=Nothing done, >0 if OK 744 */ 745 public function reopen($user, $notrigger = 0) 746 { 747 // Protection 748 if ($this->status != self::STATUS_CANCELED) { 749 return 0; 750 } 751 752 /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->bom->write)) 753 || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->bom->bom_advance->validate)))) 754 { 755 $this->error='Permission denied'; 756 return -1; 757 }*/ 758 759 return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'BOM_REOPEN'); 760 } 761 762 763 /** 764 * Return a link to the object card (with optionaly the picto) 765 * 766 * @param int $withpicto Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto) 767 * @param string $option On what the link point to ('nolink', ...) 768 * @param int $notooltip 1=Disable tooltip 769 * @param string $morecss Add more css on link 770 * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking 771 * @return string String with URL 772 */ 773 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1) 774 { 775 global $db, $conf, $langs, $hookmanager; 776 777 if (!empty($conf->dol_no_mouse_hover)) { 778 $notooltip = 1; // Force disable tooltips 779 } 780 781 $result = ''; 782 783 $label = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("BillOfMaterials").'</u>'; 784 if (isset($this->status)) { 785 $label .= ' '.$this->getLibStatut(5); 786 } 787 $label .= '<br>'; 788 $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref; 789 if (isset($this->label)) { 790 $label .= '<br><b>'.$langs->trans('Label').':</b> '.$this->label; 791 } 792 793 $url = DOL_URL_ROOT.'/bom/bom_card.php?id='.$this->id; 794 795 if ($option != 'nolink') { 796 // Add param to save lastsearch_values or not 797 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0); 798 if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) { 799 $add_save_lastsearch_values = 1; 800 } 801 if ($add_save_lastsearch_values) { 802 $url .= '&save_lastsearch_values=1'; 803 } 804 } 805 806 $linkclose = ''; 807 if (empty($notooltip)) { 808 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) { 809 $label = $langs->trans("ShowBillOfMaterials"); 810 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"'; 811 } 812 $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"'; 813 $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"'; 814 815 /* 816 $hookmanager->initHooks(array('bomdao')); 817 $parameters=array('id'=>$this->id); 818 $reshook=$hookmanager->executeHooks('getnomurltooltip',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks 819 if ($reshook > 0) $linkclose = $hookmanager->resPrint; 820 */ 821 } else { 822 $linkclose = ($morecss ? ' class="'.$morecss.'"' : ''); 823 } 824 825 $linkstart = '<a href="'.$url.'"'; 826 $linkstart .= $linkclose.'>'; 827 $linkend = '</a>'; 828 829 $result .= $linkstart; 830 if ($withpicto) { 831 $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); 832 } 833 if ($withpicto != 2) { 834 $result .= $this->ref; 835 } 836 $result .= $linkend; 837 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : ''); 838 839 global $action, $hookmanager; 840 $hookmanager->initHooks(array('bomdao')); 841 $parameters = array('id'=>$this->id, 'getnomurl'=>$result); 842 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks 843 if ($reshook > 0) { 844 $result = $hookmanager->resPrint; 845 } else { 846 $result .= $hookmanager->resPrint; 847 } 848 849 return $result; 850 } 851 852 /** 853 * Return label of the status 854 * 855 * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto 856 * @return string Label of status 857 */ 858 public function getLibStatut($mode = 0) 859 { 860 return $this->LibStatut($this->status, $mode); 861 } 862 863 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 864 /** 865 * Return the status 866 * 867 * @param int $status Id status 868 * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto 869 * @return string Label of status 870 */ 871 public function LibStatut($status, $mode = 0) 872 { 873 // phpcs:enable 874 if (empty($this->labelStatus)) { 875 global $langs; 876 //$langs->load("mrp"); 877 $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft'); 878 $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Enabled'); 879 $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Disabled'); 880 } 881 882 $statusType = 'status'.$status; 883 if ($status == self::STATUS_VALIDATED) { 884 $statusType = 'status4'; 885 } 886 if ($status == self::STATUS_CANCELED) { 887 $statusType = 'status6'; 888 } 889 890 return dolGetStatus($this->labelStatus[$status], $this->labelStatus[$status], '', $statusType, $mode); 891 } 892 893 /** 894 * Load the info information in the object 895 * 896 * @param int $id Id of object 897 * @return void 898 */ 899 public function info($id) 900 { 901 $sql = 'SELECT rowid, date_creation as datec, tms as datem,'; 902 $sql .= ' fk_user_creat, fk_user_modif'; 903 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t'; 904 $sql .= ' WHERE t.rowid = '.((int) $id); 905 $result = $this->db->query($sql); 906 if ($result) { 907 if ($this->db->num_rows($result)) { 908 $obj = $this->db->fetch_object($result); 909 $this->id = $obj->rowid; 910 if ($obj->fk_user_author) { 911 $cuser = new User($this->db); 912 $cuser->fetch($obj->fk_user_author); 913 $this->user_creation = $cuser; 914 } 915 916 if ($obj->fk_user_valid) { 917 $vuser = new User($this->db); 918 $vuser->fetch($obj->fk_user_valid); 919 $this->user_validation = $vuser; 920 } 921 922 if ($obj->fk_user_cloture) { 923 $cluser = new User($this->db); 924 $cluser->fetch($obj->fk_user_cloture); 925 $this->user_cloture = $cluser; 926 } 927 928 $this->date_creation = $this->db->jdate($obj->datec); 929 $this->date_modification = $this->db->jdate($obj->datem); 930 $this->date_validation = $this->db->jdate($obj->datev); 931 } 932 933 $this->db->free($result); 934 } else { 935 dol_print_error($this->db); 936 } 937 } 938 939 /** 940 * Create an array of lines 941 * 942 * @return array|int array of lines if OK, <0 if KO 943 */ 944 public function getLinesArray() 945 { 946 $this->lines = array(); 947 948 $objectline = new BOMLine($this->db); 949 $result = $objectline->fetchAll('ASC', 'position', 0, 0, array('customsql'=>'fk_bom = '.((int) $this->id))); 950 951 if (is_numeric($result)) { 952 $this->error = $this->error; 953 $this->errors = $this->errors; 954 return $result; 955 } else { 956 $this->lines = $result; 957 return $this->lines; 958 } 959 } 960 961 /** 962 * Create a document onto disk according to template module. 963 * 964 * @param string $modele Force template to use ('' to not force) 965 * @param Translate $outputlangs objet lang a utiliser pour traduction 966 * @param int $hidedetails Hide details of lines 967 * @param int $hidedesc Hide description 968 * @param int $hideref Hide ref 969 * @param null|array $moreparams Array to provide more information 970 * @return int 0 if KO, 1 if OK 971 */ 972 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null) 973 { 974 global $conf, $langs; 975 976 $langs->load("mrp"); 977 $outputlangs->load("products"); 978 979 if (!dol_strlen($modele)) { 980 $modele = 'standard'; 981 982 if ($this->model_pdf) { 983 $modele = $this->model_pdf; 984 } elseif (!empty($conf->global->BOM_ADDON_PDF)) { 985 $modele = $conf->global->BOM_ADDON_PDF; 986 } 987 } 988 989 $modelpath = "core/modules/bom/doc/"; 990 991 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams); 992 } 993 994 /** 995 * Initialise object with example values 996 * Id must be 0 if object instance is a specimen 997 * 998 * @return void 999 */ 1000 public function initAsSpecimen() 1001 { 1002 $this->initAsSpecimenCommon(); 1003 $this->ref = 'BOM-123'; 1004 $this->date = $this->date_creation; 1005 } 1006 1007 1008 /** 1009 * Action executed by scheduler 1010 * CAN BE A CRON TASK. In such a case, parameters come from the schedule job setup field 'Parameters' 1011 * 1012 * @return int 0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK) 1013 */ 1014 public function doScheduledJob() 1015 { 1016 global $conf, $langs; 1017 1018 //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log'; 1019 1020 $error = 0; 1021 $this->output = ''; 1022 $this->error = ''; 1023 1024 dol_syslog(__METHOD__, LOG_DEBUG); 1025 1026 $now = dol_now(); 1027 1028 $this->db->begin(); 1029 1030 // ... 1031 1032 $this->db->commit(); 1033 1034 return $error; 1035 } 1036 1037 /** 1038 * BOM costs calculation based on cost_price or pmp of each BOM line. 1039 * Set the property ->total_cost and ->unit_cost of BOM. 1040 * 1041 * @return void 1042 */ 1043 public function calculateCosts() 1044 { 1045 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; 1046 $this->unit_cost = 0; 1047 $this->total_cost = 0; 1048 1049 if (is_array($this->lines) && count($this->lines)) { 1050 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php'; 1051 $productFournisseur = new ProductFournisseur($this->db); 1052 $tmpproduct = new Product($this->db); 1053 1054 foreach ($this->lines as &$line) { 1055 $tmpproduct->cost_price = 0; 1056 $tmpproduct->pmp = 0; 1057 1058 $result = $tmpproduct->fetch($line->fk_product, '', '', '', 0, 1, 1); // We discard selling price and language loading 1059 if ($result < 0) { 1060 $this->error = $tmpproduct->error; 1061 return -1; 1062 } 1063 $line->unit_cost = price2num((!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp); 1064 if (empty($line->unit_cost)) { 1065 if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) { 1066 $line->unit_cost = $productFournisseur->fourn_unitprice; 1067 } 1068 } 1069 1070 $line->total_cost = price2num($line->qty * $line->unit_cost, 'MT'); 1071 1072 $this->total_cost += $line->total_cost; 1073 } 1074 1075 $this->total_cost = price2num($this->total_cost, 'MT'); 1076 if ($this->qty) { 1077 $this->unit_cost = price2num($this->total_cost / $this->qty, 'MU'); 1078 } 1079 } 1080 } 1081} 1082 1083 1084/** 1085 * Class for BOMLine 1086 */ 1087class BOMLine extends CommonObjectLine 1088{ 1089 /** 1090 * @var string ID to identify managed object 1091 */ 1092 public $element = 'bomline'; 1093 1094 /** 1095 * @var string Name of table without prefix where object is stored 1096 */ 1097 public $table_element = 'bom_bomline'; 1098 1099 /** 1100 * @var int Does bomline support multicompany module ? 0=No test on entity, 1=Test with field entity, 2=Test with link by societe 1101 */ 1102 public $ismultientitymanaged = 0; 1103 1104 /** 1105 * @var int Does bomline support extrafields ? 0=No, 1=Yes 1106 */ 1107 public $isextrafieldmanaged = 1; 1108 1109 /** 1110 * @var string String with name of icon for bomline. Must be the part after the 'object_' into object_bomline.png 1111 */ 1112 public $picto = 'bomline'; 1113 1114 1115 /** 1116 * 'type' if the field format. 1117 * 'label' the translation key. 1118 * 'enabled' is a condition when the field must be managed. 1119 * 'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only. Using a negative value means field is not shown by default on list but can be selected for viewing) 1120 * 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0). 1121 * 'default' is a default value for creation (can still be replaced by the global setup of default values) 1122 * 'index' if we want an index in database. 1123 * 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...). 1124 * 'position' is the sort order of field. 1125 * 'searchall' is 1 if we want to search in this field when making a search from the quick search button. 1126 * 'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8). 1127 * 'css' is the CSS style to use on field. For example: 'maxwidth200' 1128 * 'help' is a string visible as a tooltip on field 1129 * 'comment' is not used. You can store here any text of your choice. It is not used by application. 1130 * 'showoncombobox' if value of the field must be visible into the label of the combobox that list record 1131 * 'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel") 1132 */ 1133 1134 // BEGIN MODULEBUILDER PROPERTIES 1135 /** 1136 * @var array Array with all fields and their property. Do not use it as a static var. It may be modified by constructor. 1137 */ 1138 public $fields = array( 1139 'rowid' => array('type'=>'integer', 'label'=>'LineID', 'enabled'=>1, 'visible'=>-1, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>"Id",), 1140 'fk_bom' => array('type'=>'integer:BillOfMaterials:societe/class/bom.class.php', 'label'=>'BillOfMaterials', 'enabled'=>1, 'visible'=>1, 'position'=>10, 'notnull'=>1, 'index'=>1,), 1141 'fk_product' => array('type'=>'integer:Product:product/class/product.class.php', 'label'=>'Product', 'enabled'=>1, 'visible'=>1, 'position'=>20, 'notnull'=>1, 'index'=>1,), 1142 'description' => array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>-1, 'position'=>60, 'notnull'=>-1,), 1143 'qty' => array('type'=>'double(24,8)', 'label'=>'Quantity', 'enabled'=>1, 'visible'=>1, 'position'=>100, 'notnull'=>1, 'isameasure'=>'1',), 1144 'qty_frozen' => array('type'=>'smallint', 'label'=>'QuantityFrozen', 'enabled'=>1, 'visible'=>1, 'default'=>0, 'position'=>105, 'css'=>'maxwidth50imp', 'help'=>'QuantityConsumedInvariable'), 1145 'disable_stock_change' => array('type'=>'smallint', 'label'=>'DisableStockChange', 'enabled'=>1, 'visible'=>1, 'default'=>0, 'position'=>108, 'css'=>'maxwidth50imp', 'help'=>'DisableStockChangeHelp'), 1146 'efficiency' => array('type'=>'double(24,8)', 'label'=>'ManufacturingEfficiency', 'enabled'=>1, 'visible'=>0, 'default'=>1, 'position'=>110, 'notnull'=>1, 'css'=>'maxwidth50imp', 'help'=>'ValueOfEfficiencyConsumedMeans'), 1147 'position' => array('type'=>'integer', 'label'=>'Rank', 'enabled'=>1, 'visible'=>0, 'default'=>0, 'position'=>200, 'notnull'=>1,), 1148 'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>1000, 'notnull'=>-1,), 1149 ); 1150 1151 /** 1152 * @var int rowid 1153 */ 1154 public $rowid; 1155 1156 /** 1157 * @var int fk_bom 1158 */ 1159 public $fk_bom; 1160 1161 /** 1162 * @var int Id of product 1163 */ 1164 public $fk_product; 1165 1166 /** 1167 * @var string description 1168 */ 1169 public $description; 1170 public $qty; 1171 1172 /** 1173 * @var int qty frozen 1174 */ 1175 public $qty_frozen; 1176 public $disable_stock_change; 1177 public $efficiency; 1178 1179 /** 1180 * @var int position of line 1181 */ 1182 public $position; 1183 1184 /** 1185 * @var string import key 1186 */ 1187 public $import_key; 1188 // END MODULEBUILDER PROPERTIES 1189 1190 /** 1191 * @var int Calculated cost for the BOM line 1192 */ 1193 public $total_cost = 0; 1194 1195 /** 1196 * @var int Line unit cost based on product cost price or pmp 1197 */ 1198 public $unit_cost = 0; 1199 1200 1201 /** 1202 * Constructor 1203 * 1204 * @param DoliDb $db Database handler 1205 */ 1206 public function __construct(DoliDB $db) 1207 { 1208 global $conf, $langs; 1209 1210 $this->db = $db; 1211 1212 if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && isset($this->fields['rowid'])) { 1213 $this->fields['rowid']['visible'] = 0; 1214 } 1215 if (empty($conf->multicompany->enabled) && isset($this->fields['entity'])) { 1216 $this->fields['entity']['enabled'] = 0; 1217 } 1218 1219 // Unset fields that are disabled 1220 foreach ($this->fields as $key => $val) { 1221 if (isset($val['enabled']) && empty($val['enabled'])) { 1222 unset($this->fields[$key]); 1223 } 1224 } 1225 1226 // Translate some data of arrayofkeyval 1227 foreach ($this->fields as $key => $val) { 1228 if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) { 1229 foreach ($val['arrayofkeyval'] as $key2 => $val2) { 1230 $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2); 1231 } 1232 } 1233 } 1234 } 1235 1236 /** 1237 * Create object into database 1238 * 1239 * @param User $user User that creates 1240 * @param bool $notrigger false=launch triggers after, true=disable triggers 1241 * @return int <0 if KO, Id of created object if OK 1242 */ 1243 public function create(User $user, $notrigger = false) 1244 { 1245 if ($this->efficiency < 0 || $this->efficiency > 1) { 1246 $this->efficiency = 1; 1247 } 1248 1249 return $this->createCommon($user, $notrigger); 1250 } 1251 1252 /** 1253 * Load object in memory from the database 1254 * 1255 * @param int $id Id object 1256 * @param string $ref Ref 1257 * @return int <0 if KO, 0 if not found, >0 if OK 1258 */ 1259 public function fetch($id, $ref = null) 1260 { 1261 $result = $this->fetchCommon($id, $ref); 1262 //if ($result > 0 && ! empty($this->table_element_line)) $this->fetchLines(); 1263 return $result; 1264 } 1265 1266 /** 1267 * Load list of objects in memory from the database. 1268 * 1269 * @param string $sortorder Sort Order 1270 * @param string $sortfield Sort field 1271 * @param int $limit limit 1272 * @param int $offset Offset 1273 * @param array $filter Filter array. Example array('field'=>'valueforlike', 'customurl'=>...) 1274 * @param string $filtermode Filter mode (AND or OR) 1275 * @return array|int int <0 if KO, array of pages if OK 1276 */ 1277 public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND') 1278 { 1279 global $conf; 1280 1281 dol_syslog(__METHOD__, LOG_DEBUG); 1282 1283 $records = array(); 1284 1285 $sql = 'SELECT '; 1286 $sql .= $this->getFieldList(); 1287 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t'; 1288 if ($this->ismultientitymanaged) { 1289 $sql .= ' WHERE t.entity IN ('.getEntity($this->table_element).')'; 1290 } else { 1291 $sql .= ' WHERE 1 = 1'; 1292 } 1293 // Manage filter 1294 $sqlwhere = array(); 1295 if (count($filter) > 0) { 1296 foreach ($filter as $key => $value) { 1297 if ($key == 't.rowid') { 1298 $sqlwhere[] = $key.'='.$value; 1299 } elseif (strpos($key, 'date') !== false) { 1300 $sqlwhere[] = $key.' = \''.$this->db->idate($value).'\''; 1301 } elseif ($key == 'customsql') { 1302 $sqlwhere[] = $value; 1303 } else { 1304 $sqlwhere[] = $key.' LIKE \'%'.$this->db->escape($value).'%\''; 1305 } 1306 } 1307 } 1308 if (count($sqlwhere) > 0) { 1309 $sql .= ' AND ('.implode(' '.$filtermode.' ', $sqlwhere).')'; 1310 } 1311 1312 if (!empty($sortfield)) { 1313 $sql .= $this->db->order($sortfield, $sortorder); 1314 } 1315 if (!empty($limit)) { 1316 $sql .= ' '.$this->db->plimit($limit, $offset); 1317 } 1318 1319 $resql = $this->db->query($sql); 1320 if ($resql) { 1321 $num = $this->db->num_rows($resql); 1322 1323 while ($obj = $this->db->fetch_object($resql)) { 1324 $record = new self($this->db); 1325 $record->setVarsFromFetchObj($obj); 1326 1327 $records[$record->id] = $record; 1328 } 1329 $this->db->free($resql); 1330 1331 return $records; 1332 } else { 1333 $this->errors[] = 'Error '.$this->db->lasterror(); 1334 dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR); 1335 1336 return -1; 1337 } 1338 } 1339 1340 /** 1341 * Update object into database 1342 * 1343 * @param User $user User that modifies 1344 * @param bool $notrigger false=launch triggers after, true=disable triggers 1345 * @return int <0 if KO, >0 if OK 1346 */ 1347 public function update(User $user, $notrigger = false) 1348 { 1349 if ($this->efficiency < 0 || $this->efficiency > 1) { 1350 $this->efficiency = 1; 1351 } 1352 1353 return $this->updateCommon($user, $notrigger); 1354 } 1355 1356 /** 1357 * Delete object in database 1358 * 1359 * @param User $user User that deletes 1360 * @param bool $notrigger false=launch triggers after, true=disable triggers 1361 * @return int <0 if KO, >0 if OK 1362 */ 1363 public function delete(User $user, $notrigger = false) 1364 { 1365 return $this->deleteCommon($user, $notrigger); 1366 //return $this->deleteCommon($user, $notrigger, 1); 1367 } 1368 1369 /** 1370 * Return a link to the object card (with optionaly the picto) 1371 * 1372 * @param int $withpicto Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto) 1373 * @param string $option On what the link point to ('nolink', ...) 1374 * @param int $notooltip 1=Disable tooltip 1375 * @param string $morecss Add more css on link 1376 * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking 1377 * @return string String with URL 1378 */ 1379 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1) 1380 { 1381 global $db, $conf, $langs, $hookmanager; 1382 1383 if (!empty($conf->dol_no_mouse_hover)) { 1384 $notooltip = 1; // Force disable tooltips 1385 } 1386 1387 $result = ''; 1388 1389 $label = '<u>'.$langs->trans("BillOfMaterialsLine").'</u>'; 1390 $label .= '<br>'; 1391 $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref; 1392 1393 $url = dol_buildpath('/bom/bomline_card.php', 1).'?id='.$this->id; 1394 1395 if ($option != 'nolink') { 1396 // Add param to save lastsearch_values or not 1397 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0); 1398 if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) { 1399 $add_save_lastsearch_values = 1; 1400 } 1401 if ($add_save_lastsearch_values) { 1402 $url .= '&save_lastsearch_values=1'; 1403 } 1404 } 1405 1406 $linkclose = ''; 1407 if (empty($notooltip)) { 1408 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) { 1409 $label = $langs->trans("ShowBillOfMaterialsLine"); 1410 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"'; 1411 } 1412 $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"'; 1413 $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"'; 1414 1415 /* 1416 $hookmanager->initHooks(array('bomlinedao')); 1417 $parameters=array('id'=>$this->id); 1418 $reshook=$hookmanager->executeHooks('getnomurltooltip',$parameters,$this,$action); // Note that $action and $object may have been modified by some hooks 1419 if ($reshook > 0) $linkclose = $hookmanager->resPrint; 1420 */ 1421 } else { 1422 $linkclose = ($morecss ? ' class="'.$morecss.'"' : ''); 1423 } 1424 1425 $linkstart = '<a href="'.$url.'"'; 1426 $linkstart .= $linkclose.'>'; 1427 $linkend = '</a>'; 1428 1429 $result .= $linkstart; 1430 if ($withpicto) { 1431 $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); 1432 } 1433 if ($withpicto != 2) { 1434 $result .= $this->ref; 1435 } 1436 $result .= $linkend; 1437 //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : ''); 1438 1439 global $action, $hookmanager; 1440 $hookmanager->initHooks(array('bomlinedao')); 1441 $parameters = array('id'=>$this->id, 'getnomurl'=>$result); 1442 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks 1443 if ($reshook > 0) { 1444 $result = $hookmanager->resPrint; 1445 } else { 1446 $result .= $hookmanager->resPrint; 1447 } 1448 1449 return $result; 1450 } 1451 1452 /** 1453 * Return label of the status 1454 * 1455 * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto 1456 * @return string Label of status 1457 */ 1458 public function getLibStatut($mode = 0) 1459 { 1460 return $this->LibStatut($this->status, $mode); 1461 } 1462 1463 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 1464 /** 1465 * Return the status 1466 * 1467 * @param int $status Id status 1468 * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto 1469 * @return string Label of status 1470 */ 1471 public function LibStatut($status, $mode = 0) 1472 { 1473 // phpcs:enable 1474 return ''; 1475 } 1476 1477 /** 1478 * Load the info information in the object 1479 * 1480 * @param int $id Id of object 1481 * @return void 1482 */ 1483 public function info($id) 1484 { 1485 $sql = 'SELECT rowid, date_creation as datec, tms as datem,'; 1486 $sql .= ' fk_user_creat, fk_user_modif'; 1487 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t'; 1488 $sql .= ' WHERE t.rowid = '.((int) $id); 1489 $result = $this->db->query($sql); 1490 if ($result) { 1491 if ($this->db->num_rows($result)) { 1492 $obj = $this->db->fetch_object($result); 1493 $this->id = $obj->rowid; 1494 if ($obj->fk_user_author) { 1495 $cuser = new User($this->db); 1496 $cuser->fetch($obj->fk_user_author); 1497 $this->user_creation = $cuser; 1498 } 1499 1500 if ($obj->fk_user_valid) { 1501 $vuser = new User($this->db); 1502 $vuser->fetch($obj->fk_user_valid); 1503 $this->user_validation = $vuser; 1504 } 1505 1506 if ($obj->fk_user_cloture) { 1507 $cluser = new User($this->db); 1508 $cluser->fetch($obj->fk_user_cloture); 1509 $this->user_cloture = $cluser; 1510 } 1511 1512 $this->date_creation = $this->db->jdate($obj->datec); 1513 $this->date_modification = $this->db->jdate($obj->datem); 1514 $this->date_validation = $this->db->jdate($obj->datev); 1515 } 1516 1517 $this->db->free($result); 1518 } else { 1519 dol_print_error($this->db); 1520 } 1521 } 1522 1523 /** 1524 * Initialise object with example values 1525 * Id must be 0 if object instance is a specimen 1526 * 1527 * @return void 1528 */ 1529 public function initAsSpecimen() 1530 { 1531 $this->initAsSpecimenCommon(); 1532 } 1533} 1534