Passed
Push — EXTRACT_CLASSES ( 9f3ede...ff35ec )
by Rafael
76:09 queued 20:57
created

Mo   F

Complexity

Total Complexity 284

Size/Duplication

Total Lines 1953
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 935
dl 0
loc 1953
rs 1.665
c 0
b 0
f 0
wmc 284

34 Methods

Rating   Name   Duplication   Size   Complexity  
A update() 0 23 4
A delete() 0 25 6
C updateProduction() 0 42 12
F getNomUrl() 0 77 22
A replaceThirdparty() 0 5 1
A setDraft() 0 15 2
A printOriginLine() 0 33 5
C deleteLine() 0 96 13
A fetch() 0 10 3
B LibStatut() 0 34 6
F createProduction() 0 107 19
A countMovements() 0 25 4
B getAllMoChilds() 0 35 8
B getNextNumRef() 0 38 6
B getTooltipContentArray() 0 37 10
F validate() 0 117 19
F cancelConsumedAndProducedLines() 0 97 19
A getLinesArray() 0 19 3
B create() 0 48 11
F createFromClone() 0 92 19
A initAsSpecimen() 0 7 1
A getMoParent() 0 29 5
A generateDocument() 0 24 5
A fetchLines() 0 6 1
A info() 0 22 4
F printOriginLinesList() 0 59 17
B getMoChilds() 0 32 6
C getKanbanView() 0 37 10
A reopen() 0 15 3
A getLibStatut() 0 3 1
A fetchLinesLinked() 0 43 5
C __construct() 0 28 12
B cancel() 0 37 8
C fetchAll() 0 74 14

How to fix   Complexity   

Complex Class

Complex classes like Mo often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Mo, and based on these observations, apply Extract Interface, too.

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

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