Passed
Pull Request — dev (#6)
by Rafael
79:24 queued 24:08
created

Mo::getNomUrl()   F

Complexity

Conditions 22
Paths 5760

Size

Total Lines 77
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 22
eloc 52
nc 5760
nop 5
dl 0
loc 77
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/* Copyright (C) 2017       Laurent Destailleur         <[email protected]>
4
 * Copyright (C) 2020       Lenin Rivas		            <[email protected]>
5
 * Copyright (C) 2023-2024  Frédéric France             <[email protected]>
6
 * Copyright (C) 2024		MDW							<[email protected]>
7
 * Copyright (C) 2024       Rafael San José             <[email protected]>
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 3 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 */
22
23
namespace Dolibarr\Code\Mrp\Classes;
24
25
use Dolibarr\Core\Base\CommonObject;
26
use DoliDB;
27
28
/**
29
 * \file        mrp/class/mo.class.php
30
 * \ingroup     mrp
31
 * \brief       This file is a CRUD class file for Mo (Create/Read/Update/Delete)
32
 */
33
34
/**
35
 * Class for Mo
36
 */
37
class Mo extends CommonObject
38
{
39
    /**
40
     * @var string ID to identify managed object
41
     */
42
    public $element = 'mo';
43
44
    /**
45
     * @var string Name of table without prefix where object is stored
46
     */
47
    public $table_element = 'mrp_mo';
48
49
    /**
50
     * @var string String with name of icon for mo. Must be the part after the 'object_' into object_mo.png
51
     */
52
    public $picto = 'mrp';
53
54
55
    const STATUS_DRAFT = 0;
56
    const STATUS_VALIDATED = 1; // To produce
57
    const STATUS_INPROGRESS = 2;
58
    const STATUS_PRODUCED = 3;
59
    const STATUS_CANCELED = 9;
60
61
62
    /**
63
     *  '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')
64
     *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
65
     *  'label' the translation key.
66
     *  'picto' is code of a picto to show before value in forms
67
     *  'enabled' is a condition when the field must be managed (Example: 1 or 'getDolGlobalString("MY_SETUP_PARAM")'
68
     *  'position' is the sort order of field.
69
     *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
70
     *  '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)
71
     *  'noteditable' says if field is not editable (1 or 0)
72
     *  '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.
73
     *  'index' if we want an index in database.
74
     *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommended to name the field fk_...).
75
     *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
76
     *  '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).
77
     *  'css' and 'cssview' and 'csslist' is the CSS style to use on field. 'css' is used in creation and update. 'cssview' is used in view mode. 'csslist' is used for columns in lists. For example: 'maxwidth200', 'wordbreak', 'tdoverflowmax200'
78
     *  'help' is a 'TranslationString' to use to show a tooltip on field. You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
79
     *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
80
     *  '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.
81
     *  'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
82
     *  'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
83
     *  'comment' is not used. You can store here any text of your choice. It is not used by application.
84
     *
85
     *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
86
     */
87
88
    /**
89
     * @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...
90
     */
91
    public $fields = array(
92
        'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -2, 'position' => 1, 'notnull' => 1, 'index' => 1, 'comment' => "Id",),
93
        'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'position' => 5, 'notnull' => 1, 'default' => '1', 'index' => 1),
94
        'ref' => array('type' => 'varchar(128)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 4, 'position' => 10, 'notnull' => 1, 'default' => '(PROV)', 'index' => 1, 'searchall' => 1, 'comment' => "Reference of object", 'showoncombobox' => 1, 'noteditable' => 1),
95
        'fk_bom' => array('type' => 'integer:Bom:bom/class/bom.class.php:0:(t.status:=:1)', 'filter' => 'active=1', 'label' => 'BOM', 'enabled' => '$conf->bom->enabled', 'visible' => 1, 'position' => 33, 'notnull' => -1, 'index' => 1, 'comment' => "Original BOM", 'css' => 'minwidth100 maxwidth500', 'csslist' => 'tdoverflowmax150', 'picto' => 'bom'),
96
        'mrptype' => array('type' => 'integer', 'label' => 'Type', 'enabled' => 1, 'visible' => 1, 'position' => 34, 'notnull' => 1, 'default' => '0', 'arrayofkeyval' => array(0 => 'Manufacturing', 1 => 'Disassemble'), 'css' => 'minwidth150', 'csslist' => 'minwidth150 center'),
97
        'fk_product' => array('type' => 'integer:Product:product/class/product.class.php:0', 'label' => 'Product', 'enabled' => 'isModEnabled("product")', 'visible' => 1, 'position' => 35, 'notnull' => 1, 'index' => 1, 'comment' => "Product to produce", 'css' => 'maxwidth300', 'csslist' => 'tdoverflowmax100', 'picto' => 'product'),
98
        'qty' => array('type' => 'real', 'label' => 'QtyToProduce', 'enabled' => 1, 'visible' => 1, 'position' => 40, 'notnull' => 1, 'comment' => "Qty to produce", 'css' => 'width75', 'default' => '1', 'isameasure' => 1),
99
        'label' => array('type' => 'varchar(255)', 'label' => 'Label', 'enabled' => 1, 'visible' => 1, 'position' => 42, 'notnull' => -1, 'searchall' => 1, 'showoncombobox' => 2, 'css' => 'maxwidth300', 'csslist' => 'tdoverflowmax200', 'alwayseditable' => 1),
100
        'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php:1', 'label' => 'ThirdParty', 'picto' => 'company', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'position' => 50, 'notnull' => -1, 'index' => 1, 'css' => 'maxwidth400', 'csslist' => 'tdoverflowmax150'),
101
        'fk_project' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'picto' => 'project', 'enabled' => '$conf->project->enabled', 'visible' => -1, 'position' => 51, 'notnull' => -1, 'index' => 1, 'css' => 'minwidth200 maxwidth400', 'csslist' => 'tdoverflowmax100'),
102
        'fk_warehouse' => array('type' => 'integer:Entrepot:product/stock/class/entrepot.class.php:0', 'label' => 'WarehouseForProduction', 'picto' => 'stock', 'enabled' => 'isModEnabled("stock")', 'visible' => 1, 'position' => 52, 'css' => 'maxwidth400', 'csslist' => 'tdoverflowmax200'),
103
        'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 61, 'notnull' => -1,),
104
        'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 62, 'notnull' => -1,),
105
        'date_creation' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'position' => 500, 'notnull' => 1,),
106
        'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -2, 'position' => 501, 'notnull' => 1,),
107
        'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -2, 'position' => 502,),
108
        'fk_user_creat' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -2, 'position' => 510, 'notnull' => 1, 'foreignkey' => 'user.rowid', 'csslist' => 'tdoverflowmax100'),
109
        'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'position' => 511, 'notnull' => -1, 'csslist' => 'tdoverflowmax100'),
110
        'date_start_planned' => array('type' => 'datetime', 'label' => 'DateStartPlannedMo', 'enabled' => 1, 'visible' => 1, 'position' => 55, 'notnull' => -1, 'index' => 1, 'help' => 'KeepEmptyForAsap', 'alwayseditable' => 1, 'csslist' => 'nowraponall'),
111
        'date_end_planned' => array('type' => 'datetime', 'label' => 'DateEndPlannedMo', 'enabled' => 1, 'visible' => 1, 'position' => 56, 'notnull' => -1, 'index' => 1, 'alwayseditable' => 1, 'csslist' => 'nowraponall'),
112
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 1000, 'notnull' => -1,),
113
        'model_pdf' => array('type' => 'varchar(255)', 'label' => 'Model pdf', 'enabled' => 1, 'visible' => 0, 'position' => 1010),
114
        'status' => array('type' => 'integer', 'label' => 'Status', 'enabled' => 1, 'visible' => 2, 'position' => 1000, 'default' => '0', 'notnull' => 1, 'index' => 1, 'arrayofkeyval' => array('0' => 'Draft', '1' => 'Validated', '2' => 'InProgress', '3' => 'StatusMOProduced', '9' => 'Canceled')),
115
        'fk_parent_line' => array('type' => 'integer:MoLine:mrp/class/mo.class.php', 'label' => 'ParentMo', 'enabled' => 1, 'visible' => 0, 'position' => 1020, 'default' => '0', 'notnull' => 0, 'index' => 1,'showoncombobox' => 0),
116
    );
117
    public $rowid;
118
    public $entity;
119
    public $ref;
120
121
    /**
122
     * @var int mrptype
123
     */
124
    public $mrptype;
125
    public $label;
126
127
    /**
128
     * @var float Quantity
129
     */
130
    public $qty;
131
    public $fk_warehouse;
132
    public $fk_soc;
133
    public $socid;
134
135
    /**
136
     * @var string public note
137
     */
138
    public $note_public;
139
140
    /**
141
     * @var string private note
142
     */
143
    public $note_private;
144
145
    /**
146
     * @var integer|string date_creation
147
     */
148
    public $date_creation;
149
150
    /**
151
     * @var integer|string date_validation
152
     */
153
    public $date_valid;
154
155
    public $fk_user_creat;
156
    public $fk_user_modif;
157
    public $import_key;
158
    public $status;
159
160
    /**
161
     * @var int ID of product
162
     */
163
    public $fk_product;
164
165
    /**
166
     * @var Product product object
167
     */
168
    public $product;
169
170
    /**
171
     * @var integer|string date_start_planned
172
     */
173
    public $date_start_planned;
174
175
    /**
176
     * @var integer|string date_end_planned
177
     */
178
    public $date_end_planned;
179
180
    /**
181
     * @var int ID bom
182
     */
183
    public $fk_bom;
