Passed
Push — EXTRACT_CLASSES ( 231cec...0382f2 )
by Rafael
65:54 queued 05:18
created

BOM::getLibStatut()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* Copyright (C) 2019       Laurent Destailleur         <[email protected]>
4
 * Copyright (C) 2023	    Benjamin Falière	        <[email protected]>
5
 * Copyright (C) 2023	    Charlene Benke		        <[email protected]>
6
 * Copyright (C) 2024       Frédéric France             <[email protected]>
7
 * Copyright (C) 2024		MDW							<[email protected]>
8
 * Copyright (C) 2024       Rafael San José             <[email protected]>
9
 *
10
 * This program is free software; you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation; either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23
24
namespace Dolibarr\Code\Bom\Classes;
25
26
/**
27
 * \file        htdocs/bom/class/bom.class.php
28
 * \ingroup     bom
29
 * \brief       This file is a CRUD class file for BOM (Create/Read/Update/Delete)
30
 */
31
32
// Put here all includes required by your class file
33
use Dolibarr\Core\Base\CommonObject;
34
use Dolibarr\Core\Base\CommonObjectLine;
35
require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
36
37
if (isModEnabled('workstation')) {
38
    require_once constant('DOL_DOCUMENT_ROOT') . '/workstation/class/workstation.class.php';
39
}
40
41
//require_once constant('DOL_DOCUMENT_ROOT') . '/societe/class/societe.class.php';
42
//require_once constant('DOL_DOCUMENT_ROOT') . '/product/class/product.class.php';
43
44
45
/**
46
 * Class for BOM
47
 */
48
class BOM extends CommonObject
49
{
50
    /**
51
     * @var string ID of module.
52
     */
53
    public $module = 'bom';
54
55
    /**
56
     * @var string ID to identify managed object
57
     */
58
    public $element = 'bom';
59
60
    /**
61
     * @var string Name of table without prefix where object is stored
62
     */
63
    public $table_element = 'bom_bom';
64
65
    /**
66
     * @var string String with name of icon for bom. Must be the part after the 'object_' into object_bom.png
67
     */
68
    public $picto = 'bom';
69
70
    /**
71
     * @var Product Object product of the BOM
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Bom\Classes\Product was not found. Did you mean Product? If so, make sure to prefix the type with \.
Loading history...
72
     */
73
    public $product;
74
75
    const STATUS_DRAFT = 0;
76
    const STATUS_VALIDATED = 1;
77
    const STATUS_CANCELED = 9;
78
79
80
    /**
81
     *  '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')
82
     *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
83
     *  'label' the translation key.
84
     *  'picto' is code of a picto to show before value in forms
85
     *  'enabled' is a condition when the field must be managed (Example: 1 or 'getDolGlobalString("MY_SETUP_PARAM")'
86
     *  'position' is the sort order of field.
87
     *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
88
     *  '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)
89
     *  'noteditable' says if field is not editable (1 or 0)
90
     *  '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.
91
     *  'index' if we want an index in database.
92
     *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommended to name the field fk_...).
93
     *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
94
     *  '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).
95
     *  '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: 'css'=>'minwidth300 maxwidth500 widthcentpercentminusx', 'cssview'=>'wordbreak', 'csslist'=>'tdoverflowmax200'
96
     *  'help' is a 'TranslationString' to use to show a tooltip on field. You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
97
     *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
98
     *  '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.
99
     *  'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
100
     *  'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
101
     *  'comment' is not used. You can store here any text of your choice. It is not used by application.
102
     *
103
     *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
104
     */
105
106
    // BEGIN MODULEBUILDER PROPERTIES
107
    /**
108
     * @var array<string,array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string}>  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string,array{type:...ring>,comment?:string}> at position 16 could not be parsed: Expected '}' at position 16, but found 'int'.
Loading history...
109
     */
110
    public $fields = array(
111
        'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -2, 'position' => 1, 'notnull' => 1, 'index' => 1, 'comment' => "Id",),
112
        'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'default' => 1, 'index' => 1, 'position' => 5),
113
        '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'),
114
        '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'),
115
        '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'),
116
        //'bomtype' => array('type'=>'integer', 'label'=>'Type', 'enabled'=>1, 'visible'=>-1, 'position'=>32, 'notnull'=>1, 'default'=>'0', 'arrayofkeyval'=>array(0=>'Manufacturing')),
117
        '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'),
118
        'description' => array('type' => 'text', 'label' => 'Description', 'enabled' => 1, 'visible' => -1, 'position' => 60, 'notnull' => -1,),
119
        'qty' => array('type' => 'real', 'label' => 'Quantity', 'enabled' => 1, 'visible' => 1, 'default' => 1, 'position' => 55, 'notnull' => 1, 'isameasure' => 1, 'css' => 'maxwidth50imp right'),
120
        //'efficiency' => array('type'=>'real', 'label'=>'ManufacturingEfficiency', 'enabled'=>1, 'visible'=>-1, 'default'=>1, 'position'=>100, 'notnull'=>0, 'css'=>'maxwidth50imp', 'help'=>'ValueOfMeansLossForProductProduced'),
121
        'duration' => array('type' => 'duration', 'label' => 'EstimatedDuration', 'enabled' => 1, 'visible' => -1, 'position' => 101, 'notnull' => -1, 'css' => 'maxwidth50imp', 'help' => 'EstimatedDurationDesc'),
122
        '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'),
123
        'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => -2, 'position' => 161, 'notnull' => -1,),
124
        'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => -2, 'position' => 162, 'notnull' => -1,),
125
        'date_creation' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'position' => 300, 'notnull' => 1,),
126
        'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -2, 'position' => 501, 'notnull' => 1,),
127
        'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -2, 'position' => 502, 'notnull' => 0,),
128
        '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'),
129
        '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'),
130
        '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'),
131
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 1000, 'notnull' => -1,),
132
        'model_pdf' => array('type' => 'varchar(255)', 'label' => 'Model pdf', 'enabled' => 1, 'visible' => 0, 'position' => 1010),
133
        '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')),
134
    );
135
136
    /**
137
     * @var int rowid
138
     */
139
    public $rowid;
140
141
    /**
142
     * @var string ref
143
     */
144
    public $ref;
145
146
    /**
147
     * @var string label
148
     */
149
    public $label;
150
151
    /**
152
     * @var int bomtype
153
     */
154
    public $bomtype;
155
156
    /**
157
     * @var string description
158
     */
159
    public $description;
160
161
    /**
162
     * @var integer|string date_creation
163
     */
164
    public $date_creation;
165
166
    /**
167
     * @var integer|string date_valid
168
     */
169
    public $date_valid;
170
171
    /**
172
     * @var int Id User creator
173
     */
174
    public $fk_user_creat;
175
176
    /**
177
     * @var int Id User modifying
178
     */
179
    public $fk_user_modif;
180
181
    /**
182
     * @var int Id User modifying
183
     */
184
    public $fk_user_valid;
185
186
    /**
187
     * @var int Id User modifying
188
     */
189
    public $fk_warehouse;
190
191
    /**
192
     * @var string import key
193
     */
194
    public $import_key;
195
196
    /**
197
     * @var int status
198
     */
199
    public $status;
200
201
    /**
202
     * @var int product Id
203
     */
204
    public $fk_product;
205
    public $qty;
206
    public $duration;
207
    public $efficiency;
208
    // END MODULEBUILDER PROPERTIES
209
210
211
    // If this object has a subtable with lines
212
213
    /**
214
     * @var string    Name of subtable line
215
     */
216
    public $table_element_line = 'bom_bomline';
217
218
    /**
219
     * @var string    Fieldname with ID of parent key if this field has a parent
220
     */
221
    public $fk_element = 'fk_bom';
222
223
    /**
224
     * @var string    Name of subtable class that manage subtable lines
225
     */
226
    public $class_element_line = 'BOMLine';
227
228
    // /**
229
    //  * @var array    List of child tables. To test if we can delete object.
230
    //  */
231
    // protected $childtables=array();
232
233
    /**
234
     * @var string[]    List of child tables. To know object to delete on cascade.
235
     */
236
    protected $childtablesoncascade = array('bom_bomline');
237
238
    /**
239
     * @var BOMLine[]     Array of subtable lines
240
     */
241
    public $lines = array();
242
243
    /**
244
     * @var float       Calculated cost for the BOM
245
     */
246
    public $total_cost = 0;
247
248
    /**
249
     * @var float       Calculated cost for 1 unit of the product in BOM
250
     */
251
    public $unit_cost = 0;
252
253
254
    /**
255
     * Constructor
256
     *
257
     * @param DoliDB $db Database handler
258
     */
259
    public function __construct(DoliDB $db)
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Bom\Classes\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
260
    {
261
        global $conf, $langs;
262
263
        $this->db = $db;
264
265
        $this->ismultientitymanaged = 1;
266
        $this->isextrafieldmanaged = 1;
267
268
        if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid'])) {
269
            $this->fields['rowid']['visible'] = 0;
270
        }
271
        if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
272
            $this->fields['entity']['enabled'] = 0;
273
        }
274
275
        // Unset fields that are disabled
276
        foreach ($this->fields as $key => $val) {
277
            if (isset($val['enabled']) && empty($val['enabled'])) {
278
                unset($this->fields[$key]);
279
            }
280
        }
281
282
        // Translate some data of arrayofkeyval
283
        foreach ($this->fields as $key => $val) {
284
            if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
285
                foreach ($val['arrayofkeyval'] as $key2 => $val2) {
286
                    $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
287
                }
288
            }
