Passed
Push — EXTRACT_CLASSES ( ff35ec...a2ff75 )
by Rafael
48:13
created

Productlot::create()   D

Complexity

Conditions 37
Paths 0

Size

Total Lines 119
Code Lines 78

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 37
eloc 78
nc 0
nop 2
dl 0
loc 119
rs 4.1666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* Copyright (C) 2007-2012  Laurent Destailleur         <[email protected]>
4
 * Copyright (C) 2014       Juanjo Menent               <[email protected]>
5
 * Copyright (C) 2015       Florian Henry               <[email protected]>
6
 * Copyright (C) 2015       Raphaël Doursenaud          <[email protected]>
7
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
8
 * Copyright (C) 2023	   	Gauthier VERDOL		        <[email protected]>
9
 * Copyright (C) 2024		MDW							<[email protected]>
10
 * Copyright (C) 2024       Rafael San José             <[email protected]>
11
 *
12
 * This program is free software; you can redistribute it and/or modify
13
 * it under the terms of the GNU General Public License as published by
14
 * the Free Software Foundation; either version 3 of the License, or
15
 * (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU General Public License
23
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
24
 */
25
26
namespace Dolibarr\Code\Product\Classes;
27
28
use Dolibarr\Core\Base\CommonObject;
29
30
/**
31
 * \file    product/stock/class/productlot.class.php
32
 * \ingroup stock
33
 * \brief   This is CRUD class file to manage table productlot (Create/Read/Update/Delete)
34
 */
35
36
// Put here all includes required by your class file
37
//require_once constant('DOL_DOCUMENT_ROOT') . '/societe/class/societe.class.php';
38
//require_once constant('DOL_DOCUMENT_ROOT') . '/product/class/product.class.php';
39
40
/**
41
 * Class with list of lots and properties
42
 */
43
class Productlot extends CommonObject
44
{
45
    /**
46
     * @var string Id to identify managed objects
47
     */
48
    public $element = 'productlot';
49
50
    /**
51
     * @var string Name of table without prefix where object is stored
52
     */
53
    public $table_element = 'product_lot';
54
55
    /**
56
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
57
     */
58
    public $picto = 'lot';
59
60
    public $stats_propale;
61
    public $stats_commande;
62
    public $stats_contrat;
63
    public $stats_facture;
64
    public $stats_commande_fournisseur;
65
66
    /**
67
     * @var array{customers:int,nb:int,rows:int,qty:int} stats_expedition
68
     */
69
    public $stats_expedition;
70
71
    /**
72
     * @var array{customers:int,nb:int,rows:int,qty:int} stats_expedition
73
     */
74
    public $stats_reception;
75
76
    /**
77
     * @var array{customers:int,nb:int,rows:int,qty:int} stats_expedition
78
     */
79
    public $stats_supplier_order;
80
81
    public $stats_mo;
82
    public $stats_bom;
83
    public $stats_mrptoconsume;
84
    public $stats_mrptoproduce;
85
    public $stats_facturerec;
86
    public $stats_facture_fournisseur;
87
88
89
    /**
90
     *  'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
91
     *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
92
     *  'label' the translation key.
93
     *  'enabled' is a condition when the field must be managed (Example: 1 or 'getDolGlobalString("MY_SETUP_PARAM")'
94
     *  'position' is the sort order of field.
95
     *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
96
     *  '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)
97
     *  'noteditable' says if field is not editable (1 or 0)
98
     *  '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.
99
     *  'index' if we want an index in database.
100
     *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommended to name the field fk_...).
101
     *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
102
     *  '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).
103
     *  'css' is the CSS style to use on field. For example: 'maxwidth200'
104
     *  'help' is a string visible as a tooltip on field
105
     *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
106
     *  '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.
107
     *  'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
108
     *  'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
109
     *  'comment' is not used. You can store here any text of your choice. It is not used by application.
110
     *
111
     *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
112
     */
113
114
    /**
115
     * @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...
116
     */
117
    public $fields = array(
118
        'rowid'         => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -2, 'noteditable' => 1, 'notnull' => 1, 'index' => 1, 'position' => 1, 'comment' => 'Id', 'css' => 'left'),
119
        'fk_product'    => array('type' => 'integer:Product:product/class/product.class.php', 'label' => 'Product', 'enabled' => 1, 'visible' => 1, 'position' => 5, 'notnull' => 1, 'index' => 1, 'searchall' => 1, 'picto' => 'product', 'css' => 'maxwidth500 widthcentpercentminusxx', 'csslist' => 'maxwidth150'),
120
        'batch'         => array('type' => 'varchar(30)', 'label' => 'Batch', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'showoncombobox' => 1, 'index' => 1, 'position' => 10, 'comment' => 'Batch', 'searchall' => 1, 'picto' => 'lot', 'validate' => 1),
121
        'entity'        => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'default' => '1', 'notnull' => 1, 'index' => 1, 'position' => 20),
122
        'sellby'        => array('type' => 'date', 'label' => 'SellByDate', 'enabled' => 'empty($conf->global->PRODUCT_DISABLE_SELLBY)?1:0', 'visible' => 1, 'notnull' => 0, 'position' => 60),
123
        'eatby'         => array('type' => 'date', 'label' => 'EatByDate', 'enabled' => 'empty($conf->global->PRODUCT_DISABLE_EATBY)?1:0', 'visible' => 1, 'notnull' => 0, 'position' => 62),
124
        'eol_date'        => array('type' => 'date', 'label' => 'EndOfLife', 'enabled' => 'getDolGlobalInt("PRODUCT_LOT_ENABLE_QUALITY_CONTROL")?1:0', 'visible' => 'getDolGlobalInt("PRODUCT_LOT_ENABLE_QUALITY_CONTROL")?5:0', 'position' => 70),
125
        'manufacturing_date' => array('type' => 'date', 'label' => 'ManufacturingDate', 'enabled' => 'getDolGlobalInt("PRODUCT_LOT_ENABLE_TRACEABILITY")?1:0', 'visible' => 'getDolGlobalInt("PRODUCT_LOT_ENABLE_TRACEABILITY")?5:0', 'position' => 80),
126
        'scrapping_date'     => array('type' => 'date', 'label' => 'DestructionDate', 'enabled' => 'getDolGlobalInt("PRODUCT_LOT_ENABLE_TRACEABILITY")?1:0', 'visible' => 'getDolGlobalInt("PRODUCT_LOT_ENABLE_TRACEABILITY")?5:0', 'position' => 90),
127
        //'commissionning_date'        => array('type'=>'date', 'label'=>'FirstUseDate', 'enabled'=>'getDolGlobalInt("PRODUCT_LOT_ENABLE_TRACEABILITY", 0)', 'visible'=>5, 'position'=>100),
128
        'qc_frequency'        => array('type' => 'integer', 'label' => 'QCFrequency', 'enabled' => 'getDolGlobalInt("PRODUCT_LOT_ENABLE_QUALITY_CONTROL")?1:0', 'visible' => 'getDolGlobalInt("PRODUCT_LOT_ENABLE_QUALITY_CONTROL")?5:0', 'position' => 110),
129
        'lifetime'        => array('type' => 'integer', 'label' => 'Lifetime', 'enabled' => 'getDolGlobalInt("PRODUCT_LOT_ENABLE_QUALITY_CONTROL")?1:0', 'visible' => 'getDolGlobalInt("PRODUCT_LOT_ENABLE_QUALITY_CONTROL")?5:0', 'position' => 110),
130
        'model_pdf'     => array('type' => 'varchar(255)', 'label' => 'Model pdf', 'enabled' => 1, 'visible' => 0, 'position' => 215),
131
        'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -2, 'position' => 310),
132
        'datec'         => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 500),