184
185
    /**
186
     * @var Bom bom
187
     */
188
    public $bom;
189
190
    /**
191
     * @var int ID project
192
     */
193
    public $fk_project;
194
195
    /**
196
     * @var double  New quantity. When we update the quantity to produce, we set this to save old value before calling the ->update that call the updateProduction that need this
197
     *              to recalculate all the quantities in lines to consume and produce.
198
     */
199
    public $oldQty;
200
201
202
    // If this object has a subtable with lines
203
204
    /**
205
     * @var string    Name of subtable line
206
     */
207
    public $table_element_line = 'mrp_production';
208
209
    /**
210
     * @var string    Field with ID of parent key if this field has a parent
211
     */
212
    public $fk_element = 'fk_mo';
213
214
    /**
215
     * @var string    Name of subtable class that manage subtable lines
216
     */
217
    public $class_element_line = 'MoLine';
218
219
    /**
220
     * @var array<string, array<string>>    List of child tables. To test if we can delete object.
221
     */
222
    protected $childtables = array();
223
224
    /**
225
     * @var string[]    List of child tables. To know object to delete on cascade.
226
     */
227
    protected $childtablesoncascade = array('mrp_production');
228
229
    /**
230
     * @var MoLine[]     Array of subtable lines
231
     */
232
    public $lines = array();
233
234
    /**
235
     * @var MoLine|null     MO line
236
     */
237
    public $line = null;
238
239
    /**
240
     * @var int ID of parent line
241
     */
242
    public $fk_parent_line;
243
244
    /**
245
     * @var array<string,int|string> tpl
246
     */
247
    public $tpl = array();
248
249
250
    /**
251
     * Constructor
252
     *
253
     * @param DoliDB $db Database handler
254
     */
255
    public function __construct(DoliDB $db)
256
    {
257
        global $langs;
258
259
        $this->db = $db;
260
261
        $this->ismultientitymanaged = 1;
262
        $this->isextrafieldmanaged = 1;
263
264
        if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid'])) {
265
            $this->fields['rowid']['visible'] = 0;
266
        }
267
        if (!isModEnabled('multicompany') && isset($this->fields['entity'])) {
268
            $this->fields['entity']['enabled'] = 0;
269
        }
270
271
        // Unset fields that are disabled
272
        foreach ($this->fields as $key => $val) {
273
            if (isset($val['enabled']) && empty($val['enabled'])) {
274
                unset($this->fields[$key]);
275
            }
276
        }
277
278
        // Translate some data of arrayofkeyval
279
        foreach ($this->fields as $key => $val) {
280
            if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) {
281
                foreach ($val['arrayofkeyval'] as $key2 => $val2) {
282
                    $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
283
                }
284
            }
285
        }
286
    }
287
288
    /**
289
     * Create object into database
290
     *
291
     * @param  User $user      User that creates
292
     * @param  int  $notrigger 0=launch triggers after, 1=disable triggers
293
     * @return int             Return integer <=0 if KO, Id of created object if OK
294
     */
295
    public function create(User $user, $notrigger = 0)
296
    {
297
        $error = 0;
298
        $idcreated = 0;
299
300
        // If kits feature is enabled and we don't allow kits into BOM and MO, we check that the product is not a kit/virtual product
301
        if (getDolGlobalString('PRODUIT_SOUSPRODUITS') && !getDolGlobalString('ALLOW_USE_KITS_INTO_BOM_AND_MO') && $this->fk_product > 0) {
302
                        $tmpproduct = new Product($this->db);
303
            $tmpproduct->fetch($this->fk_product);
304
            if ($tmpproduct->hasFatherOrChild(1) > 0) {
305
                $this->error = 'ErrorAVirtualProductCantBeUsedIntoABomOrMo';
306
                $this->errors[] = $this->error;
307
                return -1;
308
            }
309
        }
310
311
        $this->db->begin();
312
313
        if ($this->fk_bom > 0) {
314
            // If there is a known BOM, we force the type of MO to the type of BOM
315
                $tmpbom = new BOM($this->db);
316
            $tmpbom->fetch($this->fk_bom);
317
318
            $this->mrptype = $tmpbom->bomtype;
319
        }
320
321
        if (!$error) {
322
            $idcreated = $this->createCommon($user, $notrigger);
323
            if ($idcreated <= 0) {
324
                $error++;
325
            }
326
        }
327
328
        if (!$error) {
329
            $result = $this->createProduction($user, $notrigger);   // Insert lines from BOM
330
            if ($result <= 0) {
331
                $error++;
332
            }
333
        }
334
335
        if (!$error) {
336
            $this->db->commit();
337
            return $idcreated;
338
        } else {
339
            $this->db->rollback();
340
            return -1;
341
        }
342
    }
343
344
    /**
345
     * Clone an object into another one
346
     *
347
     * @param   User    $user       User that creates
348
     * @param   int     $fromid     Id of object to clone
349
     * @return  mixed               New object created, <0 if KO
350
     */
351
    public function createFromClone(User $user, $fromid)
352
    {
353
        global $langs, $extrafields;
354
        $error = 0;
355
356
        dol_syslog(__METHOD__, LOG_DEBUG);
357
358
        $object = new self($this->db);
359
360
        $this->db->begin();
361
362
        // Load source object
363
        $result = $object->fetchCommon($fromid);
364
        if ($result > 0 && !empty($object->table_element_line)) {
365
            $object->fetchLines();
366
        }
367
368
        // get lines so they will be clone
369
        //foreach($this->lines as $line)
370
        //  $line->fetch_optionals();
371
372
        // Reset some properties
373
        unset($object->id);
374
        unset($object->fk_user_creat);
375
        unset($object->import_key);
376
377
        // We make $object->lines empty to sort it without produced and consumed lines
378
        $TLines = $object->lines;
379
        $object->lines = array();
380
381
        // Remove produced and consumed lines
382
        foreach ($TLines as $key => $line) {
383
            if (in_array($line->role, array('consumed', 'produced'))) {
384
                unset($object->lines[$key]);
385
            } else {
386
                $object->lines[] = $line;
387
            }
388
        }
389
390
391
        // Clear fields
392
        $object->ref = empty($this->fields['ref']['default']) ? "copy_of_" . $object->ref : $this->fields['ref']['default'];
393
        $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf") . " " . $object->label : $this->fields['label']['default'];
394
        $object->status = self::STATUS_DRAFT;
395
        // ...
396
        // Clear extrafields that are unique
397
        if (is_array($object->array_options) && count($object->array_options) > 0) {
398
            $extrafields->fetch_name_optionals_label($this->table_element);
399
            foreach ($object->array_options as $key => $option) {
400
                $shortkey = preg_replace('/options_/', '', $key);
401
                if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) {
402
                    //var_dump($key);
403
                    //var_dump($clonedObj->array_options[$key]); exit;
404
                    unset($object->array_options[$key]);
405
                }
406
            }
407
        }
408
409
        // Create clone
410
        $object->context['createfromclone'] = 'createfromclone';
411
        $result = $object->createCommon($user);
412
        if ($result < 0) {
413
            $error++;
414
            $this->error = $object->error;
415
            $this->errors = $object->errors;
416
        }
417
418
        if (!$error) {
419
            // copy internal contacts
420
            if ($this->copy_linked_contact($object, 'internal') < 0) {
421
                $error++;
422
            }
423
        }
424
425
        if (!$error) {
426
            // copy external contacts if same company
427
            if (property_exists($this, 'socid') && $this->socid == $object->socid) {
428
                if ($this->copy_linked_contact($object, 'external') < 0) {
429
                    $error++;
430
                }
431
            }
432
        }
433
434
        unset($object->context['createfromclone']);
435
436
        // End
437
        if (!$error) {
438
            $this->db->commit();
439
            return $object;
440
        } else {
441
            $this->db->rollback();
442
            return -1;
443
        }
444
    }
445
446
    /**
447
     * Load object in memory from the database
448
     *
449
     * @param int    $id   Id object
450
     * @param string $ref  Ref
451
     * @return int         Return integer <0 if KO, 0 if not found, >0 if OK
452
     */
453
    public function fetch($id, $ref = null)
454
    {
455
        $result = $this->fetchCommon($id, $ref);
456
        if ($result > 0 && !empty($this->table_element_line)) {
457
            $this->fetchLines();
458
        }
459
460
        $this->socid = $this->fk_soc;
461
462
        return $result;
463
    }
464
465
    /**
466
     * Load object lines in memory from the database
467
     *
468
     * @return int         Return integer <0 if KO, 0 if not found, >0 if OK
469
     */
470
    public function fetchLines()
471
    {
472
        $this->lines = array();
473
474
        $result = $this->fetchLinesCommon();
475
        return $result;
476
    }
477
478
479
    /**
480
     * Load list of objects in memory from the database.
481
     *
482
     * @param  string           $sortorder      Sort Order
483
     * @param  string           $sortfield      Sort field
484
     * @param  int              $limit          Limit
485
     * @param  int              $offset         Offset
486
     * @param  string|array     $filter         Filter USF.
487
     * @param  string           $filtermode     Filter mode (AND or OR)
488
     * @return array|int                        int <0 if KO, array of pages if OK
489
     */
490
    public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, $filter = '', $filtermode = 'AND')
491
    {
492
        dol_syslog(__METHOD__, LOG_DEBUG);
493
494
        $records = array();
495
496
        $sql = 'SELECT ';
497
        $sql .= $this->getFieldList();
498
        $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->table_element . ' as t';
499
        if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) {
500
            $sql .= ' WHERE t.entity IN (' . getEntity($this->element) . ')';
501
        } else {
502
            $sql .= ' WHERE 1 = 1';
503
        }