289
        }
290
    }
291
292
    /**
293
     * Create object into database
294
     *
295
     * @param  User $user      User that creates
296
     * @param  int  $notrigger false=launch triggers after, true=disable triggers
297
     * @return int             Return integer <0 if KO, Id of created object if OK
298
     */
299
    public function create(User $user, $notrigger = 1)
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Bom\Classes\User was not found. Did you mean User? If so, make sure to prefix the type with \.
Loading history...
300
    {
301
        if ($this->efficiency <= 0 || $this->efficiency > 1) {
302
            $this->efficiency = 1;
303
        }
304
305
        return $this->createCommon($user, $notrigger);
306
    }
307
308
    /**
309
     * Clone an object into another one
310
     *
311
     * @param   User    $user       User that creates
312
     * @param   int     $fromid     Id of object to clone
313
     * @return  mixed               New object created, <0 if KO
314
     */
315
    public function createFromClone(User $user, $fromid)
316
    {
317
        global $langs, $hookmanager, $extrafields;
318
        $error = 0;
319
320
        dol_syslog(__METHOD__, LOG_DEBUG);
321
322
        $object = new self($this->db);
323
324
        $this->db->begin();
325
326
        // Load source object
327
        $result = $object->fetchCommon($fromid);
328
        if ($result > 0 && !empty($object->table_element_line)) {
329
            $object->fetchLines();
330
        }
331
332
        // Get lines so they will be clone
333
        //foreach ($object->lines as $line)
334
        //  $line->fetch_optionals();
335
336
        // Reset some properties
337
        unset($object->id);
338
        unset($object->fk_user_creat);
339
        unset($object->import_key);
340
341
        // Clear fields
342
        $object->ref = empty($this->fields['ref']['default']) ? $langs->trans("copy_of_") . $object->ref : $this->fields['ref']['default'];
343
        $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf") . " " . $object->label : $this->fields['label']['default'];
344
        $object->status = self::STATUS_DRAFT;
345
        // ...
346
        // Clear extrafields that are unique
347
        if (is_array($object->array_options) && count($object->array_options) > 0) {
348
            $extrafields->fetch_name_optionals_label($object->table_element);
349
            foreach ($object->array_options as $key => $option) {
350
                $shortkey = preg_replace('/options_/', '', $key);
351
                if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) {
352
                    //var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
353
                    unset($object->array_options[$key]);
354
                }
355
            }
356
        }
357
358
        // Create clone
359
        $object->context['createfromclone'] = 'createfromclone';
360
        $result = $object->createCommon($user);
361
        if ($result < 0) {
362
            $error++;
363
            $this->error = $object->error;
364
            $this->errors = $object->errors;
365
        }
366
367
        if (!$error) {
368
            // copy internal contacts
369
            if ($this->copy_linked_contact($object, 'internal') < 0) {
370
                $error++;
371
            }
372
        }
373
374
        if (!$error) {
375
            // copy external contacts if same company
376
            if (property_exists($this, 'socid') && $this->socid == $object->socid) {
377
                if ($this->copy_linked_contact($object, 'external') < 0) {
378
                    $error++;
379
                }
380
            }
381
        }
382
383
        // If there is lines, create lines too
384
385
386
387
        unset($object->context['createfromclone']);
388
389
        // End
390
        if (!$error) {
391
            $this->db->commit();
392
            return $object;
393
        } else {
394
            $this->db->rollback();
395
            return -1;
396
        }
397
    }
398
399
    /**
400
     * Load object in memory from the database
401
     *
402
     * @param int    $id   Id object
403
     * @param string $ref  Ref
404
     * @return int         Return integer <0 if KO, 0 if not found, >0 if OK
405
     */
406
    public function fetch($id, $ref = null)
407
    {
408
        $result = $this->fetchCommon($id, $ref);
409
410
        if ($result > 0 && !empty($this->table_element_line)) {
411
            $this->fetchLines();
412
        }
413
        //$this->calculateCosts();      // This consume a high number of subrequests. Do not call it into fetch but when you need it.
414
415
        return $result;
416
    }
417
418
    /**
419
     * Load object lines in memory from the database
420
     *
421
     * @return int         Return integer <0 if KO, 0 if not found, >0 if OK
422
     */
423
    public function fetchLines()
424
    {
425
        $this->lines = array();
426
427
        $result = $this->fetchLinesCommon();
428
        return $result;
429
    }
430
431
    /**
432
     * Load object lines in memory from the database by type of product
433
     *
434
     *  @param int    $typeproduct   0 type product, 1 type service
435
436
     * @return int         Return integer <0 if KO, 0 if not found, >0 if OK
437
     */
438
    public function fetchLinesbytypeproduct($typeproduct = 0)
439
    {
440
        $this->lines = array();
441
442
        $objectlineclassname = get_class($this) . 'Line';
443
        if (!class_exists($objectlineclassname)) {
444
            $this->error = 'Error, class ' . $objectlineclassname . ' not found during call of fetchLinesCommon';
445
            return -1;
446
        }
447
448
        $objectline = new $objectlineclassname($this->db);
449
450
        $sql = "SELECT " . $objectline->getFieldList('l');
451
        $sql .= " FROM " . $this->db->prefix() . $objectline->table_element . " as l";
452
        $sql .= " LEFT JOIN " . $this->db->prefix() . "product as p ON p.rowid = l.fk_product";
453
        $sql .= " WHERE l.fk_" . $this->db->escape($this->element) . " = " . ((int) $this->id);
454
        $sql .= " AND p.fk_product_type = " . ((int) $typeproduct);
455
        if (isset($objectline->fields['position'])) {
456
            $sql .= $this->db->order('position', 'ASC');
457
        }
458
459
        $resql = $this->db->query($sql);
460
        if ($resql) {
461
            $num_rows = $this->db->num_rows($resql);
462
            $i = 0;
463
            while ($i < $num_rows) {
464
                $obj = $this->db->fetch_object($resql);
465
                if ($obj) {
466
                    $newline = new $objectlineclassname($this->db);
467
                    $newline->setVarsFromFetchObj($obj);
468
469
                    $this->lines[] = $newline;
470
                }
471
                $i++;
472
            }
473
474
            return $num_rows;
475
        } else {
476
            $this->error = $this->db->lasterror();
477
            $this->errors[] = $this->error;
478
            return -1;
479
        }
480
    }
481
482
483
    /**
484
     * Load list of objects in memory from the database.
485
     *
486
     * @param  string           $sortorder    Sort Order
487
     * @param  string           $sortfield    Sort field
488
     * @param  int              $limit        Limit
489
     * @param  int              $offset       Offset
490
     * @param  string           $filter       Filter USF
491
     * @param  string           $filtermode   Filter mode (AND or OR)
492
     * @return array|int                      int <0 if KO, array of pages if OK
493
     */
494
    public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, $filter = '', $filtermode = 'AND')
495
    {
496
        dol_syslog(__METHOD__, LOG_DEBUG);
497
498
        $records = array();
499
500
        $sql = 'SELECT ';
501
        $sql .= $this->getFieldList();
502
        $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->table_element . ' as t';
503
        if ($this->ismultientitymanaged) {
504
            $sql .= ' WHERE t.entity IN (' . getEntity($this->element) . ')';
505
        } else {
506
            $sql .= ' WHERE 1 = 1';
507
        }
508
509
        // Manage filter
510
        $errormessage = '';
511
        $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
512
        if ($errormessage) {
513
            $this->errors[] = $errormessage;
514
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
515
            return -1;
516
        }
517
518
        if (!empty($sortfield)) {
519
            $sql .= $this->db->order($sortfield, $sortorder);
520
        }
521
        if (!empty($limit)) {
522
            $sql .= $this->db->plimit($limit, $offset);
523
        }
524
525
        $resql = $this->db->query($sql);
526
        if ($resql) {
527
            $num = $this->db->num_rows($resql);
528
529
            while ($obj = $this->db->fetch_object($resql)) {
530
                $record = new self($this->db);
531
                $record->setVarsFromFetchObj($obj);
532
533
                $records[$record->id] = $record;
534
            }
535
            $this->db->free($resql);
536
537
            return $records;
538
        } else {
539
            $this->errors[] = 'Error ' . $this->db->lasterror();
540
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
541
542
            return -1;
543
        }
544
    }
545
546
    /**
547
     * Update object into database
548
     *
549
     * @param  User $user      User that modifies
550
     * @param  int  $notrigger 0=launch triggers after, 1=disable triggers
551
     * @return int             Return integer <0 if KO, >0 if OK
552
     */
553
    public function update(User $user, $notrigger = 1)
554
    {
555
        if ($this->efficiency <= 0 || $this->efficiency > 1) {
556
            $this->efficiency = 1;
557
        }
558
559
        return $this->updateCommon($user, $notrigger);
560
    }
561
562
    /**
563
     * Delete object in database
564
     *
565
     * @param User $user        User that deletes
566
     * @param int   $notrigger  0=launch triggers after, 1=disable triggers
567
     * @return int              Return integer <0 if KO, >0 if OK
568
     */
569
    public function delete(User $user, $notrigger = 1)
570
    {
571
        return $this->deleteCommon($user, $notrigger);
572
        //return $this->deleteCommon($user, $notrigger, 1);
573
    }