133
        'tms'           => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 501),
134
        'fk_user_creat' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 510, 'foreignkey' => 'llx_user.rowid'),
135
        'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 511),
136
        'import_key'    => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'index' => 0, 'position' => 1000)
137
    );
138
139
    /**
140
     * @var int Entity
141
     */
142
    public $entity;
143
144
    /**
145
     * @var int Product ID
146
     */
147
    public $fk_product;
148
149
    /**
150
     * @var string batch ref
151
     */
152
    public $batch;
153
154
    /**
155
     * @var int|string eatby
156
     */
157
    public $eatby = '';
158
159
    /**
160
     * @var int|string sellby
161
     */
162
    public $sellby = '';
163
164
    /**
165
     * @var int|string eal_date
166
     */
167
    public $eol_date = '';
168
169
    /**
170
     * @var int|string manufacturing_date
171
     */
172
    public $manufacturing_date = '';
173
174
    /**
175
     * @var int|string scrapping_date
176
     */
177
    public $scrapping_date = '';
178
    //public $commissionning_date = '';
179
    public $qc_frequency = '';
180
    public $lifetime = '';
181
    public $datec = '';
182
183
    /**
184
     * @var int user ID
185
     */
186
    public $fk_user_creat;
187
188
    /**
189
     * @var int user ID
190
     */
191
    public $fk_user_modif;
192
193
    /**
194
     * @var string import key
195
     */
196
    public $import_key;
197
198
199
    /**
200
     * Constructor
201
     *
202
     * @param DoliDB $db Database handler
203
     */
204
    public function __construct(DoliDB $db)
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Product\Classes\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
205
    {
206
        $this->db = $db;
207
208
        $this->ismultientitymanaged = 1;
209
    }
210
211
    /**
212
     * Check sell or eat by date is mandatory
213
     *
214
     * @param   string      $onlyFieldName      [=''] check all fields by default or only one field name ("sellby", "eatby")
215
     * @return  int         Return integer <0 if KO, 0 nothing done, >0 if OK
216
     */
217
    public function checkSellOrEatByMandatory($onlyFieldName = '')
218
    {
219
        if (getDolGlobalString('PRODUCT_DISABLE_SELLBY') && getDolGlobalString('PRODUCT_DISABLE_EATBY')) {
220
            return 0;
221
        }
222
223
        $errorMsgArr = array();
224
        if ($this->fk_product > 0) {
225
            $res = $this->fetch_product();
226
            $product = $this->product;
227
            if ($res <= 0) {
228
                $errorMsgArr[] = $product->errorsToString();
229
            }
230
231
            if (empty($errorMsgArr)) {
232
                $errorMsgArr = self::checkSellOrEatByMandatoryFromProductAndDates($product, $this->sellby, $this->eatby, $onlyFieldName, true);
233
            }
234
        }
235
236
        if (!empty($errorMsgArr)) {
237
            $this->errors = array_merge($this->errors, $errorMsgArr);
238
            return -1;
239
        } else {
240
            return 1;
241
        }
242
    }
243
244
    /**
245
     * Check sell or eat by date is mandatory from product id and sell-by and eat-by dates
246
     *
247
     * @param   int         $productId          Product id
248
     * @param   int         $sellBy             Sell by date
249
     * @param   int         $eatBy              Eat by date
250
     * @param   string      $onlyFieldName      [=''] check all fields by default or only one field name ("sellby", "eatby")
251
     * @return  array|null  Array of errors or null if nothing done
252
     */
253
    public static function checkSellOrEatByMandatoryFromProductIdAndDates($productId, $sellBy, $eatBy, $onlyFieldName = '')
254
    {
255
        global $db;
256
257
        if (getDolGlobalString('PRODUCT_DISABLE_SELLBY') && getDolGlobalString('PRODUCT_DISABLE_EATBY')) {
258
            return null;
259
        }
260
261
        $errorMsgArr = array();
262
        if ($productId > 0) {
263
            $product = new Product($db);
264
            $res = $product->fetch($productId);
265
            if ($res <= 0) {
266
                $errorMsgArr[] = $product->errorsToString();
267
            }
268
269
            if (empty($errorMsgArr)) {
270
                $errorMsgArr = self::checkSellOrEatByMandatoryFromProductAndDates($product, $sellBy, $eatBy, $onlyFieldName, true);
271
            }
272
        }
273
274
        return $errorMsgArr;
275
    }
276
277
    /**
278
     * Check sell or eat by date is mandatory from product and sell-by and eat-by dates
279
     *
280
     * @param   Product     $product            Product object
281
     * @param   int         $sellBy             Sell by date
282
     * @param   int         $eatBy              Eat by date
283
     * @param   string      $onlyFieldName      [=''] check all fields by default or only one field name ("sellby", "eatby")
284
     * @param   bool        $alreadyCheckConf   [=false] conf hasn't been already checked by default or true not to check conf
285
     * @return  array|null  Array of errors or null if nothing done
286
     */
287
    public static function checkSellOrEatByMandatoryFromProductAndDates($product, $sellBy, $eatBy, $onlyFieldName = '', $alreadyCheckConf = false)
288
    {
289
        global $langs;
290
291
        if ($alreadyCheckConf === false && getDolGlobalString('PRODUCT_DISABLE_SELLBY') && getDolGlobalString('PRODUCT_DISABLE_EATBY')) {
292
            return null;
293
        }
294
295
        $errorMsgArr = array();
296
        $checkSellByMandatory = false;
297
        $checkEatByMandatory = false;
298
299
        $sellOrEatByMandatoryId = $product->sell_or_eat_by_mandatory;
300
        if (!getDolGlobalString('PRODUCT_DISABLE_SELLBY') && $sellOrEatByMandatoryId == Product::SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY && ($onlyFieldName == '' || $onlyFieldName == 'sellby')) {
301
            $checkSellByMandatory = true;
302
        } elseif (!getDolGlobalString('PRODUCT_DISABLE_EATBY') && $sellOrEatByMandatoryId == Product::SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY && ($onlyFieldName == '' || $onlyFieldName == 'eatby')) {
303
            $checkEatByMandatory = true;
304
        } elseif ($sellOrEatByMandatoryId == Product::SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT) {
305
            if (!getDolGlobalString('PRODUCT_DISABLE_SELLBY') && ($onlyFieldName == '' || $onlyFieldName == 'sellby')) {
306
                $checkSellByMandatory = true;
307
            }
308
            if (!getDolGlobalString('PRODUCT_DISABLE_EATBY') && ($onlyFieldName == '' || $onlyFieldName == 'eatby')) {
309
                $checkEatByMandatory = true;
310
            }
311
        }
312
313
        if ($checkSellByMandatory === true) {
314
            if (!isset($sellBy) || dol_strlen($sellBy) == 0) {
315
                // error : sell by is mandatory
316
                $errorMsgArr[] = $langs->trans('ErrorFieldRequired', $langs->transnoentities('SellByDate'));
317
            }
318
        }
319
        if ($checkEatByMandatory === true) {
320
            if (!isset($eatBy) || dol_strlen($eatBy) == 0) {
321
                // error : eat by is mandatory
322
                $errorMsgArr[] = $langs->trans('ErrorFieldRequired', $langs->transnoentities('EatByDate'));
323
            }
324
        }
325
326
        return $errorMsgArr;
327
    }