504
505
        // Manage filter
506
        if (is_array($filter)) {
507
            $sqlwhere = array();
508
            if (count($filter) > 0) {
509
                foreach ($filter as $key => $value) {
510
                    if ($key == 't.rowid') {
511
                        $sqlwhere[] = $this->db->sanitize($key) . " = " . ((int) $value);
512
                    } elseif (strpos($key, 'date') !== false) {
513
                        $sqlwhere[] = $this->db->sanitize($key) . " = '" . $this->db->idate($value) . "'";
514
                    } else {
515
                        $sqlwhere[] = $this->db->sanitize($key) . " LIKE '%" . $this->db->escape($this->db->escapeforlike($value)) . "%'";
516
                    }
517
                }
518
            }
519
            if (count($sqlwhere) > 0) {
520
                $sql .= ' AND (' . implode(' ' . $this->db->escape($filtermode) . ' ', $sqlwhere) . ')';
521
            }
522
523
            $filter = '';
524
        }
525
526
        // Manage filter
527
        $errormessage = '';
528
        $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
529
        if ($errormessage) {
530
            $this->errors[] = $errormessage;
531
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
532
            return -1;
533
        }
534
535
        if (!empty($sortfield)) {
536
            $sql .= $this->db->order($sortfield, $sortorder);
537
        }
538
        if (!empty($limit)) {
539
            $sql .= $this->db->plimit($limit, $offset);
540
        }
541
542
        $resql = $this->db->query($sql);
543
        if ($resql) {
544
            $num = $this->db->num_rows($resql);
545
            $i = 0;
546
            while ($i < min($limit, $num)) {
547
                $obj = $this->db->fetch_object($resql);
548
549
                $record = new self($this->db);
550
                $record->setVarsFromFetchObj($obj);
551
552
                $records[$record->id] = $record;
553
554
                $i++;
555
            }
556
            $this->db->free($resql);
557
558
            return $records;
559
        } else {
560
            $this->errors[] = 'Error ' . $this->db->lasterror();
561
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
562
563
            return -1;
564
        }
565
    }
566
567
    /**
568
     * Get list of lines linked to current line for a defined role.
569
     *
570
     * @param   string  $role       Get lines linked to current line with the selected role ('consumed', 'produced', ...)
571
     * @param   int     $lineid     Id of production line to filter children
572
     * @return  array               Array of lines
573
     */
574
    public function fetchLinesLinked($role, $lineid = 0)
575
    {
576
        $resarray = array();
577
        $mostatic = new MoLine($this->db);
578
579
        $sql = 'SELECT ';
580
        $sql .= $mostatic->getFieldList();
581
        $sql .= ' FROM ' . MAIN_DB_PREFIX . $mostatic->table_element . ' as t';
582
        $sql .= " WHERE t.role = '" . $this->db->escape($role) . "'";
583
        if ($lineid > 0) {
584
            $sql .= ' AND t.fk_mrp_production = ' . ((int) $lineid);
585
        } else {
586
            $sql .= 'AND t.fk_mo = ' . ((int) $this->id);
587
        }
588
589
        $resql = $this->db->query($sql);
590
        if ($resql) {
591
            $num = $this->db->num_rows($resql);
592
593
            $i = 0;
594
            while ($i < $num) {
595
                $obj = $this->db->fetch_object($resql);
596
                if ($obj) {
597
                    $resarray[] = array(
598
                        'rowid' => $obj->rowid,
599
                        'date' => $this->db->jdate($obj->date_creation),
600
                        'qty' => $obj->qty,
601
                        'role' => $obj->role,
602
                        'fk_product' => $obj->fk_product,
603
                        'fk_warehouse' => $obj->fk_warehouse,
604
                        'batch' => $obj->batch,
605
                        'fk_stock_movement' => $obj->fk_stock_movement,
606
                        'fk_unit' => $obj->fk_unit
607
                    );
608
                }
609
610
                $i++;
611
            }
612
613
            return $resarray;
614
        } else {
615
            $this->error = $this->db->lasterror();
616
            return array();
617
        }
618
    }
619
620
621
    /**
622
     * Count number of movement with origin of MO
623
     *
624
     * @return  int         Number of movements
625
     */
626
    public function countMovements()
627
    {
628
        $result = 0;
629
630
        $sql = 'SELECT COUNT(rowid) as nb FROM ' . MAIN_DB_PREFIX . 'stock_mouvement as sm';
631
        $sql .= " WHERE sm.origintype = 'mo' and sm.fk_origin = " . ((int) $this->id);
632
633
        $resql = $this->db->query($sql);
634
        if ($resql) {
635
            $num = $this->db->num_rows($resql);
636
637
            $i = 0;
638
            while ($i < $num) {
639
                $obj = $this->db->fetch_object($resql);
640
                if ($obj) {
641
                    $result = $obj->nb;
642
                }
643
644
                $i++;
645
            }
646
        } else {
647
            $this->error = $this->db->lasterror();
648
        }
649
650
        return $result;
651
    }
652
653
654
    /**
655
     * Update object into database
656
     *
657
     * @param  User $user      User that modifies
658
     * @param  int  $notrigger 0=launch triggers after, 1=disable triggers
659
     * @return int             Return integer <0 if KO, >0 if OK
660
     */
661
    public function update(User $user, $notrigger = 0)
662
    {
663
        $error = 0;
664
665
        $this->db->begin();
666
667
        $result = $this->updateCommon($user, $notrigger);
668
        if ($result <= 0) {
669
            $error++;
670
        }
671
672
        // Update the lines (the qty) to consume or to produce
673
        $result = $this->updateProduction($user, $notrigger);
674
        if ($result <= 0) {
675
            $error++;
676
        }
677
678
        if (!$error) {
679
            $this->db->commit();
680
            return 1;
681
        } else {
682
            $this->db->rollback();
683
            return -1;
684
        }
685
    }
686
687
688
    /**
689
     * Erase and update the line to consume and to produce.
690
     *
691
     * @param  User $user      User that modifies
692
     * @param  int  $notrigger 0=launch triggers after, 1=disable triggers
693
     * @return int             Return integer <0 if KO, >0 if OK
694
     */
695
    public function createProduction(User $user, $notrigger = 0)
696
    {
697
        $error = 0;
698
        $role = "";
699
700
        if ($this->status != self::STATUS_DRAFT) {
701
            return -1;
702
        }
703
704
        $this->db->begin();
705
706
        // Insert lines in mrp_production table from BOM data
707
        if (!$error) {
708
            $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'mrp_production WHERE fk_mo = ' . ((int) $this->id);
709
            $this->db->query($sql);
710
711
            $moline = new MoLine($this->db);
712
713
            // Line to produce
714
            $moline->fk_mo = $this->id;
715
            $moline->qty = $this->qty;
716
            $moline->fk_product = $this->fk_product;
717
            $moline->position = 1;
718
                        $tmpproduct = new Product($this->db);
719
            $tmpproduct->fetch($this->fk_product);
720
            $moline->fk_unit = $tmpproduct->fk_unit;
721
722
            if ($this->fk_bom > 0) {    // If a BOM is defined, we know what to produce.
723
                        $bom = new BOM($this->db);
724
                $bom->fetch($this->fk_bom);
725
                if ($bom->bomtype == 1) {
726
                    $role = 'toproduce';
727
                    $moline->role = 'toconsume';
728
                } else {
729
                    $role = 'toconsume';
730
                    $moline->role = 'toproduce';
731
                }
732
            } else {
733
                if ($this->mrptype == 1) {
734
                    $moline->role = 'toconsume';
735
                } else {
736
                    $moline->role = 'toproduce';
737
                }
738
            }
739
740
            $resultline = $moline->create($user, false); // Never use triggers here
741
            if ($resultline <= 0) {
742
                $error++;
743
                $this->error = $moline->error;
744
                $this->errors = $moline->errors;
745
            }
746
747
            if ($this->fk_bom > 0) {    // If a BOM is defined, we know what to consume.
748
                if ($bom->id > 0) {
749
                    // Lines to consume
750
                    if (!$error) {
751
                        foreach ($bom->lines as $line) {
752
                            $moline = new MoLine($this->db);
753
754
                            $moline->fk_mo = $this->id;
755
                            $moline->origin_id = $line->id;
756
                            $moline->origin_type = 'bomline';
757
                            if (!empty($line->fk_unit)) {
758
                                $moline->fk_unit = $line->fk_unit;
759
                            }
760
                            if ($line->qty_frozen) {
761
                                $moline->qty = $line->qty; // Qty to consume does not depends on quantity to produce
762
                            } else {
763
                                $moline->qty = (float) price2num(($line->qty / (!empty($bom->qty) ? $bom->qty : 1)) * $this->qty / (!empty($line->efficiency) ? $line->efficiency : 1), 'MS'); // Calculate with Qty to produce and  more presition
764
                            }
765
                            if ($moline->qty <= 0) {
766
                                $error++;
767
                                $this->error = "BadValueForquantityToConsume";
768
                                break;
769
                            } else {
770
                                $moline->fk_product = $line->fk_product;
771
                                $moline->role = $role;
772
                                $moline->position = $line->position;
773
                                $moline->qty_frozen = $line->qty_frozen;
774
                                $moline->disable_stock_change = $line->disable_stock_change;
775
                                if (!empty($line->fk_default_workstation)) {
776
                                    $moline->fk_default_workstation = $line->fk_default_workstation;
777
                                }
778
779
                                $resultline = $moline->create($user, false); // Never use triggers here
780
                                if ($resultline <= 0) {
781
                                    $error++;
782
                                    $this->error = $moline->error;
783
                                    $this->errors = $moline->errors;
784
                                    dol_print_error($this->db, $moline->error, $moline->errors);
785
                                    break;
786
                                }
787
                            }
788
                        }
789
                    }
790
                }
791
            }
792
        }
793
794
        if (!$error) {
795
            $this->db->commit();
796
            return 1;
797
        } else {
798
            $this->db->rollback();
799
            return -1;
800
        }
801
    }