574
575
    /**
576
     * Add an BOM line into database (linked to BOM)
577
     *
578
     * @param   int     $fk_product             Id of product
579
     * @param   float   $qty                    Quantity
580
     * @param   int<0,1> $qty_frozen            If the qty is Frozen
581
     * @param   int     $disable_stock_change   Disable stock change on using in MO
582
     * @param   float   $efficiency             Efficiency in MO
583
     * @param   int     $position               Position of BOM-Line in BOM-Lines
584
     * @param   int     $fk_bom_child           Id of BOM Child
585
     * @param   string  $import_key             Import Key
586
     * @param   int     $fk_unit                Unit
587
     * @param   array   $array_options          extrafields array
588
     * @param   int     $fk_default_workstation Default workstation
589
     * @return  int                             Return integer <0 if KO, Id of created object if OK
590
     */
591
    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)
592
    {
593
        global $mysoc, $conf, $langs, $user;
594
595
        $logtext = "::addLine bomid=$this->id, qty=$qty, fk_product=$fk_product, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
596
        $logtext .= ", fk_bom_child=$fk_bom_child, import_key=$import_key";
597
        dol_syslog(get_class($this) . $logtext, LOG_DEBUG);
598
599
        if ($this->statut == self::STATUS_DRAFT) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

599
        if (/** @scrutinizer ignore-deprecated */ $this->statut == self::STATUS_DRAFT) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
600
            include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
601
602
            // Clean parameters
603
            if (empty($qty)) {
604
                $qty = 0;
605
            }
606
            if (empty($qty_frozen)) {
607
                $qty_frozen = 0;
608
            }
609
            if (empty($disable_stock_change)) {
610
                $disable_stock_change = 0;
611
            }
612
            if (empty($efficiency)) {
613
                $efficiency = 1.0;
614
            }
615
            if (empty($fk_bom_child)) {
616
                $fk_bom_child = null;
617
            }
618
            if (empty($import_key)) {
619
                $import_key = null;
620
            }
621
            if (empty($position)) {
622
                $position = -1;
623
            }
624
625
            $qty = (float) price2num($qty);
626
            $efficiency = (float) price2num($efficiency);
627
            $position = (float) price2num($position);
628
629
            $this->db->begin();
630
631
            // Rank to use
632
            $rangMax = $this->line_max();
633
            $rankToUse = $position;
634
            if ($rankToUse <= 0 or $rankToUse > $rangMax) { // New line after existing lines
635
                $rankToUse = $rangMax + 1;
636
            } else { // New line between the existing lines
637
                foreach ($this->lines as $bl) {
638
                    if ($bl->position >= $rankToUse) {
639
                        $bl->position++;
640
                        $bl->update($user);
641
                    }
642
                }
643
            }
644
645
            // Insert line
646
            $line = new BOMLine($this->db);
647
648
            $line->context = $this->context;
649
650
            $line->fk_bom = $this->id;
651
            $line->fk_product = $fk_product;
652
            $line->qty = $qty;
653
            $line->qty_frozen = $qty_frozen;
654
            $line->disable_stock_change = $disable_stock_change;
655
            $line->efficiency = $efficiency;
656
            $line->fk_bom_child = $fk_bom_child;
657
            $line->import_key = $import_key;
658
            $line->position = $rankToUse;
659
            $line->fk_unit = $fk_unit;
660
            $line->fk_default_workstation = $fk_default_workstation;
661
662
            if (is_array($array_options) && count($array_options) > 0) {
663
                $line->array_options = $array_options;
664
            }
665
666
            $result = $line->create($user);
667
668
            if ($result > 0) {
669
                $this->calculateCosts();
670
                $this->db->commit();
671
                return $result;
672
            } else {
673
                $this->setErrorsFromObject($line);
674
                dol_syslog(get_class($this) . "::addLine error=" . $this->error, LOG_ERR);
675
                $this->db->rollback();
676
                return -2;
677
            }
678
        } else {
679
            dol_syslog(get_class($this) . "::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
680
            return -3;
681
        }
682
    }
683
684
    /**
685
     * Update an BOM line into database
686
     *
687
     * @param   int     $rowid                  Id of line to update
688
     * @param   float   $qty                    Quantity
689
     * @param   float   $qty_frozen             Frozen quantity
690
     * @param   int     $disable_stock_change   Disable stock change on using in MO
691
     * @param   float   $efficiency             Efficiency in MO
692
     * @param   int     $position               Position of BOM-Line in BOM-Lines
693
     * @param   string  $import_key             Import Key
694
     * @param   int     $fk_unit                Unit of line
695
     * @param   array   $array_options          extrafields array
696
     * @param   int     $fk_default_workstation Default workstation
697
     * @return  int                             Return integer <0 if KO, Id of updated BOM-Line if OK
698
     */
699
    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)
700
    {
701
        global $user;
702
703
        $logtext = "::updateLine bomid=$this->id, qty=$qty, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
704
        $logtext .= ", import_key=$import_key";
705
        dol_syslog(get_class($this) . $logtext, LOG_DEBUG);
706
707
        if ($this->statut == self::STATUS_DRAFT) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

707
        if (/** @scrutinizer ignore-deprecated */ $this->statut == self::STATUS_DRAFT) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
708
            include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
709
710
            // Clean parameters
711
            if (empty($qty)) {
712
                $qty = 0;
713
            }
714
            if (empty($qty_frozen)) {
715
                $qty_frozen = 0;
716
            }
717
            if (empty($disable_stock_change)) {
718
                $disable_stock_change = 0;
719
            }
720
            if (empty($efficiency)) {
721
                $efficiency = 1.0;
722
            }
723
            if (empty($import_key)) {
724
                $import_key = null;
725
            }
726
            if (empty($position)) {
727
                $position = -1;
728
            }
729
730
            $qty = (float) price2num($qty);
731
            $efficiency = (float) price2num($efficiency);
732
            $position = (float) price2num($position);
733
734
            $this->db->begin();
735
736
            //Fetch current line from the database and then clone the object and set it in $oldline property
737
            $line = new BOMLine($this->db);
738
            $line->fetch($rowid);
739
            $line->fetch_optionals();
740
741
            $staticLine = clone $line;
742
            $line->oldcopy = $staticLine;
743
            $line->context = $this->context;
744
745
            // Rank to use
746
            $rankToUse = (int) $position;
747
            if ($rankToUse != $line->oldcopy->position) { // check if position have a new value
748
                foreach ($this->lines as $bl) {
749
                    if ($bl->position >= $rankToUse and $bl->position < ($line->oldcopy->position + 1)) { // move rank up
750
                        $bl->position++;
751
                        $bl->update($user);
752
                    }
753
                    if ($bl->position <= $rankToUse and $bl->position > ($line->oldcopy->position)) { // move rank down
754
                        $bl->position--;
755
                        $bl->update($user);
756
                    }
757
                }
758
            }
759
760
761
            $line->fk_bom = $this->id;
762
            $line->qty = $qty;
763
            $line->qty_frozen = $qty_frozen;
764
            $line->disable_stock_change = $disable_stock_change;
765
            $line->efficiency = $efficiency;
766
            $line->import_key = $import_key;
767
            $line->position = $rankToUse;
768
769
770
            if (!empty($fk_unit)) {
771
                $line->fk_unit = $fk_unit;
772
            }
773
774
775
            if (is_array($array_options) && count($array_options) > 0) {
776
                // We replace values in this->line->array_options only for entries defined into $array_options
777
                foreach ($array_options as $key => $value) {
778
                    $line->array_options[$key] = $array_options[$key];
779
                }
780
            }
781
            if ($line->fk_default_workstation != $fk_default_workstation) {
782
                $line->fk_default_workstation = ($fk_default_workstation > 0 ? $fk_default_workstation : 0);
783
            }
784
785
            $result = $line->update($user);
786
787
            if ($result > 0) {
788
                $this->calculateCosts();
789
                $this->db->commit();
790
                return $result;
791
            } else {
792
                $this->setErrorsFromObject($line);
793
                dol_syslog(get_class($this) . "::addLine error=" . $this->error, LOG_ERR);
794
                $this->db->rollback();
795
                return -2;
796
            }
797
        } else {
798
            dol_syslog(get_class($this) . "::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
799
            return -3;
800
        }
801
    }
802
803
    /**
804
     *  Delete a line of object in database
805
     *
806
     *  @param  User    $user       User that delete
807
     *  @param  int     $idline     Id of line to delete
808
     *  @param  int     $notrigger  0=launch triggers after, 1=disable triggers
809
     *  @return int                 >0 if OK, <0 if KO
810
     */
811
    public function deleteLine(User $user, $idline, $notrigger = 0)
812
    {
813
        if ($this->status < 0) {
814
            $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
815
            return -2;
816
        }
817
818
        $this->db->begin();
819
820
        //Fetch current line from the database and then clone the object and set it in $oldline property
821
        $line = new BOMLine($this->db);
822
        $line->fetch($idline);
823
        $line->fetch_optionals();
824
825
        $staticLine = clone $line;
826
        $line->oldcopy = $staticLine;
827
        $line->context = $this->context;
828
829
        $result = $line->delete($user, $notrigger);
830
831
        //Positions (rank) reordering
832
        foreach ($this->lines as $bl) {
833
            if ($bl->position > ($line->oldcopy->position)) { // move rank down
834
                $bl->position--;
835
                $bl->update($user);
836
            }
837
        }
838
839
        if ($result > 0) {
840
            $this->calculateCosts();
841
            $this->db->commit();
842
            return $result;
843
        } else {
844
            $this->setErrorsFromObject($line);
845
            dol_syslog(get_class($this) . "::addLine error=" . $this->error, LOG_ERR);
846
            $this->db->rollback();
847
            return -2;
848
        }
849
    }