328
329
330
    /**
331
     * Create object into database
332
     *
333
     * @param  User $user       User that creates
334
     * @param  int  $notrigger  0=launch triggers after, 1=disable triggers
335
     * @return int              Return integer <0 if KO, Id of created object if OK
336
     */
337
    public function create(User $user, $notrigger = 0)
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Product\Classes\User was not found. Did you mean User? If so, make sure to prefix the type with \.
Loading history...
338
    {
339
        global $conf, $langs;
340
341
        dol_syslog(__METHOD__, LOG_DEBUG);
342
343
        $error = 0;
344
345
        // Clean parameters
346
347
        if (isset($this->entity)) {
348
            $this->entity = (int) $this->entity;
349
        }
350
        if (isset($this->fk_product)) {
351
            $this->fk_product = (int) $this->fk_product;
352
        }
353
        if (isset($this->batch)) {
354
            $this->batch = trim($this->batch);
355
        }
356
        if (isset($this->fk_user_creat)) {
357
            $this->fk_user_creat = (int) $this->fk_user_creat;
358
        }
359
        if (isset($this->fk_user_modif)) {
360
            $this->fk_user_modif = (int) $this->fk_user_modif;
361
        }
362
        if (isset($this->import_key)) {
363
            $this->import_key = trim($this->import_key);
364
        }
365
366
        // Check parameters
367
        if ($this->batch === '') {
368
            $this->errors[] = $langs->trans("ErrorBadValueForBatch");
369
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
370
            return -1;
371
        }
372
        // Put here code to add control on parameters values
373
        $res = $this->checkSellOrEatByMandatory();
374
        if ($res < 0) {
375
            $error++;
376
        }
377
378
        if (!$error) {
379
            // Insert request
380
            $sql = 'INSERT INTO ' . $this->db->prefix() . $this->table_element . '(';
381
            $sql .= 'entity,';
382
            $sql .= 'fk_product,';
383
            $sql .= 'batch,';
384
            $sql .= 'eatby,';
385
            $sql .= 'sellby,';
386
            $sql .= 'eol_date,';
387
            $sql .= 'manufacturing_date,';
388
            $sql .= 'scrapping_date,';
389
            //$sql .= 'commissionning_date,';
390
            $sql .= 'qc_frequency,';
391
            $sql .= 'lifetime,';
392
            $sql .= 'datec,';
393
            $sql .= 'fk_user_creat,';
394
            $sql .= 'fk_user_modif,';
395
            $sql .= 'import_key';
396
            $sql .= ') VALUES (';
397
            $sql .= ' ' . (!isset($this->entity) ? $conf->entity : $this->entity) . ',';
398
            $sql .= ' ' . (!isset($this->fk_product) ? 'NULL' : $this->fk_product) . ',';
399
            $sql .= ' ' . (!isset($this->batch) ? 'NULL' : "'" . $this->db->escape($this->batch) . "'") . ',';
400
            $sql .= ' ' . (!isset($this->eatby) || dol_strlen($this->eatby) == 0 ? 'NULL' : "'" . $this->db->idate($this->eatby) . "'") . ',';
401
            $sql .= ' ' . (!isset($this->sellby) || dol_strlen($this->sellby) == 0 ? 'NULL' : "'" . $this->db->idate($this->sellby) . "'") . ',';
402
            $sql .= ' ' . (!isset($this->eol_date) || dol_strlen($this->eol_date) == 0 ? 'NULL' : "'" . $this->db->idate($this->eol_date) . "'") . ',';
403
            $sql .= ' ' . (!isset($this->manufacturing_date) || dol_strlen($this->manufacturing_date) == 0 ? 'NULL' : "'" . $this->db->idate($this->manufacturing_date) . "'") . ',';
404
            $sql .= ' ' . (!isset($this->scrapping_date) || dol_strlen($this->scrapping_date) == 0 ? 'NULL' : "'" . $this->db->idate($this->scrapping_date) . "'") . ',';
405
            //$sql .= ' '.(!isset($this->commissionning_date) || dol_strlen($this->commissionning_date) == 0 ? 'NULL' : "'".$this->db->idate($this->commissionning_date)."'").',';
406
            $sql .= ' ' . (empty($this->qc_frequency) ? 'NULL' : $this->qc_frequency) . ',';
407
            $sql .= ' ' . (empty($this->lifetime) ? 'NULL' : $this->lifetime) . ',';
408
            $sql .= ' ' . "'" . $this->db->idate(dol_now()) . "'" . ',';
409
            $sql .= ' ' . (!isset($this->fk_user_creat) ? 'NULL' : $this->fk_user_creat) . ',';
410
            $sql .= ' ' . (!isset($this->fk_user_modif) ? 'NULL' : $this->fk_user_modif) . ',';
411
            $sql .= ' ' . (!isset($this->import_key) ? 'NULL' : $this->import_key);
412
            $sql .= ')';
413
414
            $this->db->begin();
415
416
            $resql = $this->db->query($sql);
417
            if (!$resql) {
418
                $error++;
419
                $this->errors[] = 'Error ' . $this->db->lasterror();
420
            }
421
422
            if (!$error) {
423
                $this->id = $this->db->last_insert_id($this->db->prefix() . $this->table_element);
424
425
                // Actions on extra fields
426
                if (!$error) {
427
                    $result = $this->insertExtraFields();
428
                    if ($result < 0) {
429
                        $error++;
430
                    }
431
                }
432
433
                if (!$error && !$notrigger) {
434
                    // Call triggers
435
                    $result = $this->call_trigger('PRODUCTLOT_CREATE', $user);
436
                    if ($result < 0) {
437
                        $error++;
438
                    }
439
                    // End call triggers
440
                }
441
            }
442
443
            // Commit or rollback
444
            if ($error) {
445
                $this->db->rollback();
446
            } else {
447
                $this->db->commit();
448
            }
449
        }
450
451
        if ($error) {
452
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
453
            return -1 * $error;
454
        } else {
455
            return $this->id;
456
        }
457
    }
458
459
    /**
460
     * Load object in memory from the database
461
     *
462
     * @param int    $id            Id of lot/batch
463
     * @param int    $product_id    Id of product, batch number parameter required
464
     * @param string $batch         batch number
465
     *
466
     * @return int Return integer <0 if KO, 0 if not found, >0 if OK
467
     */
468
    public function fetch($id = 0, $product_id = 0, $batch = '')