802
803
    /**
804
     * Update quantities in lines to consume and/or lines to produce.
805
     *
806
     * @param  User $user      User that modifies
807
     * @param  int  $notrigger 0=launch triggers after, 1=disable triggers
808
     * @return int             Return integer <0 if KO, >0 if OK
809
     */
810
    public function updateProduction(User $user, $notrigger = 0)
811
    {
812
        $error = 0;
813
814
        if ($this->status != self::STATUS_DRAFT) {
815
            return 1;
816
        }
817
818
        $this->db->begin();
819
820
        $oldQty = $this->oldQty;
821
        $newQty = $this->qty;
822
        if ($newQty != $oldQty && !empty($this->oldQty)) {
823
            $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "mrp_production WHERE fk_mo = " . (int) $this->id;
824
            $resql = $this->db->query($sql);
825
            if ($resql) {
826
                while ($obj = $this->db->fetch_object($resql)) {
827
                    $moLine = new MoLine($this->db);
828
                    $res = $moLine->fetch($obj->rowid);
829
                    if (!$res) {
830
                        $error++;
831
                    }
832
833
                    if ($moLine->role == 'toconsume' || $moLine->role == 'toproduce') {
834
                        if (empty($moLine->qty_frozen)) {
835
                            $qty = $newQty * $moLine->qty / $oldQty;
836
                            $moLine->qty = (float) price2num($qty, 'MS');
837
                            $res = $moLine->update($user);
838
                            if (!$res) {
839
                                $error++;
840
                            }
841
                        }
842
                    }
843
                }
844
            }
845
        }
846
        if (!$error) {
847
            $this->db->commit();
848
            return 1;
849
        } else {
850
            $this->db->rollback();
851
            return -1;
852
        }
853
    }
854
855
856
    /**
857
     * Delete object in database
858
     *
859
     * @param   User    $user                                       User that deletes
860
     * @param   int     $notrigger                                  0=launch triggers after, 1=disable triggers
861
     * @param   bool    $also_cancel_consumed_and_produced_lines    true if the consumed and produced lines will be deleted (and stocks incremented/decremented back) (false by default)
862
     * @return  int                                                 Return integer <0 if KO, >0 if OK
863
     */
864
    public function delete(User $user, $notrigger = 0, $also_cancel_consumed_and_produced_lines = false)
865
    {
866
        $error = 0;
867
        $this->db->begin();
868
869
        if ($also_cancel_consumed_and_produced_lines) {
870
            $result = $this->cancelConsumedAndProducedLines($user, 0, false, $notrigger);
871
            if ($result < 0) {
872
                $error++;
873
            }
874
        }
875
876
        if (!$error) {
877
            $result = $this->deleteCommon($user, $notrigger);
878
            if ($result < 0) {
879
                $error++;
880
            }
881
        }
882
883
        if ($error) {
884
            $this->db->rollback();
885
            return -1;
886
        } else {
887
            $this->db->commit();
888
            return 1;
889
        }
890
    }
891
892
    /**
893
     *  Delete a line of object in database
894
     *
895
     *  @param  User    $user           User that delete
896
     *  @param  int     $idline         Id of line to delete
897
     *  @param  int     $notrigger      0=launch triggers after, 1=disable triggers
898
     *  @param  int     $fk_movement    Movement
899
     *  @return int                     Return >0 if OK, <0 if KO
900
     */
901
    public function deleteLine(User $user, $idline, $notrigger = 0, $fk_movement = 0)
902
    {
903
        global $langs;
904
        $langs->loadLangs(array('stocks', 'mrp'));
905
906
        if ($this->status < 0) {
907
            $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
908
            return -2;
909
        }
910
        $productstatic = new Product($this->db);
911
912
        $arrayoflines = $this->fetchLinesLinked('consumed', $idline);   // Get lines consumed under the one to delete
913
914
        $result = 0;
915
916
        $this->db->begin();
917
918
        if (!empty($arrayoflines)) {
919
            // If there is child lines
920
            $stockmove = new MouvementStock($this->db);
921
            $stockmove->setOrigin($this->element, $this->id);
922
923
            if (!empty($fk_movement)) {
924
                // The fk_movement was not recorded so we try to guess the product and quantity to restore.
925
                $moline = new MoLine($this->db);
926
                $TArrayMoLine = $moline->fetchAll('', '', 1, 0, '(fk_stock_movement:=:' . ((int) $fk_movement) . ')');
927
                $moline = array_shift($TArrayMoLine);
928
929
                $movement = new MouvementStock($this->db);
930
                $movement->fetch($fk_movement);
931
                $productstatic->fetch($movement->product_id);
932
                $qtytoprocess = $movement->qty;
933
934
                // Reverse stock movement
935
                $labelmovementCancel = $langs->trans("CancelProductionForRef", $productstatic->ref);
936
                $codemovementCancel = $langs->trans("StockIncrease");
937
938
                if (($qtytoprocess >= 0)) {
939
                    $idstockmove = $stockmove->reception($user, $movement->product_id, $movement->warehouse_id, $qtytoprocess, 0, $labelmovementCancel, '', '', $movement->batch, dol_now(), 0, $codemovementCancel);
940
                } else {
941
                    $idstockmove = $stockmove->livraison($user, $movement->product_id, $movement->warehouse_id, $qtytoprocess, 0, $labelmovementCancel, dol_now(), '', '', $movement->batch, 0, $codemovementCancel);
942
                }
943
                if ($idstockmove < 0) {
944
                    $this->error++;
945
                    setEventMessages($stockmove->error, $stockmove->errors, 'errors');
946
                } else {
947
                    $result = $moline->delete($user, $notrigger);
948
                }
949
            } else {
950
                // Loop on each child lines
951
                foreach ($arrayoflines as $key => $arrayofline) {
952
                    $lineDetails = $arrayoflines[$key];
953
                    $productstatic->fetch($lineDetails['fk_product']);
954
                    $qtytoprocess = $lineDetails['qty'];
955
956
                    // Reverse stock movement
957
                    $labelmovementCancel = $langs->trans("CancelProductionForRef", $productstatic->ref);
958
                    $codemovementCancel = $langs->trans("StockIncrease");
959
960
961
                    if ($qtytoprocess >= 0) {
962
                        $idstockmove = $stockmove->reception($user, $lineDetails['fk_product'], $lineDetails['fk_warehouse'], $qtytoprocess, 0, $labelmovementCancel, '', '', $lineDetails['batch'], dol_now(), 0, $codemovementCancel);
963
                    } else {
964
                        $idstockmove = $stockmove->livraison($user, $lineDetails['fk_product'], $lineDetails['fk_warehouse'], $qtytoprocess, 0, $labelmovementCancel, dol_now(), '', '', $lineDetails['batch'], 0, $codemovementCancel);
965
                    }
966
                    if ($idstockmove < 0) {
967
                        $this->error++;
968
                        setEventMessages($stockmove->error, $stockmove->errors, 'errors');
969
                    } else {
970
                        $moline = new MoLine($this->db);
971
                        $moline->fetch($lineDetails['rowid']);
972
973
                        $resdel = $moline->delete($user, $notrigger);
974
                        if ($resdel < 0) {
975
                            $this->error++;
976
                            setEventMessages($moline->error, $moline->errors, 'errors');
977
                        }
978
                    }
979
                }
980
981
                if (empty($this->error)) {
982
                    $result = $this->deleteLineCommon($user, $idline, $notrigger);
983
                }
984
            }
985
        } else {
986
            // No child lines
987
            $result = $this->deleteLineCommon($user, $idline, $notrigger);
988
        }
989
990
        if (!empty($this->error) || $result <= 0) {
991
            $this->db->rollback();
992
        } else {
993
            $this->db->commit();
994
        }
995
996
        return $result;
997
    }
998
999
1000
    /**
1001
     *  Returns the reference to the following non used MO depending on the active numbering module
1002
     *  defined into MRP_MO_ADDON
1003
     *
1004
     *  @param  Product     $prod   Object product
1005
     *  @return string              MO free reference
1006
     */
1007
    public function getNextNumRef($prod)
1008
    {
1009
        global $langs, $conf;
1010
        $langs->load("mrp");
1011
1012
        if (getDolGlobalString('MRP_MO_ADDON')) {
1013
            $mybool = false;
1014
1015
            $file = getDolGlobalString('MRP_MO_ADDON') . ".php";
1016
            $classname = getDolGlobalString('MRP_MO_ADDON');
1017
1018
            // Include file with class
1019
            $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1020
            foreach ($dirmodels as $reldir) {
1021
                $dir = dol_buildpath($reldir . "core/modules/mrp/");
1022
1023
                // Load file with numbering class (if found)
1024
                $mybool = ((bool) @include_once $dir . $file) || $mybool;
1025
            }
1026
1027
            if ($mybool === false) {
1028
                dol_print_error(null, "Failed to include file " . $file);
1029
                return '';
1030
            }
1031
1032
            $obj = new $classname();
1033
            $numref = $obj->getNextValue($prod, $this);
1034
1035
            if ($numref != "") {
1036
                return $numref;
1037
            } else {
1038
                $this->error = $obj->error;
1039
                //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
1040
                return "";
1041
            }
1042
        } else {
1043
            print $langs->trans("Error") . " " . $langs->trans("Error_MRP_MO_ADDON_NotDefined");
1044
            return "";
1045
        }
1046
    }