850
851
    /**
852
     *  Returns the reference to the following non used BOM depending on the active numbering module
853
     *  defined into BOM_ADDON
854
     *
855
     *  @param  Product     $prod   Object product
856
     *  @return string              BOM free reference
857
     */
858
    public function getNextNumRef($prod)
859
    {
860
        global $langs, $conf;
861
        $langs->load("mrp");
862
863
        if (getDolGlobalString('BOM_ADDON')) {
864
            $mybool = false;
865
866
            $file = getDolGlobalString('BOM_ADDON') . ".php";
867
            $classname = getDolGlobalString('BOM_ADDON');
868
869
            // Include file with class
870
            $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
871
            foreach ($dirmodels as $reldir) {
872
                $dir = dol_buildpath($reldir . "core/modules/bom/");
873
874
                // Load file with numbering class (if found)
875
                $mybool = ((bool) @include_once $dir . $file) || $mybool;
876
            }
877
878
            if ($mybool === false) {
879
                dol_print_error(null, "Failed to include file " . $file);
880
                return '';
881
            }
882
883
            $obj = new $classname();
884
            $numref = $obj->getNextValue($prod, $this);
885
886
            if ($numref != "") {
887
                return $numref;
888
            } else {
889
                $this->error = $obj->error;
890
                //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
891
                return "";
892
            }
893
        } else {
894
            print $langs->trans("Error") . " " . $langs->trans("Error_BOM_ADDON_NotDefined");
895
            return "";
896
        }
897
    }
898
899
    /**
900
     *  Validate bom
901
     *
902
     *  @param      User    $user           User making status change
903
     *  @param      int     $notrigger      1=Does not execute triggers, 0= execute triggers
904
     *  @return     int                     Return integer <=0 if OK, 0=Nothing done, >0 if KO
905
     */
906
    public function validate($user, $notrigger = 0)
907
    {
908
        global $conf;
909
910
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
911
912
        $error = 0;
913
914
        // Protection
915
        if ($this->status == self::STATUS_VALIDATED) {
916
            dol_syslog(get_class($this) . "::validate action abandoned: already validated", LOG_WARNING);
917
            return 0;
918
        }
919
920
        $now = dol_now();
921
922
        $this->db->begin();
923
924
        // Define new ref
925
        if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
926
            $this->fetch_product();
927
            $num = $this->getNextNumRef($this->product);
928
        } else {
929
            $num = $this->ref;
930
        }
931
        $this->newref = dol_sanitizeFileName($num);
932
933
        // Validate
934
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element;
935
        $sql .= " SET ref = '" . $this->db->escape($num) . "',";
936
        $sql .= " status = " . self::STATUS_VALIDATED . ",";
937
        $sql .= " date_valid='" . $this->db->idate($now) . "',";
938
        $sql .= " fk_user_valid = " . ((int) $user->id);
939
        $sql .= " WHERE rowid = " . ((int) $this->id);
940
941
        dol_syslog(get_class($this) . "::validate()", LOG_DEBUG);
942
        $resql = $this->db->query($sql);
943
        if (!$resql) {
944
            dol_print_error($this->db);
945
            $this->error = $this->db->lasterror();
946
            $error++;
947
        }
948
949
        if (!$error && !$notrigger) {
950
            // Call trigger
951
            $result = $this->call_trigger('BOM_VALIDATE', $user);
952
            if ($result < 0) {
953
                $error++;
954
            }
955
            // End call triggers
956
        }
957
958
        if (!$error) {
959
            $this->oldref = $this->ref;
960
961
            // Rename directory if dir was a temporary ref
962
            if (preg_match('/^[\(]?PROV/i', $this->ref)) {
963
                // Now we rename also files into index
964
                $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) . "'";
965
                $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'bom/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
966
                $resql = $this->db->query($sql);
967
                if (!$resql) {
968
                    $error++;
969
                    $this->error = $this->db->lasterror();
970
                }
971
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filepath = 'bom/" . $this->db->escape($this->newref) . "'";
972
                $sql .= " WHERE filepath = 'bom/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
973
                $resql = $this->db->query($sql);
974
                if (!$resql) {
975
                    $error++;
976
                    $this->error = $this->db->lasterror();
977
                }
978
979
                // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
980
                $oldref = dol_sanitizeFileName($this->ref);
981
                $newref = dol_sanitizeFileName($num);
982
                $dirsource = $conf->bom->dir_output . '/' . $oldref;
983
                $dirdest = $conf->bom->dir_output . '/' . $newref;
984
                if (!$error && file_exists($dirsource)) {
985
                    dol_syslog(get_class($this) . "::validate() rename dir " . $dirsource . " into " . $dirdest);
986
987
                    if (@rename($dirsource, $dirdest)) {
988
                        dol_syslog("Rename ok");
989
                        // Rename docs starting with $oldref with $newref
990
                        $listoffiles = dol_dir_list($conf->bom->dir_output . '/' . $newref, 'files', 1, '^' . preg_quote($oldref, '/'));
991
                        foreach ($listoffiles as $fileentry) {
992
                            $dirsource = $fileentry['name'];
993
                            $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
994
                            $dirsource = $fileentry['path'] . '/' . $dirsource;
995
                            $dirdest = $fileentry['path'] . '/' . $dirdest;
996
                            @rename($dirsource, $dirdest);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rename(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

996
                            /** @scrutinizer ignore-unhandled */ @rename($dirsource, $dirdest);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
997
                        }
998
                    }
999
                }
1000
            }
1001
        }
1002
1003
        // Set new ref and current status
1004
        if (!$error) {
1005
            $this->ref = $num;
1006
            $this->status = self::STATUS_VALIDATED;
1007
        }
1008
1009
        if (!$error) {
1010
            $this->db->commit();
1011
            return 1;
1012
        } else {
1013
            $this->db->rollback();
1014
            return -1;
1015
        }
1016
    }
1017
1018
    /**
1019
     *  Set draft status
1020
     *
1021
     *  @param  User    $user           Object user that modify
1022
     *  @param  int     $notrigger      1=Does not execute triggers, 0=Execute triggers
1023
     *  @return int                     Return integer <0 if KO, >0 if OK
1024
     */
1025
    public function setDraft($user, $notrigger = 0)
1026
    {
1027
        // Protection
1028
        if ($this->status <= self::STATUS_DRAFT) {
1029
            return 0;
1030
        }
1031
1032
        return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'BOM_UNVALIDATE');
1033
    }
1034
1035
    /**
1036
     *  Set cancel status
1037
     *
1038
     *  @param  User    $user           Object user that modify
1039
     *  @param  int     $notrigger      1=Does not execute triggers, 0=Execute triggers
1040
     *  @return int                     Return integer <0 if KO, 0=Nothing done, >0 if OK
1041
     */
1042
    public function cancel($user, $notrigger = 0)
1043
    {
1044
        // Protection
1045
        if ($this->status != self::STATUS_VALIDATED) {
1046
            return 0;
1047
        }
1048
1049
        return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'BOM_CLOSE');
1050
    }
1051
1052
    /**
1053
     *  Set cancel status
1054
     *
1055
     *  @param  User    $user           Object user that modify
1056
     *  @param  int     $notrigger      1=Does not execute triggers, 0=Execute triggers
1057
     *  @return int                     Return integer <0 if KO, 0=Nothing done, >0 if OK
1058
     */
1059
    public function reopen($user, $notrigger = 0)
1060
    {
1061
        // Protection
1062
        if ($this->status != self::STATUS_CANCELED) {
1063
            return 0;
1064
        }
1065
1066
        return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'BOM_REOPEN');
1067
    }
1068
1069
    /**
1070
     * getTooltipContentArray
1071
     * @param array $params params to construct tooltip data
1072
     * @since v18
1073
     * @return array
1074
     */
1075
    public function getTooltipContentArray($params)
1076
    {
1077
        global $conf, $langs, $user;
1078
1079
        $langs->loadLangs(['product', 'mrp']);
1080
1081
        $datas = [];
1082
1083
        if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1084
            return ['optimize' => $langs->trans("ShowBillOfMaterials")];
1085
        }
1086
        $picto = img_picto('', $this->picto) . ' <u class="paddingrightonly">' . $langs->trans("BillOfMaterials") . '</u>';
1087
        if (isset($this->status)) {
1088
            $picto .= ' ' . $this->getLibStatut(5);
1089
        }
1090
        $datas['picto'] = $picto;
1091
        $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
1092
        if (isset($this->label)) {
1093
            $datas['label'] = '<br><b>' . $langs->trans('Label') . ':</b> ' . $this->label;
1094
        }
1095
        if (!empty($this->fk_product) && $this->fk_product > 0) {
1096
            include_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
1097
            $product = new Product($this->db);
1098
            $resultFetch = $product->fetch($this->fk_product);
1099
            if ($resultFetch > 0) {
1100
                $datas['product'] = "<br><b>" . $langs->trans("Product") . '</b>: ' . $product->ref . ' - ' . $product->label;
1101
            }
1102
        }
1103
1104
        return $datas;
1105
    }
1106
1107
    /**
1108
     *  Return a link to the object card (with optionally the picto)
1109
     *
1110
     *  @param  int     $withpicto                  Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto)
1111
     *  @param  string  $option                     On what the link point to ('nolink', ...)
1112
     *  @param  int     $notooltip                  1=Disable tooltip
1113
     *  @param  string  $morecss                    Add more css on link
1114
     *  @param  int     $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1115
     *  @return string                              String with URL
1116
     */