469
    {
470
        global $conf;
471
        dol_syslog(__METHOD__, LOG_DEBUG);
472
473
        $sql = "SELECT";
474
        $sql .= " t.rowid,";
475
        $sql .= " t.entity,";
476
        $sql .= " t.fk_product,";
477
        $sql .= " t.batch,";
478
        $sql .= " t.eatby,";
479
        $sql .= " t.sellby,";
480
        $sql .= " t.eol_date,";
481
        $sql .= " t.manufacturing_date,";
482
        $sql .= " t.scrapping_date,";
483
        //$sql .= " t.commissionning_date,";
484
        $sql .= " t.qc_frequency,";
485
        $sql .= " t.lifetime,";
486
        $sql .= " t.model_pdf,";
487
        $sql .= " t.last_main_doc,";
488
        $sql .= " t.datec,";
489
        $sql .= " t.tms,";
490
        $sql .= " t.fk_user_creat,";
491
        $sql .= " t.fk_user_modif,";
492
        $sql .= " t.import_key,";
493
        $sql .= " t.note_public,";
494
        $sql .= " t.note_private";
495
        $sql .= " FROM " . $this->db->prefix() . $this->table_element . " as t";
496
        if ($product_id > 0 && $batch != '') {
497
            $sql .= " WHERE t.batch = '" . $this->db->escape($batch) . "' AND t.fk_product = " . ((int) $product_id);
498
        } else {
499
            $sql .= " WHERE t.rowid = " . ((int) $id);
500
        }
501
502
        $resql = $this->db->query($sql);
503
        if ($resql) {
504
            $numrows = $this->db->num_rows($resql);
505
            if ($numrows) {
506
                $obj = $this->db->fetch_object($resql);
507
508
                $this->id = $obj->rowid;
509
                $this->ref = $obj->rowid;
510
                //$this->ref = $obj->fk_product.'_'.$obj->batch;
511
512
                $this->batch = $obj->batch;
513
                $this->entity = (!empty($obj->entity) ? $obj->entity : $conf->entity); // Prevent "null" entity
514
                $this->fk_product = $obj->fk_product;
515
                $this->eatby = $this->db->jdate($obj->eatby);
516
                $this->sellby = $this->db->jdate($obj->sellby);
517
                $this->eol_date = $this->db->jdate($obj->eol_date);
518
                $this->manufacturing_date = $this->db->jdate($obj->manufacturing_date);
519
                $this->scrapping_date = $this->db->jdate($obj->scrapping_date);
520
                //$this->commissionning_date = $this->db->jdate($obj->commissionning_date);
521
                $this->qc_frequency = $obj->qc_frequency;
522
                $this->lifetime = $obj->lifetime;
523
                $this->model_pdf = $obj->model_pdf;
524
                $this->last_main_doc = $obj->last_main_doc;
525
526
                $this->datec = $this->db->jdate($obj->datec);
527
                $this->tms = $this->db->jdate($obj->tms);
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$tms has been deprecated: Use $date_modification ( Ignorable by Annotation )

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

527
                /** @scrutinizer ignore-deprecated */ $this->tms = $this->db->jdate($obj->tms);

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

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

Loading history...
528
                $this->fk_user_creat = $obj->fk_user_creat;
529
                $this->fk_user_modif = $obj->fk_user_modif;
530
                $this->import_key = $obj->import_key;
531
                $this->note_public = $obj->note_public;
532
                $this->note_private = $obj->note_private;
533
534
                // Retrieve all extrafield
535
                // fetch optionals attributes and labels
536
                $this->fetch_optionals();
537
            }
538
            $this->db->free($resql);
539
540
            if ($numrows) {
541
                return 1;
542
            } else {
543
                return 0;
544
            }
545
        } else {
546
            $this->errors[] = 'Error ' . $this->db->lasterror();
547
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
548
549
            return -1;
550
        }
551
    }
552
553
    /**
554
     * Update object into database
555
     *
556
     * @param  User $user       User that modifies
557
     * @param  int  $notrigger  0=launch triggers after, 1=disable triggers
558
     * @return int              Return integer <0 if KO, >0 if OK
559
     */
560
    public function update(User $user, $notrigger = 0)
561
    {
562
        $error = 0;
563
564
        dol_syslog(__METHOD__, LOG_DEBUG);
565
566
        // Clean parameters
567
568
        if (isset($this->entity)) {
569
            $this->entity = (int) $this->entity;
570
        }
571
        if (isset($this->fk_product)) {
572
            $this->fk_product = (int) $this->fk_product;
573
        }
574
        if (isset($this->batch)) {
575
            $this->batch = trim($this->batch);
576
        }
577
        if (isset($this->fk_user_creat)) {
578
            $this->fk_user_creat = (int) $this->fk_user_creat;
579
        }
580
        if (isset($this->fk_user_modif)) {
581
            $this->fk_user_modif = (int) $this->fk_user_modif;
582
        }
583
        if (isset($this->import_key)) {
584
            $this->import_key = trim($this->import_key);
585
        }
586
587
        // Check parameters
588
        // Put here code to add a control on parameters values
589
        $res = $this->checkSellOrEatByMandatory();
590
        if ($res < 0) {
591
            $error++;
592
        }
593
594
        // $this->oldcopy should have been set by the caller of update (here properties were already modified)
595
        if (empty($this->oldcopy)) {
596
            $this->oldcopy = dol_clone($this, 2);
597
        }
598
599
        if (!$error) {
600
            // Update request
601
            $sql = 'UPDATE ' . $this->db->prefix() . $this->table_element . ' SET';
602
            $sql .= ' entity = ' . (isset($this->entity) ? $this->entity : "null") . ',';
603
            $sql .= ' fk_product = ' . (isset($this->fk_product) ? $this->fk_product : "null") . ',';
604
            $sql .= ' batch = ' . (isset($this->batch) ? "'" . $this->db->escape($this->batch) . "'" : "null") . ',';
605
            $sql .= ' eatby = ' . (!isset($this->eatby) || dol_strlen($this->eatby) != 0 ? "'" . $this->db->idate($this->eatby) . "'" : 'null') . ',';
606
            $sql .= ' sellby = ' . (!isset($this->sellby) || dol_strlen($this->sellby) != 0 ? "'" . $this->db->idate($this->sellby) . "'" : 'null') . ',';
607
            $sql .= ' eol_date = ' . (!isset($this->eol_date) || dol_strlen($this->eol_date) != 0 ? "'" . $this->db->idate($this->eol_date) . "'" : 'null') . ',';
608
            $sql .= ' manufacturing_date = ' . (!isset($this->manufacturing_date) || dol_strlen($this->manufacturing_date) != 0 ? "'" . $this->db->idate($this->manufacturing_date) . "'" : 'null') . ',';
609
            $sql .= ' scrapping_date = ' . (!isset($this->scrapping_date) || dol_strlen($this->scrapping_date) != 0 ? "'" . $this->db->idate($this->scrapping_date) . "'" : 'null') . ',';
610
            //$sql .= ' commissionning_date = '.(!isset($this->first_use_date) || dol_strlen($this->first_use_date) != 0 ? "'".$this->db->idate($this->first_use_date)."'" : 'null').',';
611
            $sql .= ' qc_frequency = ' . (!empty($this->qc_frequency) ? (int) $this->qc_frequency : 'null') . ',';
612
            $sql .= ' lifetime = ' . (!empty($this->lifetime) ? (int) $this->lifetime : 'null') . ',';
613
            $sql .= ' datec = ' . (!isset($this->datec) || dol_strlen($this->datec) != 0 ? "'" . $this->db->idate($this->datec) . "'" : 'null') . ',';
614
            $sql .= ' tms = ' . (dol_strlen($this->tms) != 0 ? "'" . $this->db->idate($this->tms) . "'" : "'" . $this->db->idate(dol_now()) . "'") . ',';
615
            $sql .= ' fk_user_creat = ' . (isset($this->fk_user_creat) ? $this->fk_user_creat : "null") . ',';
616
            $sql .= ' fk_user_modif = ' . (isset($this->fk_user_modif) ? $this->fk_user_modif : "null") . ',';
617
            $sql .= ' import_key = ' . (isset($this->import_key) ? $this->import_key : "null");
618
            $sql .= ' WHERE rowid=' . ((int) $this->id);
619
620
            $this->db->begin();
621
622
            $resql = $this->db->query($sql);
623
            if (!$resql) {
624
                $error++;
625
                $this->errors[] = 'Error ' . $this->db->lasterror();
626
            }
627
628
            // Actions on extra fields
629
            if (!$error) {
630
                $result = $this->insertExtraFields();
631
                if ($result < 0) {
632
                    $error++;
633
                }
634
            }
635
636
            if (!$error && !$notrigger) {
637
                // Call triggers
638
                $result = $this->call_trigger('PRODUCTLOT_MODIFY', $user);
639
                if ($result < 0) {
640
                    $error++;
641
                }
642
                // End call triggers
643
            }
644
645
            // Commit or rollback
646
            if ($error) {
647
                $this->db->rollback();
648
            } else {
649
                $this->db->commit();
650
            }
651
        }
652
653
        if ($error) {
654
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
655
            return -1 * $error;
656
        } else {
657
            return 1;
658
        }
659
    }