1047
1048
    /**
1049
     *  Validate Mo
1050
     *
1051
     *  @param      User    $user           User making status change
1052
     *  @param      int     $notrigger      1=Does not execute triggers, 0= execute triggers
1053
     *  @return     int                     Return integer <=0 if OK, 0=Nothing done, >0 if KO
1054
     */
1055
    public function validate($user, $notrigger = 0)
1056
    {
1057
        global $conf;
1058
1059
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1060
1061
        $error = 0;
1062
1063
        // Protection
1064
        if ($this->status == self::STATUS_VALIDATED) {
1065
            dol_syslog(get_class($this) . "::validate action abandoned: already validated", LOG_WARNING);
1066
            return 0;
1067
        }
1068
1069
        /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->mrp->create))
1070
         || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->mrp->mrp_advance->validate))))
1071
         {
1072
         $this->error='NotEnoughPermissions';
1073
         dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
1074
         return -1;
1075
         }*/
1076
1077
        $now = dol_now();
1078
1079
        $this->db->begin();
1080
1081
        // Define new ref
1082
        if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
1083
            $this->fetch_product();
1084
            $num = $this->getNextNumRef($this->product);
1085
        } else {
1086
            $num = $this->ref;
1087
        }
1088
        $this->newref = $num;
1089
1090
        // Validate
1091
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element;
1092
        $sql .= " SET ref = '" . $this->db->escape($num) . "',";
1093
        $sql .= " status = " . self::STATUS_VALIDATED . ",";
1094
        $sql .= " date_valid='" . $this->db->idate($now) . "',";
1095
        $sql .= " fk_user_valid = " . $user->id;
1096
        $sql .= " WHERE rowid = " . ((int) $this->id);
1097
1098
        dol_syslog(get_class($this) . "::validate()", LOG_DEBUG);
1099
        $resql = $this->db->query($sql);
1100
        if (!$resql) {
1101
            dol_print_error($this->db);
1102
            $this->error = $this->db->lasterror();
1103
            $error++;
1104
        }
1105
1106
        if (!$error && !$notrigger) {
1107
            // Call trigger
1108
            $result = $this->call_trigger('MRP_MO_VALIDATE', $user);
1109
            if ($result < 0) {
1110
                $error++;
1111
            }
1112
            // End call triggers
1113
        }
1114
1115
        if (!$error) {
1116
            $this->oldref = $this->ref;
1117
1118
            // Rename directory if dir was a temporary ref
1119
            if (preg_match('/^[\(]?PROV/i', $this->ref)) {
1120
                // Now we rename also files into index
1121
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filename = CONCAT('" . $this->db->escape($this->newref) . "', SUBSTR(filename, " . (strlen($this->ref) + 1) . ")), filepath = 'mrp/" . $this->db->escape($this->newref) . "'";
1122
                $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'mrp/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
1123
                $resql = $this->db->query($sql);
1124
                if (!$resql) {
1125
                    $error++;
1126
                    $this->error = $this->db->lasterror();
1127
                }
1128
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filepath = 'mrp/" . $this->db->escape($this->newref) . "'";
1129
                $sql .= " WHERE filepath = 'mrp/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
1130
                $resql = $this->db->query($sql);
1131
                if (!$resql) {
1132
                    $error++;
1133
                    $this->error = $this->db->lasterror();
1134
                }
1135
1136
                // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1137
                $oldref = dol_sanitizeFileName($this->ref);
1138
                $newref = dol_sanitizeFileName($num);
1139
                $dirsource = $conf->mrp->dir_output . '/' . $oldref;
1140
                $dirdest = $conf->mrp->dir_output . '/' . $newref;
1141
                if (!$error && file_exists($dirsource)) {
1142
                    dol_syslog(get_class($this) . "::validate() rename dir " . $dirsource . " into " . $dirdest);
1143
1144
                    if (@rename($dirsource, $dirdest)) {
1145
                        dol_syslog("Rename ok");
1146
                        // Rename docs starting with $oldref with $newref
1147
                        $listoffiles = dol_dir_list($conf->mrp->dir_output . '/' . $newref, 'files', 1, '^' . preg_quote($oldref, '/'));
1148
                        foreach ($listoffiles as $fileentry) {
1149
                            $dirsource = $fileentry['name'];
1150
                            $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
1151
                            $dirsource = $fileentry['path'] . '/' . $dirsource;
1152
                            $dirdest = $fileentry['path'] . '/' . $dirdest;
1153
                            @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

1153
                            /** @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...
1154
                        }
1155
                    }
1156
                }
1157
            }
1158
        }
1159
1160
        // Set new ref and current status
1161
        if (!$error) {
1162
            $this->ref = $num;
1163
            $this->status = self::STATUS_VALIDATED;
1164
        }
1165
1166
        if (!$error) {
1167
            $this->db->commit();
1168
            return 1;
1169
        } else {
1170
            $this->db->rollback();
1171
            return -1;
1172
        }
1173
    }
1174
1175
    /**
1176
     *  Set draft status
1177
     *
1178
     *  @param  User    $user           Object user that modify
1179
     *  @param  int     $notrigger      1=Does not execute triggers, 0=Execute triggers
1180
     *  @return int                     Return integer <0 if KO, >0 if OK
1181
     */
1182
    public function setDraft($user, $notrigger = 0)
1183
    {
1184
        // Protection
1185
        if ($this->status <= self::STATUS_DRAFT) {
1186
            return 0;
1187
        }
1188
1189
        /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->mymodule->write))
1190
         || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->mymodule->mymodule_advance->validate))))
1191
         {
1192
         $this->error='Permission denied';
1193
         return -1;
1194
         }*/
1195
1196
        return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'MRP_MO_UNVALIDATE');
1197
    }
1198
1199
    /**
1200
     *  Set cancel status
1201
     *
1202
     *  @param  User    $user                                       Object user that modify
1203
     *  @param  int     $notrigger                                  1=Does not execute triggers, 0=Execute triggers
1204
     *  @param  bool    $also_cancel_consumed_and_produced_lines    true if the consumed and produced lines will be deleted (and stocks incremented/decremented back) (false by default)
1205
     *  @return int                                                 Return integer <0 if KO, 0=Nothing done, >0 if OK
1206
     */
1207
    public function cancel($user, $notrigger = 0, $also_cancel_consumed_and_produced_lines = false)
1208
    {
1209
        // Protection
1210
        if ($this->status != self::STATUS_VALIDATED && $this->status != self::STATUS_INPROGRESS) {
1211
            return 0;
1212
        }
1213
1214
        /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->mymodule->write))
1215
         || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->mymodule->mymodule_advance->validate))))
1216
         {
1217
         $this->error='Permission denied';
1218
         return -1;
1219
         }*/
1220
1221
        $error = 0;
1222
        $this->db->begin();
1223
1224
        if ($also_cancel_consumed_and_produced_lines) {
1225
            $result = $this->cancelConsumedAndProducedLines($user, 0, true, $notrigger);
1226
            if ($result < 0) {
1227
                $error++;
1228
            }
1229
        }
1230
1231
        if (!$error) {
1232
            $result = $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'MRP_MO_CANCEL');
1233
            if ($result < 0) {
1234
                $error++;
1235
            }
1236
        }
1237
1238
        if ($error) {
1239
            $this->db->rollback();
1240
            return -1;
1241
        } else {
1242
            $this->db->commit();
1243
            return 1;
1244
        }
1245
    }
1246
1247
    /**
1248
     *  Set back to validated status
1249
     *
1250
     *  @param  User    $user           Object user that modify
1251
     *  @param  int     $notrigger      1=Does not execute triggers, 0=Execute triggers
1252
     *  @return int                     Return integer <0 if KO, 0=Nothing done, >0 if OK
1253
     */
1254
    public function reopen($user, $notrigger = 0)
1255
    {
1256
        // Protection
1257
        if ($this->status != self::STATUS_PRODUCED && $this->status != self::STATUS_CANCELED) {
1258
            return 0;
1259
        }
1260
1261
        /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->mymodule->write))
1262
         || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->mymodule->mymodule_advance->validate))))
1263
         {
1264
         $this->error='Permission denied';
1265
         return -1;
1266
         }*/
1267
1268
        return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'MRP_MO_REOPEN');
1269
    }
1270
1271
    /**
1272
     *  Cancel consumed and produced lines (movement stocks)
1273
     *
1274
     *  @param  User    $user                   Object user that modify
1275
     *  @param  int     $mode                   Type line supported (0 by default) (0: consumed and produced lines; 1: consumed lines; 2: produced lines)
1276
     *  @param  bool    $also_delete_lines      true if the consumed/produced lines is deleted (false by default)
1277
     *  @param  int     $notrigger              1=Does not execute triggers, 0=Execute triggers
1278
     *  @return int                             Return integer <0 if KO, 0=Nothing done, >0 if OK
1279
     */
1280
    public function cancelConsumedAndProducedLines($user, $mode = 0, $also_delete_lines = false, $notrigger = 0)