1117
    public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1118
    {
1119
        global $db, $conf, $langs, $hookmanager;
1120
1121
        if (!empty($conf->dol_no_mouse_hover)) {
1122
            $notooltip = 1; // Force disable tooltips
1123
        }
1124
1125
        $result = '';
1126
        $params = [
1127
            'id' => $this->id,
1128
            'objecttype' => $this->element,
1129
            'option' => $option,
1130
        ];
1131
        $classfortooltip = 'classfortooltip';
1132
        $dataparams = '';
1133
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1134
            $classfortooltip = 'classforajaxtooltip';
1135
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
1136
            $label = '';
1137
        } else {
1138
            $label = implode($this->getTooltipContentArray($params));
1139
        }
1140
1141
        $url = constant('BASE_URL') . '/bom/bom_card.php?id=' . $this->id;
1142
1143
        if ($option != 'nolink') {
1144
            // Add param to save lastsearch_values or not
1145
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1146
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1147
                $add_save_lastsearch_values = 1;
1148
            }
1149
            if ($add_save_lastsearch_values) {
1150
                $url .= '&save_lastsearch_values=1';
1151
            }
1152
        }
1153
1154
        $linkclose = '';
1155
        if (empty($notooltip)) {
1156
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1157
                $label = $langs->trans("ShowBillOfMaterials");
1158
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
1159
            }
1160
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
1161
            $linkclose .= $dataparams . ' class="' . $classfortooltip . ($morecss ? ' ' . $morecss : '') . '"';
1162
        } else {
1163
            $linkclose = ($morecss ? ' class="' . $morecss . '"' : '');
1164
        }
1165
1166
        $linkstart = '<a href="' . $url . '"';
1167
        $linkstart .= $linkclose . '>';
1168
        $linkend = '</a>';
1169
1170
        $result .= $linkstart;
1171
        if ($withpicto) {
1172
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1173
        }
1174
        if ($withpicto != 2) {
1175
            $result .= $this->ref;
1176
        }
1177
        $result .= $linkend;
1178
        //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1179
1180
        global $action, $hookmanager;
1181
        $hookmanager->initHooks(array('bomdao'));
1182
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1183
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1184
        if ($reshook > 0) {
1185
            $result = $hookmanager->resPrint;
1186
        } else {
1187
            $result .= $hookmanager->resPrint;
1188
        }
1189
1190
        return $result;
1191
    }
1192
1193
    /**
1194
     *  Return label of the status
1195
     *
1196
     *  @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
1197
     *  @return string                 Label of status
1198
     */
1199
    public function getLibStatut($mode = 0)
1200
    {
1201
        return $this->LibStatut($this->status, $mode);
1202
    }
1203
1204
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1205
    /**
1206
     *  Return the status
1207
     *
1208
     *  @param  int     $status        Id status
1209
     *  @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
1210
     *  @return string                 Label of status
1211
     */
1212
    public function LibStatut($status, $mode = 0)
1213
    {
1214
		// phpcs:enable
1215
        if (empty($this->labelStatus)) {
1216
            global $langs;
1217
            //$langs->load("mrp");
1218
            $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1219
            $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Enabled');
1220
            $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Disabled');
1221
        }
1222
1223
        $statusType = 'status' . $status;
1224
        if ($status == self::STATUS_VALIDATED) {
1225
            $statusType = 'status4';
1226
        }
1227
        if ($status == self::STATUS_CANCELED) {
1228
            $statusType = 'status6';
1229
        }
1230
1231
        return dolGetStatus($this->labelStatus[$status], $this->labelStatus[$status], '', $statusType, $mode);
1232
    }
1233
1234
    /**
1235
     *  Load the info information in the object
1236
     *
1237
     *  @param  int     $id       Id of object
1238
     *  @return void
1239
     */
1240
    public function info($id)
1241
    {
1242
        $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1243
        $sql .= ' fk_user_creat, fk_user_modif';
1244
        $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->table_element . ' as t';
1245
        $sql .= ' WHERE t.rowid = ' . ((int) $id);
1246
        $result = $this->db->query($sql);
1247
        if ($result) {
1248
            if ($this->db->num_rows($result)) {
1249
                $obj = $this->db->fetch_object($result);
1250
1251
                $this->id = $obj->rowid;
1252
1253
                $this->user_creation_id = $obj->fk_user_creat;
1254
                $this->user_modification_id = $obj->fk_user_modif;
1255
                $this->date_creation     = $this->db->jdate($obj->datec);
1256
                $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1257
            }
1258
1259
            $this->db->free($result);
1260
        } else {
1261
            dol_print_error($this->db);
1262
        }
1263
    }
1264
1265
    /**
1266
     *  Create an array of lines
1267
     *
1268
     *  @return array|int       array of lines if OK, <0 if KO
1269
     */
1270
    public function getLinesArray()
1271
    {
1272
        $this->lines = array();
1273
1274
        $objectline = new BOMLine($this->db);
1275
        $result = $objectline->fetchAll('ASC', 'position', 0, 0, '(fk_bom:=:' . ((int) $this->id) . ')');
1276
1277
        if (is_numeric($result)) {
1278
            $this->error = $objectline->error;
1279
            $this->errors = $objectline->errors;
1280
            return $result;
1281
        } else {
1282
            $this->lines = $result;
1283
            return $this->lines;
1284
        }
1285
    }
1286
1287
    /**
1288
     *  Create a document onto disk according to template module.
1289
     *
1290
     *  @param      string      $modele         Force template to use ('' to not force)
1291
     *  @param      Translate   $outputlangs    object lang a utiliser pour traduction
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Bom\Classes\Translate was not found. Did you mean Translate? If so, make sure to prefix the type with \.
Loading history...
1292
     *  @param      int         $hidedetails    Hide details of lines
1293
     *  @param      int         $hidedesc       Hide description
1294
     *  @param      int         $hideref        Hide ref
1295
     *  @param      null|array  $moreparams     Array to provide more information
1296
     *  @return     int                         0 if KO, 1 if OK
1297
     */
1298
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
1299
    {
1300
        global $conf, $langs;
1301
1302
        $langs->load("mrp");
1303
        $outputlangs->load("products");
1304
1305
        if (!dol_strlen($modele)) {
1306
            $modele = '';
1307
1308
            if ($this->model_pdf) {
1309
                $modele = $this->model_pdf;
1310
            } elseif (getDolGlobalString('BOM_ADDON_PDF')) {
1311
                $modele = getDolGlobalString('BOM_ADDON_PDF');
1312
            }
1313
        }
1314
1315
        $modelpath = "core/modules/bom/doc/";
1316
        if (!empty($modele)) {
1317
            return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
1318
        } else {
1319
            return 0;
1320
        }
1321
    }
1322
1323
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1324
    /**
1325
     *  Return if at least one photo is available
1326
     *
1327
     * @param  string $sdir Directory to scan
1328
     * @return boolean                 True if at least one photo is available, False if not
1329
     */
1330
    public function is_photo_available($sdir)
1331
    {
1332
		// phpcs:enable
1333
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1334
        include_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';
1335
1336
        $sdir .= '/' . get_exdir(0, 0, 0, 0, $this, 'bom');
1337
1338
        $dir_osencoded = dol_osencode($sdir);
1339
        if (file_exists($dir_osencoded)) {
1340
            $handle = opendir($dir_osencoded);
1341
            if (is_resource($handle)) {
1342
                while (($file = readdir($handle)) !== false) {
1343
                    if (!utf8_check($file)) {
1344
                        $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
1345
                    }
1346
                    if (dol_is_file($sdir . $file) && image_format_supported($file) >= 0) {
1347
                        return true;
1348
                    }
1349
                }
1350
            }
1351
        }
1352
        return false;
1353
    }
1354
1355
    /**
1356
     * Initialise object with example values
1357
     * Id must be 0 if object instance is a specimen
1358
     *
1359
     * @return int
1360
     */
1361
    public function initAsSpecimen()
1362
    {
1363
        $this->initAsSpecimenCommon();
1364
        $this->ref = 'BOM-123';
1365
        $this->date_creation = dol_now() - 20000;
1366
1367
        return 1;
1368
    }
1369
1370
1371
    /**
1372
     * Action executed by scheduler
1373
     * CAN BE A CRON TASK. In such a case, parameters come from the schedule job setup field 'Parameters'
1374
     *
1375
     * @return  int         0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK)
1376
     */
1377
    public function doScheduledJob()
1378
    {
1379
        global $conf, $langs;
1380
1381
        //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
1382
1383
        $error = 0;
1384
        $this->output = '';
1385
        $this->error = '';
1386
1387
        dol_syslog(__METHOD__, LOG_DEBUG);
1388
1389
        $now = dol_now();
1390
1391
        $this->db->begin();
1392
1393
        // ...
1394
1395
        $this->db->commit();
1396
1397
        return $error;
1398
    }
1399
1400
    /**
1401
     * BOM costs calculation based on cost_price or pmp of each BOM line.
1402
     * Set the property ->total_cost and ->unit_cost of BOM.
1403
     *
1404
     * @return int|string   Return integer <0 if KO, >0 if OK, or printable error result from hook
1405
     */
1406
    public function calculateCosts()