660
661
    /**
662
     * Delete object in database
663
     *
664
     * @param User  $user       User that deletes
665
     * @param int   $notrigger  0=launch triggers after, 1=disable triggers
666
     * @return int              Return integer <0 if KO, >0 if OK
667
     */
668
    public function delete(User $user, $notrigger = 0)
669
    {
670
        dol_syslog(__METHOD__, LOG_DEBUG);
671
672
        $error = 0;
673
674
        $this->db->begin();
675
676
        // Check there is no stock for this lot
677
        $sql = "SELECT pb.rowid FROM " . $this->db->prefix() . "product_batch as pb, " . $this->db->prefix() . "product_stock as ps";
678
        $sql .= " WHERE pb.fk_product_stock = ps.rowid AND pb.batch = '" . $this->db->escape($this->batch) . "'";
679
        $sql .= " AND ps.fk_product = " . ((int) $this->fk_product);
680
        $sql .= $this->db->plimit(1);
681
682
        $resql = $this->db->query($sql);
683
        if ($resql) {
684
            $obj = $this->db->fetch_object($resql);
685
            if ($obj) {
686
                $error++;
687
                $this->errors[] = 'Error Lot is used in stock (ID = ' . $obj->rowid . '). Deletion not possible.';
688
                dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
689
            }
690
        } else {
691
            $error++;
692
            $this->errors[] = 'Error ' . $this->db->lasterror();
693
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
694
        }
695
696
        // Check there is no movement for this lot
697
        $sql = "SELECT sm.rowid FROM " . $this->db->prefix() . "stock_mouvement as sm";
698
        $sql .= " WHERE sm.batch = '" . $this->db->escape($this->batch) . "'";
699
        $sql .= " AND sm.fk_product = " . ((int) $this->fk_product);
700
        $sql .= $this->db->plimit(1);
701
702
        $resql = $this->db->query($sql);
703
        if ($resql) {
704
            $obj = $this->db->fetch_object($resql);
705
            if ($obj) {
706
                $error++;
707
                $this->errors[] = 'Error Lot was used in a stock movement (ID ' . $obj->rowid . '). Deletion not possible.';
708
                dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
709
            }
710
        } else {
711
            $error++;
712
            $this->errors[] = 'Error ' . $this->db->lasterror();
713
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
714
        }
715
716
        // TODO
717
        //if (!$error) {
718
        //if (!$notrigger) {
719
        // Uncomment this and change PRODUCTLOT to your own tag if you
720
        // want this action calls a trigger.
721
722
        //// Call triggers
723
        //$result=$this->call_trigger('PRODUCTLOT_DELETE',$user);
724
        //if ($result < 0) { $error++; //Do also what you must do to rollback action if trigger fail}
725
        //// End call triggers
726
        //}
727
        //}
728
729
        if (!$error) {
730
            $sql = 'DELETE FROM ' . $this->db->prefix() . $this->table_element;
731
            $sql .= ' WHERE rowid=' . ((int) $this->id);
732
733
            $resql = $this->db->query($sql);
734
            if (!$resql) {
735
                $error++;
736
                $this->errors[] = 'Error ' . $this->db->lasterror();
737
                dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
738
            }
739
        }
740
741
        // Commit or rollback
742
        if ($error) {
743
            $this->db->rollback();
744
745
            return -1 * $error;
746
        } else {
747
            $this->db->commit();
748
749
            return 1;
750
        }
751
    }
752
753
    /**
754
     * Load an object from its id and create a new one in database
755
     *
756
     * @param   User    $user       User making the clone
757
     * @param   int     $fromid     Id of object to clone
758
     * @return  int                 New id of clone
759
     */
760
    public function createFromClone(User $user, $fromid)
761
    {
762
        dol_syslog(__METHOD__, LOG_DEBUG);
763
764
        $error = 0;
765
        $object = new Productlot($this->db);
766
767
        $this->db->begin();
768
769
        // Load source object
770
        $object->fetch($fromid);
771
        // Reset object
772
        $object->id = 0;
773
774
        // Clear fields
775
        // ...
776
777
        // Create clone
778
        $object->context['createfromclone'] = 'createfromclone';
779
        $result = $object->create($user);
780
781
        // Other options
782
        if ($result < 0) {
783
            $error++;
784
            $this->errors = $object->errors;
785
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
786
        }
787
788
        unset($object->context['createfromclone']);
789
790
        // End
791
        if (!$error) {
792
            $this->db->commit();
793
794
            return $object->id;
795
        } else {
796
            $this->db->rollback();
797
798
            return -1;
799
        }
800
    }
801
802
    /**
803
     *  Charge tableau des stats expedition pour le lot/numéro de série
804
     *
805
     * @param  int $socid Id societe
806
     * @return int                     Array of stats in $this->stats_expedition, <0 if ko or >0 if ok
807
     */
808
    public function loadStatsExpedition($socid = 0)