1281
    {
1282
        global $langs;
1283
1284
        if (!isModEnabled('stock')) {
1285
            return 1;
1286
        }
1287
1288
        $error = 0;
1289
        $langs->load('stocks');
1290
1291
        $this->db->begin();
1292
1293
        // Cancel consumed lines
1294
        if (empty($mode) || $mode == 1) {
1295
            $arrayoflines = $this->fetchLinesLinked('consumed');
1296
            if (!empty($arrayoflines)) {
1297
                foreach ($arrayoflines as $key => $lineDetails) {
1298
                    $productstatic = new Product($this->db);
1299
                    $productstatic->fetch($lineDetails['fk_product']);
1300
                    $qtytoprocess = $lineDetails['qty'];
1301
1302
                    // Reverse stock movement
1303
                    $labelmovementCancel = $langs->trans("CancelProductionForRef", $productstatic->ref);
1304
                    $codemovementCancel = $langs->trans("StockIncrease");
1305
1306
                    $stockmove = new MouvementStock($this->db);
1307
                    $stockmove->setOrigin($this->element, $this->id);
1308
                    if ($qtytoprocess >= 0) {
1309
                        $idstockmove = $stockmove->reception($user, $lineDetails['fk_product'], $lineDetails['fk_warehouse'], $qtytoprocess, 0, $labelmovementCancel, '', '', $lineDetails['batch'], dol_now(), 0, $codemovementCancel);
1310
                    } else {
1311
                        $idstockmove = $stockmove->livraison($user, $lineDetails['fk_product'], $lineDetails['fk_warehouse'], $qtytoprocess, 0, $labelmovementCancel, dol_now(), '', '', $lineDetails['batch'], 0, $codemovementCancel);
1312
                    }
1313
                    if ($idstockmove < 0) {
1314
                        $this->error = $stockmove->error;
1315
                        $this->errors = $stockmove->errors;
1316
                        $error++;
1317
                        break;
1318
                    }
1319
1320
                    if ($also_delete_lines) {
1321
                        $result = $this->deleteLineCommon($user, $lineDetails['rowid'], $notrigger);
1322
                        if ($result < 0) {
1323
                            $error++;
1324
                            break;
1325
                        }
1326
                    }
1327
                }
1328
            }
1329
        }
1330
1331
        // Cancel produced lines
1332
        if (empty($mode) || $mode == 2) {
1333
            $arrayoflines = $this->fetchLinesLinked('produced');
1334
            if (!empty($arrayoflines)) {
1335
                foreach ($arrayoflines as $key => $lineDetails) {
1336
                    $productstatic = new Product($this->db);
1337
                    $productstatic->fetch($lineDetails['fk_product']);
1338
                    $qtytoprocess = $lineDetails['qty'];
1339
1340
                    // Reverse stock movement
1341
                    $labelmovementCancel = $langs->trans("CancelProductionForRef", $productstatic->ref);
1342
                    $codemovementCancel = $langs->trans("StockDecrease");
1343
1344
                    $stockmove = new MouvementStock($this->db);
1345
                    $stockmove->setOrigin($this->element, $this->id);
1346
                    if ($qtytoprocess >= 0) {
1347
                        $idstockmove = $stockmove->livraison($user, $lineDetails['fk_product'], $lineDetails['fk_warehouse'], $qtytoprocess, 0, $labelmovementCancel, dol_now(), '', '', $lineDetails['batch'], 0, $codemovementCancel);
1348
                    } else {
1349
                        $idstockmove = $stockmove->reception($user, $lineDetails['fk_product'], $lineDetails['fk_warehouse'], $qtytoprocess, 0, $labelmovementCancel, '', '', $lineDetails['batch'], dol_now(), 0, $codemovementCancel);
1350
                    }
1351
                    if ($idstockmove < 0) {
1352
                        $this->error = $stockmove->error;
1353
                        $this->errors = $stockmove->errors;
1354
                        $error++;
1355
                        break;
1356
                    }
1357
1358
                    if ($also_delete_lines) {
1359
                        $result = $this->deleteLineCommon($user, $lineDetails['rowid'], $notrigger);
1360
                        if ($result < 0) {
1361
                            $error++;
1362
                            break;
1363
                        }
1364
                    }
1365
                }
1366
            }
1367
        }
1368
1369
        if ($error) {
1370
            $this->db->rollback();
1371
            return -1;
1372
        } else {
1373
            $this->db->commit();
1374
            return 1;
1375
        }
1376
    }
1377
1378
    /**
1379
     * getTooltipContentArray
1380
     *
1381
     * @param array $params ex option, infologin
1382
     * @since v18
1383
     * @return array
1384
     */
1385
    public function getTooltipContentArray($params)
1386
    {
1387
        global $langs;
1388
1389
        $langs->loadLangs(['mrp', 'products']);
1390
        $nofetch = isset($params['nofetch']) ? true : false;
1391
1392
        $datas = [];
1393
1394
        $datas['picto'] = img_picto('', $this->picto) . ' <u class="paddingrightonly">' . $langs->trans("ManufacturingOrder") . '</u>';
1395
        if (isset($this->status)) {
1396
            $datas['picto'] .= ' ' . $this->getLibStatut(5);
1397
        }
1398
        $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
1399
        if (isset($this->label)) {
1400
            $datas['label'] = '<br><b>' . $langs->trans('Label') . ':</b> ' . $this->label;
1401
        }
1402
        if (isset($this->mrptype)) {
1403
            $datas['type'] = '<br><b>' . $langs->trans('Type') . ':</b> ' . $this->fields['mrptype']['arrayofkeyval'][$this->mrptype];
1404
        }
1405
        if (isset($this->qty)) {
1406
            $datas['qty'] = '<br><b>' . $langs->trans('QtyToProduce') . ':</b> ' . $this->qty;
1407
        }
1408
        if (!$nofetch && isset($this->fk_product)) {
1409
            $product = new Product($this->db);
1410
            $product->fetch($this->fk_product);
1411
            $datas['product'] = '<br><b>' . $langs->trans('Product') . ':</b> ' . $product->getNomUrl(1, '', 0, -1, 1);
1412
        }
1413
        if (!$nofetch && isset($this->fk_warehouse)) {
1414
            $warehouse = new Entrepot($this->db);
1415
            $warehouse->fetch($this->fk_warehouse);
1416
            $datas['warehouse'] = '<br><b>' . $langs->trans('WarehouseForProduction') . ':</b> ' . $warehouse->getNomUrl(1, '', 0, 1);
1417
        }
1418
1419
        return $datas;
1420
    }
1421
1422
    /**
1423
     *  Return a link to the object card (with optionally the picto)
1424
     *
1425
     *  @param  int     $withpicto                  Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto)
1426
     *  @param  string  $option                     On what the link point to ('nolink', '', 'production', ...)
1427
     *  @param  int     $notooltip                  1=Disable tooltip
1428
     *  @param  string  $morecss                    Add more css on link
1429
     *  @param  int     $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1430
     *  @return string                              String with URL
1431
     */
1432
    public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1433
    {
1434
        global $conf, $langs, $action, $hookmanager;
1435
1436
        if (!empty($conf->dol_no_mouse_hover)) {
1437
            $notooltip = 1; // Force disable tooltips
1438
        }
1439
1440
        $result = '';
1441
        $params = [
1442
            'id' => $this->id,
1443
            'objecttype' => $this->element,
1444
            'option' => $option,
1445
            'nofetch' => 1,
1446
        ];
1447
        $classfortooltip = 'classfortooltip';
1448
        $dataparams = '';
1449
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1450
            $classfortooltip = 'classforajaxtooltip';
1451
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
1452
            $label = '';
1453
        } else {
1454
            $label = implode($this->getTooltipContentArray($params));
1455
        }
1456
1457
        $url = constant('BASE_URL') . '/mrp/mo_card.php?id=' . $this->id;
1458
        if ($option == 'production') {
1459
            $url = constant('BASE_URL') . '/mrp/mo_production.php?id=' . $this->id;
1460
        }
1461
1462
        if ($option != 'nolink') {
1463
            // Add param to save lastsearch_values or not
1464
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1465
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1466
                $add_save_lastsearch_values = 1;
1467
            }
1468
            if ($add_save_lastsearch_values) {
1469
                $url .= '&save_lastsearch_values=1';
1470
            }
1471
        }
1472
1473
        $linkclose = '';
1474
        if (empty($notooltip)) {
1475
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1476
                $label = $langs->trans("ShowMo");
1477
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
1478
            }
1479
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
1480
            $linkclose .= $dataparams . ' class="' . $classfortooltip . ($morecss ? ' ' . $morecss : '') . '"';
1481
        } else {
1482
            $linkclose = ($morecss ? ' class="' . $morecss . '"' : '');
1483
        }
1484
1485
        $linkstart = '<a href="' . $url . '"';
1486
        $linkstart .= $linkclose . '>';
1487
        $linkend = '</a>';
1488
1489
        $result .= $linkstart;
1490
        if ($withpicto) {
1491
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1492
        }
1493
        if ($withpicto != 2) {
1494
            $result .= $this->ref;
1495
        }
1496
        $result .= $linkend;
1497
        //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1498
1499
        $hookmanager->initHooks(array('modao'));
1500
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1501
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1502
        if ($reshook > 0) {
1503
            $result = $hookmanager->resPrint;
1504
        } else {
1505
            $result .= $hookmanager->resPrint;
1506
        }
1507
1508
        return $result;
1509
    }
1510
1511
    /**
1512
     *  Return label of the status
1513
     *
1514
     *  @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
1515
     *  @return string                 Label of status
1516
     */
1517
    public function getLibStatut($mode = 0)
1518
    {
1519
        return $this->LibStatut($this->status, $mode);
1520
    }
1521
1522
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1523
    /**
1524
     *  Return the status
1525
     *
1526
     *  @param  int     $status        Id status
1527
     *  @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
1528
     *  @return string                 Label of status
1529
     */
1530
    public function LibStatut($status, $mode = 0)