1407
    {
1408
        global $conf, $hookmanager;
1409
1410
        include_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
1411
        $this->unit_cost = 0;
1412
        $this->total_cost = 0;
1413
1414
        $parameters = array();
1415
        $reshook = $hookmanager->executeHooks('calculateCostsBom', $parameters, $this); // Note that $action and $object may have been modified by hook
1416
1417
        if ($reshook > 0) {
1418
            return $hookmanager->resPrint;
1419
        }
1420
1421
        if (is_array($this->lines) && count($this->lines)) {
1422
            require_once constant('DOL_DOCUMENT_ROOT') . '/fourn/class/fournisseur.product.class.php';
1423
            $productFournisseur = new ProductFournisseur($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Bom\Classes\ProductFournisseur was not found. Did you mean ProductFournisseur? If so, make sure to prefix the type with \.
Loading history...
1424
            $tmpproduct = new Product($this->db);
1425
1426
            foreach ($this->lines as &$line) {
1427
                $tmpproduct->cost_price = 0;
1428
                $tmpproduct->pmp = 0;
1429
                $result = $tmpproduct->fetch($line->fk_product, '', '', '', 0, 1, 1);   // We discard selling price and language loading
1430
1431
                if ($tmpproduct->type == $tmpproduct::TYPE_PRODUCT) {
1432
                    if (empty($line->fk_bom_child)) {
1433
                        if ($result < 0) {
1434
                            $this->error = $tmpproduct->error;
1435
                            return -1;
1436
                        }
1437
                        $unit_cost = (!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp;
1438
                        $line->unit_cost = (float) price2num($unit_cost);
1439
                        if (empty($line->unit_cost)) {
1440
                            if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) {
1441
                                if ($productFournisseur->fourn_remise_percent != "0") {
1442
                                    $line->unit_cost = $productFournisseur->fourn_unitprice_with_discount;
1443
                                } else {
1444
                                    $line->unit_cost = $productFournisseur->fourn_unitprice;
1445
                                }
1446
                            }
1447
                        }
1448
1449
                        $line->total_cost = (float) price2num($line->qty * $line->unit_cost, 'MT');
1450
1451
                        $this->total_cost += $line->total_cost;
1452
                    } else {
1453
                        $bom_child = new BOM($this->db);
1454
                        $res = $bom_child->fetch($line->fk_bom_child);
1455
                        if ($res > 0) {
1456
                            $bom_child->calculateCosts();
1457
                            $line->childBom[] = $bom_child;
1458
                            $this->total_cost += (float) price2num($bom_child->total_cost * $line->qty, 'MT');
1459
                            $this->total_cost += $line->total_cost;
1460
                        } else {
1461
                            $this->error = $bom_child->error;
1462
                            return -2;
1463
                        }
1464
                    }
1465
                } else {
1466
                    // Convert qty of line into hours
1467
                    $unitforline = measuringUnitString($line->fk_unit, '', '', 1);
1468
                    $qtyhourforline = convertDurationtoHour($line->qty, $unitforline);
1469
1470
                    if (isModEnabled('workstation') && !empty($line->fk_default_workstation)) {
1471
                        $workstation = new Workstation($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Bom\Classes\Workstation was not found. Did you mean Workstation? If so, make sure to prefix the type with \.
Loading history...
1472
                        $res = $workstation->fetch($line->fk_default_workstation);
1473
1474
                        if ($res > 0) {
1475
                            $line->total_cost = (float) price2num($qtyhourforline * ($workstation->thm_operator_estimated + $workstation->thm_machine_estimated), 'MT');
1476
                        } else {
1477
                            $this->error = $workstation->error;
1478
                            return -3;
1479
                        }
1480
                    } else {
1481
                        $defaultdurationofservice = $tmpproduct->duration;
1482
                        $reg = array();
1483
                        $qtyhourservice = 0;
1484
                        if (preg_match('/^(\d+)([a-z]+)$/', $defaultdurationofservice, $reg)) {
1485
                            $qtyhourservice = convertDurationtoHour($reg[1], $reg[2]);
1486
                        }
1487
1488
                        if ($qtyhourservice) {
1489
                            $line->total_cost = (float) price2num($qtyhourforline / $qtyhourservice * $tmpproduct->cost_price, 'MT');
1490
                        } else {
1491
                            $line->total_cost = (float) price2num($line->qty * $tmpproduct->cost_price, 'MT');
1492
                        }
1493
                    }
1494
1495
                    $this->total_cost += $line->total_cost;
1496
                }
1497
            }
1498
1499
            $this->total_cost = (float) price2num($this->total_cost, 'MT');
1500
1501
            if ($this->qty > 0) {
1502
                $this->unit_cost = (float) price2num($this->total_cost / $this->qty, 'MU');
1503
            } elseif ($this->qty < 0) {
1504
                $this->unit_cost = (float) price2num($this->total_cost * $this->qty, 'MU');
1505
            }
1506
        }
1507
1508
        return 1;
1509
    }
1510
1511
    /**
1512
     * Function used to replace a product id with another one.
1513
     *
1514
     * @param DoliDB $db Database handler
1515
     * @param int $origin_id Old product id
1516
     * @param int $dest_id New product id
1517
     * @return bool
1518
     */
1519
    public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
1520
    {
1521
        $tables = array(
1522
            'bom_bomline'
1523
        );
1524
1525
        return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
1526
    }
1527
1528
    /**
1529
     * Get Net needs by product
1530
     *
1531
     * @param array<int,array{qty:float,fk_unit:?int}>  $TNetNeeds  Array of ChildBom and infos linked to
1532
     * @param float                                     $qty        qty needed (used as a factor to produce 1 unit)
1533
     * @return void
1534
     */
1535
    public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
1536
    {
1537
        if (!empty($this->lines)) {
1538
            foreach ($this->lines as $line) {
1539
                if (!empty($line->childBom)) {
1540
                    foreach ($line->childBom as $childBom) {
1541
                        $childBom->getNetNeeds($TNetNeeds, $line->qty * $qty);
1542
                    }
1543
                } else {
1544
                    if (empty($TNetNeeds[$line->fk_product]['qty'])) {
1545
                        $TNetNeeds[$line->fk_product]['qty'] = 0.0;
1546
                    }
1547
                    // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1548
                    // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1549
                    $TNetNeeds[$line->fk_product]['fk_unit'] = $line->fk_unit;
1550
                    $TNetNeeds[$line->fk_product]['qty'] += $line->qty * $qty;
1551
                }
1552
            }
1553
        }
1554
    }
1555
1556
    /**
1557
     * Get/add Net needs Tree by product or bom
1558
     *
1559
     * @param array<int,array{product:array,bom:BOM,parentid:int,qty:float,level:int,fk_unit:?int}>     $TNetNeeds  Array of ChildBom and infos linked to
1560
     * @param float     $qty       qty needed (used as a factor to produce 1 unit)
1561
     * @param int       $level     level of recursivity
1562
     * @return void
1563
     */
1564
    public function getNetNeedsTree(&$TNetNeeds = array(), $qty = 0, $level = 0)
1565
    {
1566
        if (!empty($this->lines)) {
1567
            foreach ($this->lines as $line) {
1568
                if (!empty($line->childBom)) {
1569
                    foreach ($line->childBom as $childBom) {
1570
                        $TNetNeeds[$childBom->id]['bom'] = $childBom;
1571
                        $TNetNeeds[$childBom->id]['parentid'] = $this->id;
1572
                        // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1573
                        // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1574
                        //$TNetNeeds[$childBom->id]['fk_unit'] = $line->fk_unit;
1575
                        $TNetNeeds[$childBom->id]['qty'] = $line->qty * $qty;
1576
                        $TNetNeeds[$childBom->id]['level'] = $level;
1577
                        $childBom->getNetNeedsTree($TNetNeeds, $line->qty * $qty, $level + 1);
1578
                    }
1579
                } else {
1580
                    // When using nested level (or not), the qty for needs must always use the same unit to be able to be cumulated.
1581
                    // So if unit in bom is not the same than default, we must recalculate qty after units comparisons.
1582
                    if (!isset($TNetNeeds[$this->id]['product'])) {
1583
                        $TNetNeeds[$this->id]['product'] = array();
1584
                    }
1585
                    if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product])) {
1586
                        $TNetNeeds[$this->id]['product'][$line->fk_product] = array();
1587
                    }
1588
                    $TNetNeeds[$this->id]['product'][$line->fk_product]['fk_unit'] = $line->fk_unit;
1589
                    if (!isset($TNetNeeds[$this->id]['product'][$line->fk_product]['qty'])) {
1590
                        $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] = 0.0;
1591
                    }
1592
                    $TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
1593
                    $TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
1594
                }
1595
            }
1596
        }
1597
    }
1598
1599
    /**
1600
     * Recursively retrieves all parent bom in the tree that leads to the $bom_id bom
1601
     *
1602
     * @param   array   $TParentBom     We put all found parent bom in $TParentBom
1603
     * @param   int     $bom_id         ID of bom from which we want to get parent bom ids
1604
     * @param   int     $level      Protection against infinite loop
1605
     * @return  void
1606
     */
1607
    public function getParentBomTreeRecursive(&$TParentBom, $bom_id = 0, $level = 1)
1608
    {
1609
1610
        // Protection against infinite loop
1611
        if ($level > 1000) {
1612
            return;
1613
        }
1614
1615
        if (empty($bom_id)) {
1616
            $bom_id = $this->id;
1617
        }
1618
1619
        $sql = 'SELECT l.fk_bom, b.label
1620
				FROM ' . MAIN_DB_PREFIX . 'bom_bomline l
1621
				INNER JOIN ' . MAIN_DB_PREFIX . $this->table_element . ' b ON b.rowid = l.fk_bom
1622
				WHERE fk_bom_child = ' . ((int) $bom_id);
1623
1624
        $resql = $this->db->query($sql);
1625
        if (!empty($resql)) {
1626
            while ($res = $this->db->fetch_object($resql)) {
1627
                $TParentBom[$res->fk_bom] = $res->fk_bom;
1628
                $this->getParentBomTreeRecursive($TParentBom, $res->fk_bom, $level + 1);
1629
            }
1630
        }
1631
    }