809
    {
810
        // phpcs:enable
811
        global $user, $hookmanager, $action;
812
813
        $sql = "SELECT COUNT(DISTINCT exp.fk_soc) as nb_customers, COUNT(DISTINCT exp.rowid) as nb,";
814
        $sql .= " COUNT(ed.rowid) as nb_rows, SUM(edb.qty) as qty";
815
        $sql .= " FROM " . $this->db->prefix() . "expeditiondet_batch as edb";
816
        $sql .= " INNER JOIN " . $this->db->prefix() . "expeditiondet as ed ON (ed.rowid = edb.fk_expeditiondet)";
817
        $sql .= " INNER JOIN " . $this->db->prefix() . "expedition as exp ON (exp.rowid = ed.fk_expedition)";
818
        //      $sql .= ", ".$this->db->prefix()."societe as s";
819
        if (!$user->hasRight('societe', 'client', 'voir')) {
820
            $sql .= ", " . $this->db->prefix() . "societe_commerciaux as sc";
821
        }
822
        $sql .= " WHERE exp.entity IN (" . getEntity('expedition') . ")";
823
        $sql .= " AND edb.batch = '" . ($this->db->escape($this->batch)) . "'";
824
        if (!$user->hasRight('societe', 'client', 'voir')) {
825
            $sql .= " AND exp.fk_soc = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
826
        }
827
        //$sql.= " AND exp.fk_statut != 0";
828
        if ($socid > 0) {
829
            $sql .= " AND exp.fk_soc = " . ((int) $socid);
830
        }
831
832
        $result = $this->db->query($sql);
833
        if ($result) {
834
            $obj = $this->db->fetch_object($result);
835
            $this->stats_expedition['customers'] = $obj->nb_customers;
836
            $this->stats_expedition['nb'] = $obj->nb;
837
            $this->stats_expedition['rows'] = $obj->nb_rows;
838
            $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
839
840
841
            // Virtual products can't be used with kits (see langs with key ErrorNoteAlsoThatSubProductCantBeFollowedByLot)
842
843
            // if it's a virtual product, maybe it is in invoice by extension
844
            //          if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
845
            //              $TFather = $this->getFather();
846
            //              if (is_array($TFather) && !empty($TFather)) {
847
            //                  foreach ($TFather as &$fatherData) {
848
            //                      $pFather = new Product($this->db);
849
            //                      $pFather->id = $fatherData['id'];
850
            //                      $qtyCoef = $fatherData['qty'];
851
            //
852
            //                      if ($fatherData['incdec']) {
853
            //                          $pFather->loadStatsExpedition($socid);
854
            //
855
            //                          $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
856
            //                          $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
857
            //                          $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
858
            //                          $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
859
            //                      }
860
            //                  }
861
            //              }
862
            //          }
863
864
            $parameters = array('socid' => $socid);
865
            $reshook = $hookmanager->executeHooks('loadStatsLotExpedition', $parameters, $this, $action);
866
            if ($reshook > 0) {
867
                $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
868
            }
869
870
            return 1;
871
        } else {
872
            $this->error = $this->db->error();
873
            return -1;
874
        }
875
    }
876
877
    /**
878
     *  Charge tableau des stats commande fournisseur pour le lot/numéro de série
879
     *
880
     * @param  int $socid Id societe
881
     * @return int                     Array of stats in $this->stats_expedition, <0 if ko or >0 if ok
882
     */
883
    public function loadStatsSupplierOrder($socid = 0)
884
    {
885
        // phpcs:enable
886
        global $user, $hookmanager, $action;
887
888
        $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_customers, COUNT(DISTINCT cf.rowid) as nb,";
889
        $sql .= " COUNT(cfd.rowid) as nb_rows, SUM(cfdi.qty) as qty";
890
        $sql .= " FROM " . $this->db->prefix() . "receptiondet_batch as cfdi";
891
        $sql .= " INNER JOIN " . $this->db->prefix() . "commande_fournisseurdet as cfd ON (cfd.rowid = cfdi.fk_elementdet)";
892
        $sql .= " INNER JOIN " . $this->db->prefix() . "commande_fournisseur as cf ON (cf.rowid = cfd.fk_commande)";
893
        //      $sql .= ", ".$this->db->prefix()."societe as s";
894
        if (!$user->hasRight('societe', 'client', 'voir')) {
895
            $sql .= ", " . $this->db->prefix() . "societe_commerciaux as sc";
896
        }
897
        $sql .= " WHERE cf.entity IN (" . getEntity('expedition') . ")";
898
        $sql .= " AND cfdi.batch = '" . ($this->db->escape($this->batch)) . "'";
899
        if (!$user->hasRight('societe', 'client', 'voir')) {
900
            $sql .= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
901
        }
902
        //$sql.= " AND cf.fk_statut != 0";
903
        if ($socid > 0) {
904
            $sql .= " AND cf.fk_soc = " . ((int) $socid);
905
        }
906
907
        $result = $this->db->query($sql);
908
        if ($result) {
909
            $obj = $this->db->fetch_object($result);
910
            $this->stats_supplier_order['customers'] = $obj->nb_customers;
911
            $this->stats_supplier_order['nb'] = $obj->nb;
912
            $this->stats_supplier_order['rows'] = $obj->nb_rows;
913
            $this->stats_supplier_order['qty'] = $obj->qty ? $obj->qty : 0;
914
915
916
            // Virtual products can't be used with kits (see langs with key ErrorNoteAlsoThatSubProductCantBeFollowedByLot)
917
918
            // if it's a virtual product, maybe it is in invoice by extension
919
            //          if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
920
            //              $TFather = $this->getFather();
921
            //              if (is_array($TFather) && !empty($TFather)) {
922
            //                  foreach ($TFather as &$fatherData) {
923
            //                      $pFather = new Product($this->db);
924
            //                      $pFather->id = $fatherData['id'];
925
            //                      $qtyCoef = $fatherData['qty'];
926
            //
927
            //                      if ($fatherData['incdec']) {
928
            //                          $pFather->stats_supplier_order($socid);
929
            //
930
            //                          $this->stats_supplier_order['customers'] += $pFather->stats_supplier_order['customers'];
931
            //                          $this->stats_supplier_order['nb'] += $pFather->stats_supplier_order['nb'];
932
            //                          $this->stats_supplier_order['rows'] += $pFather->stats_supplier_order['rows'];
933
            //                          $this->stats_supplier_order['qty'] += $pFather->stats_supplier_order['qty'] * $qtyCoef;
934
            //                      }
935
            //                  }
936
            //              }
937
            //          }
938
939
            $parameters = array('socid' => $socid);
940
            $reshook = $hookmanager->executeHooks('loadStatsLotSupplierOrder', $parameters, $this, $action);
941
            if ($reshook > 0) {
942
                $this->stats_supplier_order = $hookmanager->resArray['stats_supplier_order'];
943
            }
944
945
            return 1;
946
        } else {
947
            $this->error = $this->db->error();
948
            return -1;
949
        }
950
    }
951
952
    /**
953
     *  Charge tableau des stats expedition pour le lot/numéro de série
954
     *
955
     * @param  int $socid Id societe
956
     * @return int                     Array of stats in $this->stats_expedition, <0 if ko or >0 if ok
957
     */
958
    public function loadStatsReception($socid = 0)