1531
    {
1532
		// phpcs:enable
1533
        if (empty($this->labelStatus)) {
1534
            global $langs;
1535
            //$langs->load("mrp");
1536
            $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1537
            $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('ValidatedToProduce');
1538
            $this->labelStatus[self::STATUS_INPROGRESS] = $langs->transnoentitiesnoconv('InProgress');
1539
            $this->labelStatus[self::STATUS_PRODUCED] = $langs->transnoentitiesnoconv('StatusMOProduced');
1540
            $this->labelStatus[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Canceled');
1541
1542
            $this->labelStatusShort[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('Draft');
1543
            $this->labelStatusShort[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('Validated');
1544
            $this->labelStatusShort[self::STATUS_INPROGRESS] = $langs->transnoentitiesnoconv('InProgress');
1545
            $this->labelStatusShort[self::STATUS_PRODUCED] = $langs->transnoentitiesnoconv('StatusMOProduced');
1546
            $this->labelStatusShort[self::STATUS_CANCELED] = $langs->transnoentitiesnoconv('Canceled');
1547
        }
1548
1549
        $statusType = 'status' . $status;
1550
        if ($status == self::STATUS_VALIDATED) {
1551
            $statusType = 'status1';
1552
        }
1553
        if ($status == self::STATUS_INPROGRESS) {
1554
            $statusType = 'status4';
1555
        }
1556
        if ($status == self::STATUS_PRODUCED) {
1557
            $statusType = 'status6';
1558
        }
1559
        if ($status == self::STATUS_CANCELED) {
1560
            $statusType = 'status9';
1561
        }
1562
1563
        return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
1564
    }
1565
1566
    /**
1567
     *  Load the info information in the object
1568
     *
1569
     *  @param  int     $id       Id of object
1570
     *  @return void
1571
     */
1572
    public function info($id)
1573
    {
1574
        $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
1575
        $sql .= ' fk_user_creat, fk_user_modif';
1576
        $sql .= ' FROM ' . MAIN_DB_PREFIX . $this->table_element . ' as t';
1577
        $sql .= ' WHERE t.rowid = ' . ((int) $id);
1578
        $result = $this->db->query($sql);
1579
        if ($result) {
1580
            if ($this->db->num_rows($result)) {
1581
                $obj = $this->db->fetch_object($result);
1582
1583
                $this->id = $obj->rowid;
1584
1585
                $this->user_creation_id = $obj->fk_user_creat;
1586
                $this->user_modification_id = $obj->fk_user_modif;
1587
                $this->date_creation     = $this->db->jdate($obj->datec);
1588
                $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
1589
            }
1590
1591
            $this->db->free($result);
1592
        } else {
1593
            dol_print_error($this->db);
1594
        }
1595
    }
1596
1597
    /**
1598
     * Initialise object with example values
1599
     * Id must be 0 if object instance is a specimen
1600
     *
1601
     * @return int
1602
     */
1603
    public function initAsSpecimen()
1604
    {
1605
        $ret = $this->initAsSpecimenCommon();
1606
1607
        $this->lines = array();
1608
1609
        return $ret;
1610
    }
1611
1612
    /**
1613
     *  Create an array of lines
1614
     *
1615
     *  @param string       $rolefilter     string lines role filter
1616
     *  @return array|int                   array of lines if OK, <0 if KO
1617
     */
1618
    public function getLinesArray($rolefilter = '')
1619
    {
1620
        $this->lines = array();
1621
1622
        $objectline = new MoLine($this->db);
1623
1624
        $filter = '(fk_mo:=:' . ((int) $this->id) . ')';
1625
        if (!empty($rolefilter)) {
1626
            $filter .= " AND (role:=:'" . $this->db->escape($rolefilter) . "')";
1627
        }
1628
        $result = $objectline->fetchAll('ASC', 'position', 0, 0, $filter);
1629
1630
        if (is_numeric($result)) {
1631
            $this->error = $objectline->error;
1632
            $this->errors = $objectline->errors;
1633
            return $result;
1634
        } else {
1635
            $this->lines = $result;
1636
            return $this->lines;
1637
        }
1638
    }
1639
1640
    /**
1641
     *  Create a document onto disk according to template module.
1642
     *
1643
     *  @param      string      $modele         Force template to use ('' to not force)
1644
     *  @param      Translate   $outputlangs    object lang a utiliser pour traduction
1645
     *  @param      int         $hidedetails    Hide details of lines
1646
     *  @param      int         $hidedesc       Hide description
1647
     *  @param      int         $hideref        Hide ref
1648
     *  @param      null|array  $moreparams     Array to provide more information
1649
     *  @return     int                         0 if KO, 1 if OK
1650
     */
1651
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
1652
    {
1653
        global $langs;
1654
1655
        $langs->load("mrp");
1656
1657
        if (!dol_strlen($modele)) {
1658
            //$modele = 'standard';
1659
            $modele = ''; // Remove this once a pdf_standard.php exists.
1660
1661
            if ($this->model_pdf) {
1662
                $modele = $this->model_pdf;
1663
            } elseif (getDolGlobalString('MRP_MO_ADDON_PDF')) {
1664
                $modele = getDolGlobalString('MRP_MO_ADDON_PDF');
1665
            }
1666
        }
1667
1668
        $modelpath = "core/modules/mrp/doc/";
1669
1670
        if (empty($modele)) {
1671
            return 1; // Remove this once a pdf_standard.php exists.
1672
        }
1673
1674
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
1675
    }
1676
1677
    /**
1678
     *  Return HTML table table of source object lines
1679
     *  TODO Move this and previous function into output html class file (htmlline.class.php).
1680
     *  If lines are into a template, title must also be into a template
1681
     *  But for the moment we don't know if it's possible, so we keep the method available on overloaded objects.
1682
     *
1683
     *  @param  string      $restrictlist       ''=All lines, 'services'=Restrict to services only
1684
     *  @param  array       $selectedLines      Array of lines id for selected lines
1685
     *  @return void
1686
     */
1687
    public function printOriginLinesList($restrictlist = '', $selectedLines = array())
1688
    {
1689
        global $langs, $hookmanager, $form, $action;
1690
1691
        $langs->load('stocks');
1692
        $text_stock_options = $langs->trans("RealStockDesc") . '<br>';
1693
        $text_stock_options .= $langs->trans("RealStockWillAutomaticallyWhen") . '<br>';
1694
        $text_stock_options .= (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') ? '- ' . $langs->trans("DeStockOnShipment") . '<br>' : '');
1695
        $text_stock_options .= (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER') ? '- ' . $langs->trans("DeStockOnValidateOrder") . '<br>' : '');
1696
        $text_stock_options .= (getDolGlobalString('STOCK_CALCULATE_ON_BILL') ? '- ' . $langs->trans("DeStockOnBill") . '<br>' : '');
1697
        $text_stock_options .= (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') ? '- ' . $langs->trans("ReStockOnBill") . '<br>' : '');
1698
        $text_stock_options .= (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER') ? '- ' . $langs->trans("ReStockOnValidateOrder") . '<br>' : '');
1699
        $text_stock_options .= (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER') ? '- ' . $langs->trans("ReStockOnDispatchOrder") . '<br>' : '');
1700
        $text_stock_options .= (getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION') || getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION_CLOSE') ? '- ' . $langs->trans("StockOnReception") . '<br>' : '');
1701
1702
        print '<tr class="liste_titre">';
1703
        // Product or sub-bom
1704
        print '<td class="linecoldescription">' . $langs->trans('Ref');
1705
        if (getDolGlobalString('BOM_SUB_BOM')) {
1706
            print ' &nbsp; <a id="show_all" href="#">' . img_picto('', 'folder-open', 'class="paddingright"') . $langs->trans("ExpandAll") . '</a>&nbsp;&nbsp;';
1707
            print '<a id="hide_all" href="#">' . img_picto('', 'folder', 'class="paddingright"') . $langs->trans("UndoExpandAll") . '</a>&nbsp;';
1708
        }
1709
        print '</td>';
1710
        // Qty
1711
        print '<td class="right">' . $langs->trans('Qty');
1712
        if ($this->bom->bomtype == 0) {
1713
            print ' <span class="opacitymedium">(' . $langs->trans("ForAQuantityOf", $this->bom->qty) . ')</span>';
1714
        } else {
1715
            print ' <span class="opacitymedium">(' . $langs->trans("ForAQuantityToConsumeOf", $this->bom->qty) . ')</span>';
1716
        }
1717
        // Unit
1718
        print '<td class="right">' . $langs->trans('Unit');
1719
1720
        print '</td>';
1721
        print '<td class="center">' . $form->textwithpicto($langs->trans("PhysicalStock"), $text_stock_options, 1) . '</td>';
1722
        print '<td class="center">' . $form->textwithpicto($langs->trans("VirtualStock"), $langs->trans("VirtualStockDesc")) . '</td>';
1723
        print '<td class="center">' . $langs->trans('QtyFrozen') . '</td>';
1724
        print '<td class="center">' . $langs->trans('DisableStockChange') . '</td>';
1725
        print '<td class="center">' . $langs->trans('MoChildGenerate') . '</td>';
1726
        //print '<td class="center">'.$form->showCheckAddButtons('checkforselect', 1).'</td>';
1727
        //print '<td class="center"></td>';
1728
        print '</tr>';
1729
        $i = 0;
1730
1731
        if (!empty($this->lines)) {
1732
            foreach ($this->lines as $line) {
1733
                $reshook = 0;
1734
                if (is_object($hookmanager)) {
1735
                    $parameters = array('line' => $line, 'i' => $i, 'restrictlist' => $restrictlist, 'selectedLines' => $selectedLines);
1736
                    if (!empty($line->fk_parent_line)) {
1737
                        $parameters['fk_parent_line'] = $line->fk_parent_line;
1738
                    }
1739
                    $reshook = $hookmanager->executeHooks('printOriginObjectLine', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1740
                }
1741
                if (empty($reshook)) {
1742
                    $this->printOriginLine($line, '', $restrictlist, '/core/tpl', $selectedLines);
1743
                }
1744
1745
                $i++;
1746
            }
1747
        }
1748
    }
1749
1750
1751
    /**
1752
     *  Return HTML with a line of table array of source object lines
1753
     *  TODO Move this and previous function into output html class file (htmlline.class.php).
1754
     *  If lines are into a template, title must also be into a template
1755
     *  But for the moment we don't know if it's possible as we keep a method available on overloaded objects.
1756
     *
1757
     *  @param  MoLine  $line               Line
1758
     *  @param  string              $var                Var
1759
     *  @param  string              $restrictlist       ''=All lines, 'services'=Restrict to services only (strike line if not)
1760
     *  @param  string              $defaulttpldir      Directory where to find the template
1761
     *  @param  array               $selectedLines      Array of lines id for selected lines
1762
     *  @return void
1763
     */
1764
    public function printOriginLine($line, $var, $restrictlist = '', $defaulttpldir = '/core/tpl', $selectedLines = array())
1765
    {
1766
        $productstatic = new Product($this->db);
1767
1768
        $this->tpl['id'] = $line->id;
1769
1770
        $this->tpl['label'] = '';
1771
        if (!empty($line->fk_product) && $line->fk_product > 0) {
1772
            $productstatic->fetch($line->fk_product);
1773
            $productstatic->load_virtual_stock();
1774
            $this->tpl['label'] .= $productstatic->getNomUrl(1);
1775
            //$this->tpl['label'].= ' - '.$productstatic->label;
1776
        } else {
1777
            // If origin MO line is not a product, but another MO
1778
            // TODO
1779
        }
1780
1781
        $this->tpl['qty_bom'] = 1;
1782
        if (is_object($this->bom) && $this->bom->qty > 1) {
1783
            $this->tpl['qty_bom'] = $this->bom->qty;
1784
        }
1785
1786
        $this->tpl['stock'] = $productstatic->stock_reel;
1787
        $this->tpl['seuil_stock_alerte'] = $productstatic->seuil_stock_alerte;
1788
        $this->tpl['virtual_stock'] = $productstatic->stock_theorique;
1789
        $this->tpl['qty'] = $line->qty;
1790
        $this->tpl['fk_unit'] = $line->fk_unit;
1791
        $this->tpl['qty_frozen'] = $line->qty_frozen;
1792
        $this->tpl['disable_stock_change'] = $line->disable_stock_change;
1793
        $this->tpl['efficiency'] = $line->efficiency;
1794
1795
        global $conf;   // used into template
1796
        $res = include DOL_DOCUMENT_ROOT . '/mrp/tpl/originproductline.tpl.php';
1797
    }
1798
1799
    /**
1800
     * Function used to replace a thirdparty id with another one.
1801
     *
1802
     * @param DoliDB    $db             Database handler
1803
     * @param int       $origin_id      Old thirdparty id
1804
     * @param int       $dest_id        New thirdparty id
1805
     * @return bool
1806
     */
1807
    public static function replaceThirdparty($db, $origin_id, $dest_id)
1808
    {
1809
        $tables = array('mrp_mo');
1810
1811
        return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
1812
    }
1813
1814
1815
    /**
1816
     * Function used to return children of Mo
1817
     *
1818
     * @return Mo[]|int             array if OK, -1 if KO
1819
     */
1820
    public function getMoChilds()
1821
    {
1822
        $TMoChilds = array();
1823
        $error = 0;
1824
1825
        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "mrp_mo as mo_child";
1826
        $sql .= " WHERE fk_parent_line IN ";
1827
        $sql .= " (SELECT rowid FROM " . MAIN_DB_PREFIX . "mrp_production as line_parent";
1828
        $sql .= " WHERE fk_mo=" . ((int) $this->id) . ")";
1829
1830
        $resql = $this->db->query($sql);
1831
1832
        if ($resql) {
1833
            if ($this->db->num_rows($resql) > 0) {
1834
                while ($obj = $this->db->fetch_object($resql)) {
1835
                    $MoChild = new Mo($this->db);
1836
                    $res = $MoChild->fetch($obj->rowid);
1837
                    if ($res > 0) {
1838
                        $TMoChilds[$MoChild->id] = $MoChild;
1839
                    } else {
1840
                        $error++;
1841
                    }
1842
                }
1843
            }
1844
        } else {
1845
            $error++;
1846
        }
1847
1848
        if ($error) {
1849
            return -1;
1850
        } else {
1851
            return $TMoChilds;
1852
        }
1853
    }
1854
1855
    /**
1856
     * Function used to return all child MOs recursively
1857
     *
1858
     * @param int $depth   Depth for recursing loop count
1859
     * @return Mo[]|int[]|int  array of MOs if OK, -1 if KO
1860
     */
1861
    public function getAllMoChilds($depth = 0)
1862
    {
1863
        if ($depth > 1000) {
1864
            return -1;
1865
        }
1866
1867
        $TMoChilds = array();
1868
        $error = 0;
1869
1870
        $childMoList = $this->getMoChilds();
1871
1872
        if ($childMoList == -1) {
1873
            return -1;
1874
        }
1875
1876
        foreach ($childMoList as $childMo) {
0 ignored issues
show
Bug introduced by
The expression $childMoList of type integer is not traversable.
Loading history...
1877
            $TMoChilds[$childMo->id] = $childMo;
1878
        }
1879
1880
        foreach ($childMoList as $childMo) {
0 ignored issues
show
Bug introduced by
The expression $childMoList of type integer is not traversable.
Loading history...
1881
            $childMoChildren = $childMo->getAllMoChilds($depth + 1);
1882
1883
            if ($childMoChildren == -1) {
1884
                $error++;
1885
            } else {
1886
                foreach ($childMoChildren as $child) {
1887
                    $TMoChilds[$child->id] = $child;
1888
                }
1889
            }
1890
        }
1891
1892
        if ($error) {
1893
            return -1;
1894
        } else {
1895
            return $TMoChilds;
1896
        }
1897
    }
1898
1899
1900
1901
    /**
1902
     * Function used to return children of Mo
1903
     *
1904
     * @return Mo|int           MO object if OK, -1 if KO, 0 if not exist
1905
     */
1906
    public function getMoParent()
1907
    {
1908
        $MoParent = new Mo($this->db);
1909
        $error = 0;
1910
1911
        $sql = "SELECT lineparent.fk_mo as id_moparent FROM " . MAIN_DB_PREFIX . "mrp_mo as mo";
1912
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "mrp_production lineparent ON mo.fk_parent_line = lineparent.rowid";
1913
        $sql .= " WHERE mo.rowid = " . ((int) $this->id);
1914
1915
        $resql = $this->db->query($sql);
1916
1917
        if ($resql) {
1918
            if ($this->db->num_rows($resql) > 0) {
1919
                $obj = $this->db->fetch_object($resql);
1920
                $res = $MoParent->fetch($obj->id_moparent);
1921
                if ($res < 0) {
1922
                    $error++;
1923
                }
1924
            } else {
1925
                return 0;
1926
            }
1927
        } else {
1928
            $error++;
1929
        }
1930
1931
        if ($error) {
1932
            return -1;
1933
        } else {
1934
            return $MoParent;
1935
        }
1936
    }
1937
1938
    /**
1939
     *  Return clicable link of object (with eventually picto)
1940
     *
1941
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
1942
     *  @param      array       $arraydata              Array of data
1943
     *  @return     string                              HTML Code for Kanban thumb.
1944
     */
1945
    public function getKanbanView($option = '', $arraydata = null)
1946
    {
1947
        global $langs;
1948
1949
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1950
1951
        $return = '<div class="box-flex-item box-flex-grow-zero">';
1952
        $return .= '<div class="info-box info-box-sm">';
1953
        $return .= '<span class="info-box-icon bg-infobox-action">';
1954
        $return .= img_picto('', $this->picto);
1955
        //$return .= '<i class="fa fa-dol-action"></i>'; // Can be image
1956
        $return .= '</span>';
1957
        $return .= '<div class="info-box-content">';
1958
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref) . '</span>';
1959
        if ($selected >= 0) {
1960
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
1961
        }
1962
        if (!empty($arraydata['bom'])) {
1963
            $return .= '<br><span class="info-box-label">' . $arraydata['bom']->getNomUrl(1) . '</span>';
1964
        }
1965
        if (!empty($arraydata['product'])) {
1966
            $return .= '<br><span class="info-box-label">' . $arraydata['product']->getNomUrl(1) . '</span>';
1967
            if (property_exists($this, 'qty')) {
1968
                $return .= ' <span class="info-box-label">(' . $langs->trans("Qty") . ' ' . $this->qty . ')</span>';
1969
            }
1970
        } else {
1971
            if (property_exists($this, 'qty')) {
1972
                $return .= '<br><span class="info-box-label">' . $langs->trans('Quantity') . ' : ' . $this->qty . '</span>';
1973
            }
1974
        }
1975
        if (method_exists($this, 'getLibStatut')) {
1976
            $return .= '<br><div class="info-box-status">' . $this->getLibStatut(3) . '</div>';
1977
        }
1978
        $return .= '</div>';
1979
        $return .= '</div>';
1980
        $return .= '</div>';
1981
        return $return;
1982
    }
1983
}
1984