1632
1633
    /**
1634
     *  Return clicable link of object (with eventually picto)
1635
     *
1636
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
1637
     *  @param      array       $arraydata              Array of data
1638
     *  @return     string                              HTML Code for Kanban thumb.
1639
     */
1640
    public function getKanbanView($option = '', $arraydata = null)
1641
    {
1642
        global $db,$langs;
1643
1644
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1645
1646
        $return = '<div class="box-flex-item box-flex-grow-zero">';
1647
        $return .= '<div class="info-box info-box-sm">';
1648
        $return .= '<span class="info-box-icon bg-infobox-action">';
1649
        $return .= img_picto('', $this->picto);
1650
        $return .= '</span>';
1651
        $return .= '<div class="info-box-content">';
1652
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl() : '') . '</span>';
1653
        if ($selected >= 0) {
1654
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
1655
        }
1656
        if (property_exists($this, 'fields') && !empty($this->fields['bomtype']['arrayofkeyval'])) {
1657
            $return .= '<br><span class="info-box-label opacitymedium">' . $langs->trans("Type") . ' : </span>';
1658
            if ($this->bomtype == 0) {
1659
                $return .= '<span class="info-box-label">' . $this->fields['bomtype']['arrayofkeyval'][0] . '</span>';
1660
            } else {
1661
                $return .= '<span class="info-box-label">' . $this->fields['bomtype']['arrayofkeyval'][1] . '</span>';
1662
            }
1663
        }
1664
        if (!empty($arraydata['prod'])) {
1665
            $prod = $arraydata['prod'];
1666
            $return .= '<br><span class="info-box-label">' . $prod->getNomUrl(1) . '</span>';
1667
        }
1668
        if (method_exists($this, 'getLibStatut')) {
1669
            $return .= '<br><div class="info-box-status">' . $this->getLibStatut(3) . '</div>';
1670
        }
1671
1672
        $return .= '</div>';
1673
        $return .= '</div>';
1674
        $return .= '</div>';
1675
        return $return;
1676
    }
1677
}
1678
1679
1680
/**
1681
 * Class for BOMLine
1682
 */
1683
class BOMLine extends CommonObjectLine
1684
{
1685
    /**
1686
     * @var string ID to identify managed object
1687
     */
1688
    public $element = 'bomline';
1689
1690
    /**
1691
     * @var string Name of table without prefix where object is stored
1692
     */
1693
    public $table_element = 'bom_bomline';
1694
1695
    /**
1696
     * @see CommonObjectLine
1697
     */
1698
    public $parent_element = 'bom';
1699
1700
    /**
1701
     * @see CommonObjectLine
1702
     */
1703
    public $fk_parent_attribute = 'fk_bom';
1704
1705
    /**
1706
     * @var string String with name of icon for bomline. Must be the part after the 'object_' into object_bomline.png
1707
     */
1708
    public $picto = 'bomline';
1709
1710
1711
    /**
1712
     *  'type' if the field format.
1713
     *  'label' the translation key.
1714
     *  'enabled' is a condition when the field must be managed.
1715
     *  '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)
1716
     *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
1717
     *  'default' is a default value for creation (can still be replaced by the global setup of default values)
1718
     *  'index' if we want an index in database.
1719
     *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommended to name the field fk_...).
1720
     *  'position' is the sort order of field.
1721
     *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
1722
     *  '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).
1723
     *  'css' is the CSS style to use on field. For example: 'maxwidth200'
1724
     *  'help' is a string visible as a tooltip on field
1725
     *  'comment' is not used. You can store here any text of your choice. It is not used by application.
1726
     *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
1727
     *  'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
1728
     */
1729
1730
    // BEGIN MODULEBUILDER PROPERTIES
1731
    /**
1732
     * @var array<string,array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string}>  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string,array{type:...ring>,comment?:string}> at position 16 could not be parsed: Expected '}' at position 16, but found 'int'.
Loading history...
1733
     */
1734
    public $fields = array(
1735
        'rowid' => array('type' => 'integer', 'label' => 'LineID', 'enabled' => 1, 'visible' => -1, 'position' => 1, 'notnull' => 1, 'index' => 1, 'comment' => "Id",),
1736
        'fk_bom' => array('type' => 'integer:BillOfMaterials:societe/class/bom.class.php', 'label' => 'BillOfMaterials', 'enabled' => 1, 'visible' => 1, 'position' => 10, 'notnull' => 1, 'index' => 1,),
1737
        'fk_product' => array('type' => 'integer:Product:product/class/product.class.php', 'label' => 'Product', 'enabled' => 1, 'visible' => 1, 'position' => 20, 'notnull' => 1, 'index' => 1,),
1738
        'fk_bom_child' => array('type' => 'integer:BOM:bom/class/bom.class.php', 'label' => 'BillOfMaterials', 'enabled' => 1, 'visible' => -1, 'position' => 40, 'notnull' => -1,),
1739
        'description' => array('type' => 'text', 'label' => 'Description', 'enabled' => 1, 'visible' => -1, 'position' => 60, 'notnull' => -1,),
1740
        'qty' => array('type' => 'double(24,8)', 'label' => 'Quantity', 'enabled' => 1, 'visible' => 1, 'position' => 100, 'notnull' => 1, 'isameasure' => 1,),
1741
        'qty_frozen' => array('type' => 'smallint', 'label' => 'QuantityFrozen', 'enabled' => 1, 'visible' => 1, 'default' => '0', 'position' => 105, 'css' => 'maxwidth50imp', 'help' => 'QuantityConsumedInvariable'),
1742
        'disable_stock_change' => array('type' => 'smallint', 'label' => 'DisableStockChange', 'enabled' => 1, 'visible' => 1, 'default' => '0', 'position' => 108, 'css' => 'maxwidth50imp', 'help' => 'DisableStockChangeHelp'),
1743
        'efficiency' => array('type' => 'double(24,8)', 'label' => 'ManufacturingEfficiency', 'enabled' => 1, 'visible' => 0, 'default' => '1', 'position' => 110, 'notnull' => 1, 'css' => 'maxwidth50imp', 'help' => 'ValueOfEfficiencyConsumedMeans'),
1744
        'fk_unit' => array('type' => 'integer', 'label' => 'Unit', 'enabled' => 1, 'visible' => 1, 'position' => 120, 'notnull' => -1,),
1745
        'position' => array('type' => 'integer', 'label' => 'Rank', 'enabled' => 1, 'visible' => 0, 'default' => '0', 'position' => 200, 'notnull' => 1,),
1746
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 1000, 'notnull' => -1,),
1747
        'fk_default_workstation' => array('type' => 'integer', 'label' => 'DefaultWorkstation', 'enabled' => 1, 'visible' => 1, 'notnull' => 0, 'position' => 1050)
1748
    );
1749
1750
    /**
1751
     * @var int rowid
1752
     */
1753
    public $rowid;
1754
1755
    /**
1756
     * @var int fk_bom
1757
     */
1758
    public $fk_bom;
1759
1760
    /**
1761
     * @var int Id of product
1762
     */
1763
    public $fk_product;
1764
1765
    /**
1766
     * @var int Id of parent bom
1767
     */
1768
    public $fk_bom_child;
1769
1770
    /**
1771
     * @var string description
1772
     */
1773
    public $description;
1774
1775
    /**
1776
     * @var double qty
1777
     */
1778
    public $qty;
1779
1780
    /**
1781
     * @var float qty frozen
1782
     */
1783
    public $qty_frozen;
1784
1785
    /**
1786
     * @var int disable stock change
1787
     */
1788
    public $disable_stock_change;
1789
1790
    /**
1791
     * @var double efficiency
1792
     */
1793
    public $efficiency;
1794
1795
    /**
1796
     * @var int|null                ID of the unit of measurement (rowid in llx_c_units table)
1797
     * @see measuringUnitString()
1798
     * @see getLabelOfUnit()
1799
     */
1800
    public $fk_unit;
1801
1802
    /**
1803
     * @var int Service Workstation
1804
     */
1805
    public $fk_default_workstation;
1806
1807
    /**
1808
     * @var int position of line
1809
     */
1810
    public $position;
1811
1812
    /**
1813
     * @var string import key
1814
     */
1815
    public $import_key;
1816
    // END MODULEBUILDER PROPERTIES
1817
1818
    /**
1819
     * @var float       Calculated cost for the BOM line
1820
     */
1821
    public $total_cost = 0;
1822
1823
    /**
1824
     * @var float       Line unit cost based on product cost price or pmp
1825
     */
1826
    public $unit_cost = 0;
1827
1828
    /**
1829
     * @var array     array of Bom in line
1830
     */
1831
    public $childBom = array();
1832
1833
1834
1835
    /**
1836
     * Constructor
1837
     *
1838
     * @param DoliDB $db Database handler
1839
     */
1840
    public function __construct(DoliDB $db)
1841
    {
1842
        global $langs;
1843
1844
        $this->db = $db;
1845
1846
        $this->ismultientitymanaged = 0;
1847
1848
        $this->isextrafieldmanaged = 1;
1849
1850
        if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid'])) {
1851
            $this->fields['rowid']['visible'] = 0;
1852
        }
1853
        if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
1854
            $this->fields['entity']['enabled'] = 0;
1855
        }
1856
1857
        // Unset fields that are disabled