959
    {
960
        // phpcs:enable
961
        global $user, $hookmanager, $action;
962
963
        $sql = "SELECT COUNT(DISTINCT recep.fk_soc) as nb_customers, COUNT(DISTINCT recep.rowid) as nb,";
964
        $sql .= " COUNT(cfdi.rowid) as nb_rows, SUM(cfdi.qty) as qty";
965
        $sql .= " FROM " . $this->db->prefix() . "receptiondet_batch as cfdi";
966
        $sql .= " INNER JOIN " . $this->db->prefix() . "reception as recep ON (recep.rowid = cfdi.fk_reception)";
967
        //      $sql .= ", ".$this->db->prefix()."societe as s";
968
        if (!$user->hasRight('societe', 'client', 'voir')) {
969
            $sql .= ", " . $this->db->prefix() . "societe_commerciaux as sc";
970
        }
971
        $sql .= " WHERE recep.entity IN (" . getEntity('reception') . ")";
972
        $sql .= " AND cfdi.batch = '" . ($this->db->escape($this->batch)) . "'";
973
        if (!$user->hasRight('societe', 'client', 'voir')) {
974
            $sql .= " AND recep.fk_soc = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
975
        }
976
        //$sql.= " AND exp.fk_statut != 0";
977
        if ($socid > 0) {
978
            $sql .= " AND recep.fk_soc = " . ((int) $socid);
979
        }
980
981
        $result = $this->db->query($sql);
982
        if ($result) {
983
            $obj = $this->db->fetch_object($result);
984
            $this->stats_reception['customers'] = $obj->nb_customers;
985
            $this->stats_reception['nb'] = $obj->nb;
986
            $this->stats_reception['rows'] = $obj->nb_rows;
987
            $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
988
989
990
            // Virtual products can't be used with kits (see langs with key ErrorNoteAlsoThatSubProductCantBeFollowedByLot)
991
992
            // if it's a virtual product, maybe it is in invoice by extension
993
            //          if (!empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
994
            //              $TFather = $this->getFather();
995
            //              if (is_array($TFather) && !empty($TFather)) {
996
            //                  foreach ($TFather as &$fatherData) {
997
            //                      $pFather = new Product($this->db);
998
            //                      $pFather->id = $fatherData['id'];
999
            //                      $qtyCoef = $fatherData['qty'];
1000
            //
1001
            //                      if ($fatherData['incdec']) {
1002
            //                          $pFather->loadStatsReception($socid);
1003
            //
1004
            //                          $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
1005
            //                          $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
1006
            //                          $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
1007
            //                          $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
1008
            //                      }
1009
            //                  }
1010
            //              }
1011
            //          }
1012
1013
            $parameters = array('socid' => $socid);
1014
            $reshook = $hookmanager->executeHooks('loadStatsLotReception', $parameters, $this, $action);
1015
            if ($reshook > 0) {
1016
                $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
1017
            }
1018
1019
            return 1;
1020
        } else {
1021
            $this->error = $this->db->error();
1022
            return -1;
1023
        }
1024
    }
1025
1026
    /**
1027
     *  Charge tableau des stats expedition pour le lot/numéro de série
1028
     *
1029
     * @param  int $socid Id societe
1030
     * @return int                     Array of stats in $this->stats_expedition, <0 if ko or >0 if ok
1031
     */
1032
    public function loadStatsMo($socid = 0)
1033
    {
1034
        // phpcs:enable
1035
        global $user, $hookmanager, $action;
1036
1037
        $error = 0;
1038
1039
        foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
1040
            $this->stats_mo['customers_' . $role] = 0;
1041
            $this->stats_mo['nb_' . $role] = 0;
1042
            $this->stats_mo['qty_' . $role] = 0;
1043
1044
            $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
1045
            $sql .= " SUM(mp.qty) as qty";
1046
            $sql .= " FROM " . $this->db->prefix() . "mrp_mo as c";
1047
            $sql .= " INNER JOIN " . $this->db->prefix() . "mrp_production as mp ON mp.fk_mo=c.rowid";
1048
            if (!$user->hasRight('societe', 'client', 'voir')) {
1049
                $sql .= "INNER JOIN " . $this->db->prefix() . "societe_commerciaux as sc ON sc.fk_soc=c.fk_soc AND sc.fk_user = " . ((int) $user->id);
1050
            }
1051
            $sql .= " WHERE ";
1052
            $sql .= " c.entity IN (" . getEntity('mo') . ")";
1053
1054
            $sql .= " AND mp.batch = '" . ($this->db->escape($this->batch)) . "'";
1055
            $sql .= " AND mp.role ='" . $this->db->escape($role) . "'";
1056
            if ($socid > 0) {
1057
                $sql .= " AND c.fk_soc = " . ((int) $socid);
1058
            }
1059
1060
            $result = $this->db->query($sql);
1061
            if ($result) {
1062
                $obj = $this->db->fetch_object($result);
1063
                $this->stats_mo['customers_' . $role] = $obj->nb_customers ? $obj->nb_customers : 0;
1064
                $this->stats_mo['nb_' . $role] = $obj->nb ? $obj->nb : 0;
1065
                $this->stats_mo['qty_' . $role] = $obj->qty ? price2num($obj->qty, 'MS') : 0;     // qty may be a float due to the SUM()
1066
            } else {
1067
                $this->error = $this->db->error();
1068
                $error++;
1069
            }
1070
        }
1071
1072
        if (!empty($error)) {
1073
            return -1;
1074
        }
1075
1076
        $parameters = array('socid' => $socid);
1077
        $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
1078
        if ($reshook > 0) {
1079
            $this->stats_mo = $hookmanager->resArray['stats_mo'];
1080
        }
1081
1082
        return 1;
1083
    }
1084
1085
1086
    /**
1087
     *  Return label of status of object
1088
     *
1089
     *  @param      int     $mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
1090
     *  @return     string              Label of status
1091
     */
1092
    public function getLibStatut($mode = 0)
1093
    {
1094
        return $this->LibStatut(0, $mode);
1095
    }
1096
1097
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1098
    /**
1099
     *  Return label of a given status
1100
     *
1101
     *  @param  int     $status     Status
1102
     *  @param  int     $mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
1103
     *  @return string              Label of status
1104
     */
1105
    public function LibStatut($status, $mode = 0)
1106
    {
1107
		// phpcs:enable
1108
        //global $langs;
1109
1110
        //$langs->load('stocks');
1111
1112
        return '';
1113
    }
1114
1115
1116
    /**
1117
     * getTooltipContentArray
1118
     *
1119
     * @param   array   $params     Params to construct tooltip data
1120
     * @since   v18
1121
     * @return  array
1122
     */
1123
    public function getTooltipContentArray($params)
1124
    {
1125
        global $langs;
1126
1127
        $langs->loadLangs(['stocks', 'productbatch']);
1128
1129
        //$option = $params['option'] ?? '';
1130
1131
        $datas = [];
1132
        $datas['picto'] = img_picto('', $this->picto) . ' <u class="paddingrightonly">' . $langs->trans("Batch") . '</u>';
1133
        //$datas['divopen'] = '<div width="100%">';
1134
        $datas['batch'] = '<br><b>' . $langs->trans('Batch') . ':</b> ' . $this->batch;
1135
        if (isDolTms($this->eatby) && !getDolGlobalString('PRODUCT_DISABLE_EATBY')) {
1136
            $datas['eatby'] = '<br><b>' . $langs->trans('EatByDate') . ':</b> ' . dol_print_date($this->eatby, 'day');
1137
        }
1138
        if (isDolTms($this->sellby) && !getDolGlobalString('PRODUCT_DISABLE_SELLBY')) {
1139
            $datas['sellby'] = '<br><b>' . $langs->trans('SellByDate') . ':</b> ' . dol_print_date($this->sellby, 'day');
1140
        }
1141
        //$datas['divclose'] = '</div>';
1142
1143
        return $datas;
1144
    }
1145
1146
    /**
1147
     *  Return a link to the a lot card (with optionally the picto)
1148
     *  Use this->id,this->lastname, this->firstname
1149
     *
1150
     *  @param  int     $withpicto              Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto)
1151
     *  @param  string  $option                 On what the link point to
1152
     *  @param  integer $notooltip              1=Disable tooltip
1153
     *  @param  int     $maxlen                 Max length of visible user name
1154
     *  @param  string  $morecss                Add more css on link
1155
     *  @param  int     $save_lastsearch_value  -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1156
     *  @return string                          String with URL
1157
     */
1158
    public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $maxlen = 24, $morecss = '', $save_lastsearch_value = -1)
1159
    {
1160
        global $langs, $hookmanager;
1161
1162
        $result = '';
1163
        $params = [
1164
            'id' => $this->id,
1165
            'objecttype' => $this->element,
1166
            'option' => $option,
1167
        ];
1168
        $classfortooltip = 'classfortooltip';
1169
        $dataparams = '';
1170
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1171
            $classfortooltip = 'classforajaxtooltip';
1172
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
1173
            $label = '';
1174
        } else {
1175
            $label = implode($this->getTooltipContentArray($params));
1176
        }
1177
1178
        $url = constant('BASE_URL') . '/product/stock/productlot_card.php?id=' . $this->id;
1179
1180
        if ($option != 'nolink') {
1181
            // Add param to save lastsearch_values or not
1182
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1183
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1184
                $add_save_lastsearch_values = 1;
1185
            }
1186
            if ($add_save_lastsearch_values) {
1187
                $url .= '&save_lastsearch_values=1';
1188
            }
1189
        }
1190
1191
        $linkclose = '';
1192
        if (empty($notooltip)) {
1193
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1194
                $label = $langs->trans("ShowMyObject");
1195
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
1196
            }
1197
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
1198
            $linkclose .= $dataparams . ' class="' . $classfortooltip . ($morecss ? ' ' . $morecss : '') . '"';
1199
        } else {
1200
            $linkclose = ($morecss ? ' class="' . $morecss . '"' : '');
1201
        }
1202
1203
        if ($option == 'nolink') {
1204
            $linkstart = '<span';
1205
        } else {
1206
            $linkstart = '<a href="' . $url . '"';
1207
        }
1208
        $linkstart .= $linkclose . '>';
1209
        if ($option == 'nolink') {
1210
            $linkend = '</span>';
1211
        } else {
1212
            $linkend = '</a>';
1213
        }
1214
1215
        $result .= $linkstart;
1216
        if ($withpicto) {
1217
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="' . (($withpicto != 2) ? 'paddingright ' : '') . '"'), 0, 0, $notooltip ? 0 : 1);
1218
        }
1219
        if ($withpicto != 2) {
1220
            $result .= $this->batch;
1221
        }
1222
        $result .= $linkend;
1223
1224
        global $action;
1225
        $hookmanager->initHooks(array('productlotdao'));
1226
        $parameters = array('id' => $this->id, 'getnomurl' => $result);
1227
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1228
        if ($reshook > 0) {
1229
            $result = $hookmanager->resPrint;
1230
        } else {
1231
            $result .= $hookmanager->resPrint;
1232
        }
1233
1234
        return $result;
1235
    }
1236
1237
1238
    /**
1239
     * Initialise object with example values
1240
     * Id must be 0 if object instance is a specimen
1241
     *
1242
     * @return int
1243
     */
1244
    public function initAsSpecimen()
1245
    {
1246
        global $conf;
1247
1248
        $now = dol_now();
1249
1250
        // Initialise parameters
1251
        $this->id = 0;
1252
        $this->ref = 'SPECIMEN';
1253
        $this->specimen = 1;
1254
1255
        $this->entity = $conf->entity;
1256
        $this->fk_product = 0;
1257
        $this->batch = 'ABCD123456';
1258
        $this->eatby = $now - 100000;
1259
        $this->sellby = $now - 100000;
1260
        $this->datec = $now - 3600;
1261
        $this->tms = $now;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$tms has been deprecated: Use $date_modification ( Ignorable by Annotation )

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

1261
        /** @scrutinizer ignore-deprecated */ $this->tms = $now;

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

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

Loading history...
1262
        $this->fk_user_creat = 0;
1263
        $this->fk_user_modif = 0;
1264
        $this->import_key = '123456';
1265
1266
        return 1;
1267
    }
1268
1269
    /**
1270
     *  Create a document onto disk according to template module.
1271
     *
1272
     * @param  string    $modele      Force model to use ('' to not force)
1273
     * @param  Translate $outputlangs Object langs to use for output
1274
     * @param  int       $hidedetails Hide details of lines
1275
     * @param  int       $hidedesc    Hide description
1276
     * @param  int       $hideref     Hide ref
1277
     * @return int                         0 if KO, 1 if OK
1278
     */
1279
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
1280
    {
1281
        global $langs;
1282
1283
        $langs->loadLangs(array('stocks', 'productbatch', "products"));
1284
        $outputlangs->loadLangs(array('stocks', 'productbatch', "products"));
1285
1286
        // Positionne le modele sur le nom du modele a utiliser
1287
        if (!dol_strlen($modele)) {
1288
            $modele = '';
1289
1290
            if (!empty($this->model_pdf)) {
1291
                $modele = $this->model_pdf;
1292
            } elseif (getDolGlobalString('PRODUCT_BATCH_ADDON_PDF')) {
1293
                $modele = getDolGlobalString('PRODUCT_BATCH_ADDON_PDF');
1294
            }
1295
        }
1296
1297
        $modelpath = "core/modules/product_batch/doc/";
1298
1299
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
1300
    }
1301
1302
    /**
1303
     * Return validation test result for a field
1304
     *
1305
     * @param  array   $fields              Array of properties of field to show
1306
     * @param  string  $fieldKey            Key of attribute
1307
     * @param  string  $fieldValue          value of attribute
1308
     * @return bool                         Return false if fail, true on success, set $this->error for error message
1309
     */
1310
    public function validateField($fields, $fieldKey, $fieldValue)
1311
    {
1312
        // Add your own validation rules here.
1313
        if ($fieldKey == 'batch') {
1314
            if (preg_match('/\s/', $fieldValue)) {
1315
                $this->error = 'ErrorABatchShouldNotContainsSpaces';
1316
                return false;
1317
            }
1318
        }
1319
1320
        return parent::validateField($fields, $fieldKey, $fieldValue);
1321
    }
1322
}
1323