1858
        foreach ($this->fields as $key => $val) {
1859
            if (isset($val['enabled']) && empty($val['enabled'])) {
1860
                unset($this->fields[$key]);
1861
            }
1862
        }
1863
1864
        // Translate some data of arrayofkeyval
1865
        foreach ($this->fields as $key => $val) {
1866
            if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
1867
                foreach ($val['arrayofkeyval'] as $key2 => $val2) {
1868
                    $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
1869
                }
1870
            }
1871
        }
1872
    }
1873
1874
    /**
1875
     * Create object into database
1876
     *
1877
     * @param  User $user      User that creates
1878
     * @param  int  $notrigger 0=launch triggers after, 1=disable triggers
1879
     * @return int             Return integer <0 if KO, Id of created object if OK
1880
     */
1881
    public function create(User $user, $notrigger = 0)
1882
    {
1883
        if ($this->efficiency < 0 || $this->efficiency > 1) {
1884
            $this->efficiency = 1;
1885
        }
1886
1887
        return $this->createCommon($user, $notrigger);
1888
    }
1889
1890
    /**
1891
     * Load object in memory from the database
1892
     *
1893
     * @param int    $id   Id object
1894
     * @param string $ref  Ref
1895
     * @return int         Return integer <0 if KO, 0 if not found, >0 if OK
1896
     */
1897
    public function fetch($id, $ref = null)
1898
    {
1899
        $result = $this->fetchCommon($id, $ref);
1900
        //if ($result > 0 && !empty($this->table_element_line)) $this->fetchLines();
1901
        return $result;
1902
    }
1903
1904
    /**
1905
     * Load list of objects in memory from the database.
1906
     *
1907
     * @param  string       $sortorder      Sort Order
1908
     * @param  string       $sortfield      Sort field
1909
     * @param  int          $limit          limit
1910
     * @param  int          $offset         Offset
1911
     * @param  string       $filter         Filter as an Universal Search string.
1912
     *                                      Example: '((client:=:1) OR ((client:>=:2) AND (client:<=:3))) AND (client:!=:8) AND (nom:like:'a%')'
1913
     * @param  string       $filtermode     No more used
1914
     * @return array|int                    int <0 if KO, array of pages if OK
1915
     */
1916
    public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, $filter = '', $filtermode = 'AND')
1917
    {
1918
        dol_syslog(__METHOD__, LOG_DEBUG);
1919
1920
        $records = array();
1921
1922
        $sql = 'SELECT ';
1923
        $sql .= $this->getFieldList();
1924
        $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->table_element . ' as t';
1925
        if ($this->ismultientitymanaged) {
1926
            $sql .= ' WHERE t.entity IN (' . getEntity($this->element) . ')';
1927
        } else {
1928
            $sql .= ' WHERE 1 = 1';
1929
        }
1930
1931
        // Manage filter
1932
        $errormessage = '';
1933
        $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
1934
        if ($errormessage) {
1935
            $this->errors[] = $errormessage;
1936
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
1937
            return -1;
1938
        }
1939
1940
        if (!empty($sortfield)) {
1941
            $sql .= $this->db->order($sortfield, $sortorder);
1942
        }
1943
        if (!empty($limit)) {
1944
            $sql .= $this->db->plimit($limit, $offset);
1945
        }
1946
1947
        $resql = $this->db->query($sql);
1948
        if ($resql) {
1949
            $num = $this->db->num_rows($resql);
1950
1951
            while ($obj = $this->db->fetch_object($resql)) {
1952
                $record = new self($this->db);
1953
                $record->setVarsFromFetchObj($obj);
1954
                $record->fetch_optionals();
1955
1956
                $records[$record->id] = $record;
1957
            }
1958
            $this->db->free($resql);
1959
1960
            return $records;
1961
        } else {
1962
            $this->errors[] = 'Error ' . $this->db->lasterror();
1963
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
1964
1965
            return -1;
1966
        }
1967
    }
1968
1969
    /**
1970
     * Update object into database
1971
     *
1972
     * @param  User $user      User that modifies
1973
     * @param  int  $notrigger 0=launch triggers after, 1=disable triggers
1974
     * @return int             Return integer <0 if KO, >0 if OK
1975
     */
1976
    public function update(User $user, $notrigger = 0)
1977
    {
1978
        if ($this->efficiency < 0 || $this->efficiency > 1) {
1979
            $this->efficiency = 1;
1980
        }
1981
1982
        return $this->updateCommon($user, $notrigger);
1983
    }
1984
1985
    /**
1986
     * Delete object in database
1987
     *
1988
     * @param User  $user       User that deletes
1989
     * @param int   $notrigger  0=launch triggers after, 1=disable triggers
1990
     * @return int              Return integer <0 if KO, >0 if OK
1991
     */
1992
    public function delete(User $user, $notrigger = 0)
1993
    {
1994
        return $this->deleteCommon($user, $notrigger);
1995
        //return $this->deleteCommon($user, $notrigger, 1);
1996
    }
1997
1998
    /**
1999
     *  Return a link to the object card (with optionally the picto)
2000
     *
2001
     *  @param  int     $withpicto                  Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto)
2002
     *  @param  string  $option                     On what the link point to ('nolink', ...)
2003
     *  @param  int     $notooltip                  1=Disable tooltip
2004
     *  @param  string  $morecss                    Add more css on link
2005
     *  @param  int     $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
2006
     *  @return string                              String with URL
2007
     */
2008
    public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
2009
    {
2010
        global $db, $conf, $langs, $hookmanager;
2011
2012
        if (!empty($conf->dol_no_mouse_hover)) {
2013
            $notooltip = 1; // Force disable tooltips
2014
        }
2015
2016
        $result = '';
2017
2018
        $label = '<u>' . $langs->trans("BillOfMaterialsLine") . '</u>';
2019
        $label .= '<br>';
2020
        $label .= '<b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
2021
2022
        $url = constant('BASE_URL') . '/bom/bomline_card.php?id=' . $this->id;
2023
2024
        if ($option != 'nolink') {
2025
            // Add param to save lastsearch_values or not
2026
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2027
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2028
                $add_save_lastsearch_values = 1;
2029
            }
2030
            if ($add_save_lastsearch_values) {
2031
                $url .= '&save_lastsearch_values=1';
2032
            }
2033
        }
2034
2035
        $linkclose = '';
2036
        if (empty($notooltip)) {
2037
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2038
                $label = $langs->trans("ShowBillOfMaterialsLine");
2039
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
2040
            }
2041
            $linkclose .= ' title="' . dol_escape_htmltag($label, 1) . '"';
2042
            $linkclose .= ' class="classfortooltip' . ($morecss ? ' ' . $morecss : '') . '"';
2043
        } else {
2044
            $linkclose = ($morecss ? ' class="' . $morecss . '"' : '');
2045
        }
2046
2047
        $linkstart = '<a href="' . $url . '"';
2048
        $linkstart .= $linkclose . '>';
2049
        $linkend = '</a>';
2050
2051
        $result .= $linkstart;
2052
        if ($withpicto) {
2053
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="' . (($withpicto != 2) ? 'paddingright ' : '') . 'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
2054
        }
2055
        if ($withpicto != 2) {
2056
            $result .= $this->ref;
2057
        }
2058
        $result .= $linkend;
2059
        //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
2060
2061
        global $action, $hookmanager;
2062
        $hookmanager->initHooks(array('bomlinedao'));
2063
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2064
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2065
        if ($reshook > 0) {
2066
            $result = $hookmanager->resPrint;
2067
        } else {
2068
            $result .= $hookmanager->resPrint;
2069
        }
2070
2071
        return $result;
2072
    }
2073
2074
    /**
2075
     *  Return label of the status
2076
     *
2077
     *  @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
2078
     *  @return string                 Label of status
2079
     */
2080
    public function getLibStatut($mode = 0)
2081
    {
2082
        return $this->LibStatut($this->status, $mode);
2083
    }
2084
2085
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2086
    /**
2087
     *  Return the status
2088
     *
2089
     *  @param  int     $status        Id status
2090
     *  @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
2091
     *  @return string                 Label of status
2092
     */
2093
    public function LibStatut($status, $mode = 0)
2094
    {
2095
		// phpcs:enable
2096
        return '';
2097
    }
2098
2099
    /**
2100
     *  Load the info information in the object
2101
     *
2102
     *  @param  int     $id       Id of object
2103
     *  @return void
2104
     */
2105
    public function info($id)
2106
    {
2107
        $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
2108
        $sql .= ' fk_user_creat, fk_user_modif';
2109
        $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->table_element . ' as t';
2110
        $sql .= ' WHERE t.rowid = ' . ((int) $id);
2111
        $result = $this->db->query($sql);
2112
        if ($result) {
2113
            if ($this->db->num_rows($result)) {
2114
                $obj = $this->db->fetch_object($result);
2115
2116
                $this->id = $obj->rowid;
2117
2118
                $this->user_creation_id = $obj->fk_user_creat;
2119
                $this->user_modification_id = $obj->fk_user_modif;
2120
                $this->date_creation     = $this->db->jdate($obj->datec);
2121
                $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
2122
            }
2123
            $this->db->free($result);
2124
        } else {
2125
            dol_print_error($this->db);
2126
        }
2127
    }
2128
2129
    /**
2130
     * Initialise object with example values
2131
     * Id must be 0 if object instance is a specimen
2132
     *
2133
     * @return int
2134
     */
2135
    public function initAsSpecimen()
2136
    {
2137
        return $this->initAsSpecimenCommon();
2138
    }
2139
}
2140