FormProduct   F
last analyzed

Complexity

Total Complexity 206

Size/Duplication

Total Lines 899
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 467
dl 0
loc 899
rs 2
c 0
b 0
f 0
wmc 206

13 Methods

Rating   Name   Duplication   Size   Complexity  
F selectLotStock() 0 69 27
D selectLotDataList() 0 59 18
C loadLotStock() 0 71 15
F selectWarehouses() 0 103 42
F selectWorkstations() 0 78 27
F selectMeasuringUnits() 0 64 18
F loadWarehouses() 0 97 27
A formSelectWarehouses() 0 20 3
A __construct() 0 3 1
A select_measuring_units() 0 4 1
C selectProductNature() 0 56 14
B loadWorkstations() 0 44 9
A get_parent_path() 0 17 4

How to fix   Complexity   

Complex Class

Complex classes like FormProduct 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 FormProduct, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* Copyright (C) 2008-2009  Laurent Destailleur         <[email protected]>
4
 * Copyright (C) 2015-2017  Francis Appels              <[email protected]>
5
 * Copyright (C) 2024		Frédéric France			    <[email protected]>
6
 * Copyright (C) 2024       Rafael San José             <[email protected]>
7
 *
8
 * This program is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation; either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20
 */
21
22
namespace Dolibarr\Code\Product\Classes;
23
24
use Dolibarr\Code\Core\Classes\CProductNature;
25
use Dolibarr\Code\Core\Classes\CUnits;
26
use DoliDB;
27
use Exception;
28
29
/**
30
 *  \file       htdocs/product/class/html.formproduct.class.php
31
 *  \brief      File for class with methods for building product related HTML components
32
 */
33
34
35
/**
36
 *  Class with static methods for building HTML components related to products
37
 *  Only components common to products and services must be here.
38
 */
39
class FormProduct
40
{
41
    /**
42
     * @var DoliDB Database handler.
43
     */
44
    public $db;
45
46
    /**
47
     * @var string Error code (or message)
48
     */
49
    public $error = '';
50
51
    // Cache arrays
52
    public $cache_warehouses = array();
53
    public $cache_lot = array();
54
    public $cache_workstations = array();
55
56
57
    /**
58
     *  Constructor
59
     *
60
     *  @param  DoliDB  $db     Database handler
61
     */
62
    public function __construct($db)
63
    {
64
        $this->db = $db;
65
    }
66
67
68
    /**
69
     * Load in cache array list of warehouses
70
     * If fk_product is not 0, we do not use cache
71
     *
72
     * @param   int         $fk_product         Add quantity of stock in label for product with id fk_product. Nothing if 0.
73
     * @param   string      $batch              Add quantity of batch stock in label for product with batch name batch, batch name precedes batch_id. Nothing if ''.
74
     * @param   string      $status             warehouse status filter, following comma separated filter options can be used
75
     *                                          'warehouseopen' = select products from open warehouses,
76
     *                                          'warehouseclosed' = select products from closed warehouses,
77
     *                                          'warehouseinternal' = select products from warehouses for internal correct/transfer only
78
     * @param   boolean     $sumStock           sum total stock of a warehouse, default true
79
     * @param   array       $exclude            warehouses ids to exclude
80
     * @param   bool|int    $stockMin           [=false] Value of minimum stock to filter (only warehouse with stock > stockMin are loaded) or false not not filter by minimum stock
81
     * @param   string      $orderBy            [='e.ref'] Order by
82
     * @return  int                             Nb of loaded lines, 0 if already loaded, <0 if KO
83
     * @throws  Exception
84
     */
85
    public function loadWarehouses($fk_product = 0, $batch = '', $status = '', $sumStock = true, $exclude = array(), $stockMin = false, $orderBy = 'e.ref')
86
    {
87
        global $conf, $langs;
88
89
        if (empty($fk_product) && count($this->cache_warehouses)) {
90
            return 0; // Cache already loaded and we do not want a list with information specific to a product
91
        }
92
93
        $warehouseStatus = array();
94
95
        if (preg_match('/warehouseclosed/', $status)) {
96
            $warehouseStatus[] = Entrepot::STATUS_CLOSED;
97
        }
98
        if (preg_match('/warehouseopen/', $status)) {
99
            $warehouseStatus[] = Entrepot::STATUS_OPEN_ALL;
100
        }
101
        if (preg_match('/warehouseinternal/', $status)) {
102
            $warehouseStatus[] = Entrepot::STATUS_OPEN_INTERNAL;
103
        }
104
105
        $sql = "SELECT e.rowid, e.ref as label, e.description, e.fk_parent";
106
        if (!empty($fk_product) && $fk_product > 0) {
107
            if (!empty($batch)) {
108
                $sql .= ", pb.qty as stock";
109
            } else {
110
                $sql .= ", ps.reel as stock";
111
            }
112
        } elseif ($sumStock) {
113
            $sql .= ", sum(ps.reel) as stock";
114
        }
115
        $sql .= " FROM " . $this->db->prefix() . "entrepot as e";
116
        $sql .= " LEFT JOIN " . $this->db->prefix() . "product_stock as ps on ps.fk_entrepot = e.rowid";
117
        if (!empty($fk_product) && $fk_product > 0) {
118
            $sql .= " AND ps.fk_product = " . ((int) $fk_product);
119
            if (!empty($batch)) {
120
                $sql .= " LEFT JOIN " . $this->db->prefix() . "product_batch as pb on pb.fk_product_stock = ps.rowid AND pb.batch = '" . $this->db->escape($batch) . "'";
121
            }
122
        }
123
        $sql .= " WHERE e.entity IN (" . getEntity('stock') . ")";
124
        if (count($warehouseStatus)) {
125
            $sql .= " AND e.statut IN (" . $this->db->sanitize(implode(',', $warehouseStatus)) . ")";
126
        } else {
127
            $sql .= " AND e.statut = 1";
128
        }
129
130
        if (is_array($exclude) && !empty($exclude)) {
131
            $sql .= ' AND e.rowid NOT IN(' . $this->db->sanitize(implode(',', $exclude)) . ')';
132
        }
133
134
        // minimum stock
135
        if ($stockMin !== false) {
136
            if (!empty($fk_product) && $fk_product > 0) {
137
                if (!empty($batch)) {
138
                    $sql .= " AND pb.qty > " . ((float) $stockMin);
139
                } else {
140
                    $sql .= " AND ps.reel > " . ((float) $stockMin);
141
                }
142
            }
143
        }
144
145
        if ($sumStock && empty($fk_product)) {
146
            $sql .= " GROUP BY e.rowid, e.ref, e.description, e.fk_parent";
147
148
            // minimum stock
149
            if ($stockMin !== false) {
150
                $sql .= " HAVING sum(ps.reel) > " . ((float) $stockMin);
151
            }
152
        }
153
        $sql .= " ORDER BY " . $orderBy;
154
155
        dol_syslog(get_only_class($this) . '::loadWarehouses', LOG_DEBUG);
156
        $resql = $this->db->query($sql);
157
        if ($resql) {
158
            $num = $this->db->num_rows($resql);
159
            $i = 0;
160
            while ($i < $num) {
161
                $obj = $this->db->fetch_object($resql);
162
                if ($sumStock) {
163
                    $obj->stock = price2num($obj->stock, 5);
164
                }
165
                $this->cache_warehouses[$obj->rowid]['id'] = $obj->rowid;
166
                $this->cache_warehouses[$obj->rowid]['label'] = $obj->label;
167
                $this->cache_warehouses[$obj->rowid]['parent_id'] = $obj->fk_parent;
168
                $this->cache_warehouses[$obj->rowid]['description'] = $obj->description;
169
                $this->cache_warehouses[$obj->rowid]['stock'] = $obj->stock;
170
                $i++;
171
            }
172
173
            // Full label init
174
            foreach ($this->cache_warehouses as $obj_rowid => $tab) {
175
                $this->cache_warehouses[$obj_rowid]['full_label'] = $this->get_parent_path($tab);
176
            }
177
178
            return $num;
179
        } else {
180
            dol_print_error($this->db);
181
            return -1;
182
        }
183
    }
184
185
    /**
186
     * Load in cache array list of workstations
187
     * If fk_product is not 0, we do not use cache
188
     *
189
     * @param   int         $fk_product         Add quantity of stock in label for product with id fk_product. Nothing if 0.
190
     * @param   array       $exclude            warehouses ids to exclude
191
     * @param   string      $orderBy            [='e.ref'] Order by
192
     * @return  int                             Nb of loaded lines, 0 if already loaded, <0 if KO
193
     * @throws  Exception
194
     */
195
    public function loadWorkstations($fk_product = 0, $exclude = array(), $orderBy = 'w.ref')
196
    {
197
        global $conf, $langs;
198
199
        if (empty($fk_product) && count($this->cache_workstations)) {
200
            return 0; // Cache already loaded and we do not want a list with information specific to a product
201
        }
202
203
        $sql = "SELECT w.rowid, w.ref as ref, w.label as label, w.type, w.nb_operators_required,w.thm_operator_estimated,w.thm_machine_estimated";
204
        $sql .= " FROM " . $this->db->prefix() . "workstation_workstation as w";
205
        $sql .= " WHERE 1 = 1";
206
        if (!empty($fk_product) && $fk_product > 0) {
207
            $sql .= " AND w.fk_product = " . ((int) $fk_product);
208
        }
209
        $sql .= " AND w.entity IN (" . getEntity('workstation') . ")";
210
211
        if (is_array($exclude) && !empty($exclude)) {
212
            $sql .= ' AND w.rowid NOT IN(' . $this->db->sanitize(implode(',', $exclude)) . ')';
213
        }
214
215
        $sql .= " ORDER BY " . $orderBy;
216
217
        dol_syslog(get_only_class($this) . '::loadWorkstations', LOG_DEBUG);
218
        $resql = $this->db->query($sql);
219
        if ($resql) {
220
            $num = $this->db->num_rows($resql);
221
            $i = 0;
222
            while ($i < $num) {
223
                $obj = $this->db->fetch_object($resql);
224
225
                $this->cache_workstations[$obj->rowid]['id'] = $obj->rowid;
226
                $this->cache_workstations[$obj->rowid]['ref'] = $obj->ref;
227
                $this->cache_workstations[$obj->rowid]['label'] = $obj->label;
228
                $this->cache_workstations[$obj->rowid]['type'] = $obj->type;
229
                $this->cache_workstations[$obj->rowid]['nb_operators_required'] = $obj->nb_operators_required;
230
                $this->cache_workstations[$obj->rowid]['thm_operator_estimated'] = $obj->thm_operator_estimated;
231
                $this->cache_workstations[$obj->rowid]['thm_machine_estimated'] = $obj->thm_machine_estimated;
232
                $i++;
233
            }
234
235
            return $num;
236
        } else {
237
            dol_print_error($this->db);
238
            return -1;
239
        }
240
    }
241
242
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
243
    /**
244
     * Return full path to current warehouse in $tab (recursive function)
245
     *
246
     * @param   array   $tab            warehouse data in $this->cache_warehouses line
247
     * @param   string  $final_label    full label with all parents, separated by ' >> ' (completed on each call)
248
     * @return  string                  full label with all parents, separated by ' >> '
249
     */
250
    private function get_parent_path($tab, $final_label = '')
251
    {
252
		//phpcs:enable
253
        if (empty($final_label)) {
254
            $final_label = $tab['label'];
255
        }
256
257
        if (empty($tab['parent_id'])) {
258
            return $final_label;
259
        } else {
260
            if (!empty($this->cache_warehouses[$tab['parent_id']])) {
261
                $final_label = $this->cache_warehouses[$tab['parent_id']]['label'] . ' >> ' . $final_label;
262
                return $this->get_parent_path($this->cache_warehouses[$tab['parent_id']], $final_label);
263
            }
264
        }
265
266
        return $final_label;
267
    }
268
269
    /**
270
     *  Return list of warehouses
271
     *
272
     *  @param  string|int|array  $selected     Id of preselected warehouse ('' or '-1' for no value, 'ifone' and 'ifonenodefault' = select value if one value otherwise no value, '-2' to use the default value from setup)
273
     *  @param  string      $htmlname           Name of html select html
274
     *  @param  string      $filterstatus       warehouse status filter, following comma separated filter options can be used
275
     *                                          'warehouseopen' = select products from open warehouses,
276
     *                                          'warehouseclosed' = select products from closed warehouses,
277
     *                                          'warehouseinternal' = select products from warehouses for internal correct/transfer only
278
     *  @param  int         $empty              1=Can be empty, 0 if not
279
     *  @param  int         $disabled           1=Select is disabled
280
     *  @param  int         $fk_product         Add quantity of stock in label for product with id fk_product. Nothing if 0.
281
     *  @param  string      $empty_label        Empty label if needed (only if $empty=1)
282
     *  @param  int         $showstock          1=Show stock count
283
     *  @param  int         $forcecombo         1=Force combo iso ajax select2
284
     *  @param  array       $events             Events to add to select2
285
     *  @param  string      $morecss            Add more css classes to HTML select
286
     *  @param  array       $exclude            Warehouses ids to exclude
287
     *  @param  int         $showfullpath       1=Show full path of name (parent ref into label), 0=Show only ref of current warehouse
288
     *  @param  bool|int    $stockMin           [=false] Value of minimum stock to filter (only warehouse with stock > stockMin are loaded) or false not not filter by minimum stock
289
     *  @param  string      $orderBy            [='e.ref'] Order by
290
     *  @param  int         $multiselect        1=Allow multiselect
291
     *  @return string                          HTML select
292
     *
293
     *  @throws Exception
294
     */
295
    public function selectWarehouses($selected = '', $htmlname = 'idwarehouse', $filterstatus = '', $empty = 0, $disabled = 0, $fk_product = 0, $empty_label = '', $showstock = 0, $forcecombo = 0, $events = array(), $morecss = 'minwidth200', $exclude = array(), $showfullpath = 1, $stockMin = false, $orderBy = 'e.ref', $multiselect = 0)
296
    {
297
        global $conf, $langs, $user, $hookmanager;
298
299
        dol_syslog(get_only_class($this) . "::selectWarehouses " . (is_array($selected) ? 'selected is array' : $selected) . ", $htmlname, $filterstatus, $empty, $disabled, $fk_product, $empty_label, $showstock, $forcecombo, $morecss", LOG_DEBUG);
300
301
        $out = '';
302
        if (!getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
303
            $filterstatus = '';
304
        }
305
        if (!empty($fk_product) && $fk_product > 0) {
306
            $this->cache_warehouses = array();
307
        }
308
309
        $this->loadWarehouses($fk_product, '', $filterstatus, true, $exclude, $stockMin, $orderBy);
310
        $nbofwarehouses = count($this->cache_warehouses);
311
312
        if ($conf->use_javascript_ajax && !$forcecombo) {
313
            include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
314
            $comboenhancement = ajax_combobox($htmlname, $events);
315
            $out .= $comboenhancement;
316
        }
317
318
        if (strpos($htmlname, 'search_') !== 0) {
319
            if (empty($user->fk_warehouse) || $user->fk_warehouse == -1) {
320
                if (is_scalar($selected) && ($selected == '-2' || $selected == 'ifone') && getDolGlobalString('MAIN_DEFAULT_WAREHOUSE')) {
321
                    $selected = getDolGlobalString('MAIN_DEFAULT_WAREHOUSE');
322
                }
323
            } else {
324
                if (is_scalar($selected) && ($selected == '-2' || $selected == 'ifone') && getDolGlobalString('MAIN_DEFAULT_WAREHOUSE_USER')) {
325
                    $selected = $user->fk_warehouse;
326
                }
327
            }
328
        }
329
330
        $out .= '<select ' . ($multiselect ? 'multiple ' : '') . 'class="flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($disabled ? ' disabled' : '');
331
        $out .= ' id="' . $htmlname . '" name="' . ($htmlname . ($multiselect ? '[]' : '') . ($disabled ? '_disabled' : '')) . '"';
332
        //$out .= ' placeholder="todo"';    // placeholder for select2 must be added by setting the id+placeholder js param when calling select2
333
        $out .= '>';
334
        if ($empty) {
335
            $out .= '<option value="-1">' . ($empty_label ? $empty_label : '&nbsp;') . '</option>';
336
        }
337
        foreach ($this->cache_warehouses as $id => $arraytypes) {
338
            $label = '';
339
            if ($showfullpath) {
340
                $label .= $arraytypes['full_label'];
341
            } else {
342
                $label .= $arraytypes['label'];
343
            }
344
            if (($fk_product || ($showstock > 0)) && ($arraytypes['stock'] != 0 || ($showstock > 0))) {
345
                if ($arraytypes['stock'] <= 0) {
346
                    $label .= ' <span class="text-warning">(' . $langs->trans("Stock") . ':' . $arraytypes['stock'] . ')</span>';
347
                } else {
348
                    $label .= ' <span class="opacitymedium">(' . $langs->trans("Stock") . ':' . $arraytypes['stock'] . ')</span>';
349
                }
350
            }
351
352
            $out .= '<option value="' . $id . '"';
353
            if (is_array($selected)) {
354
                if (in_array($id, $selected)) {
355
                    $out .= ' selected';
356
                }
357
            } else {
358
                if ($selected == $id || (!empty($selected) && preg_match('/^ifone/', $selected) && $nbofwarehouses == 1)) {
359
                    $out .= ' selected';
360
                }
361
            }
362
            $out .= ' data-html="' . dol_escape_htmltag($label) . '"';
363
            $out .= '>';
364
            $out .= $label;
365
            $out .= '</option>';
366
        }
367
        $out .= '</select>';
368
        if ($disabled) {
369
            $out .= '<input type="hidden" name="' . $htmlname . '" value="' . (($selected > 0) ? $selected : '') . '">';
370
        }
371
372
        $parameters = array(
373
            'selected' => $selected,
374
            'htmlname' => $htmlname,
375
            'filterstatus' => $filterstatus,
376
            'empty' => $empty,
377
            'disabled ' => $disabled,
378
            'fk_product' => $fk_product,
379
            'empty_label' => $empty_label,
380
            'showstock' => $showstock,
381
            'forcecombo' => $forcecombo,
382
            'events' => $events,
383
            'morecss' => $morecss,
384
            'exclude' => $exclude,
385
            'showfullpath' => $showfullpath,
386
            'stockMin' => $stockMin,
387
            'orderBy' => $orderBy
388
        );
389
390
        $reshook = $hookmanager->executeHooks('selectWarehouses', $parameters, $this);
391
        if ($reshook > 0) {
392
            $out = $hookmanager->resPrint;
393
        } elseif ($reshook == 0) {
394
            $out .= $hookmanager->resPrint;
395
        }
396
397
        return $out;
398
    }
399
400
    /**
401
     *  Return list of workstations
402
     *
403
     *  @param  string|int  $selected           Id of preselected warehouse ('' or '-1' for no value, 'ifone' and 'ifonenodefault' = select value if one value otherwise no value, '-2' to use the default value from setup)
404
     *  @param  string      $htmlname           Name of html select html
405
     *  @param  int         $empty              1=Can be empty, 0 if not
406
     *  @param  int         $disabled           1=Select is disabled
407
     *  @param  int         $fk_product         Add quantity of stock in label for product with id fk_product. Nothing if 0.
408
     *  @param  string      $empty_label        Empty label if needed (only if $empty=1)
409
     *  @param  int         $forcecombo         1=Force combo iso ajax select2
410
     *  @param  array       $events                     Events to add to select2
411
     *  @param  string      $morecss                    Add more css classes to HTML select
412
     *  @param  array       $exclude            Warehouses ids to exclude
413
     *  @param  int         $showfullpath       1=Show full path of name (parent ref into label), 0=Show only ref of current warehouse
414
     *  @param  string      $orderBy            [='e.ref'] Order by
415
     *  @return string                          HTML select
416
     *
417
     *  @throws Exception
418
     */
419
    public function selectWorkstations($selected = '', $htmlname = 'idworkstations', $empty = 0, $disabled = 0, $fk_product = 0, $empty_label = '', $forcecombo = 0, $events = array(), $morecss = 'minwidth200', $exclude = array(), $showfullpath = 1, $orderBy = 'e.ref')
420
    {
421
        global $conf, $langs, $user, $hookmanager;
422
423
        dol_syslog(get_only_class($this) . "::selectWorkstations $selected, $htmlname, $empty, $disabled, $fk_product, $empty_label, $forcecombo, $morecss", LOG_DEBUG);
424
425
        $filterstatus = '';
426
        $out = '';
427
        if (!empty($fk_product) && $fk_product > 0) {
428
            $this->cache_workstations = array();
429
        }
430
431
        $this->loadWorkstations($fk_product);
432
        $nbofworkstations = count($this->cache_workstations);
433
434
        if ($conf->use_javascript_ajax && !$forcecombo) {
435
            include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
436
            $comboenhancement = ajax_combobox($htmlname, $events);
437
            $out .= $comboenhancement;
438
        }
439
440
        if (strpos($htmlname, 'search_') !== 0) {
441
            if (empty($user->fk_workstation) || $user->fk_workstation == -1) {
442
                if (($selected == '-2' || $selected == 'ifone') && getDolGlobalString('MAIN_DEFAULT_WORKSTATION')) {
443
                    $selected = getDolGlobalString('MAIN_DEFAULT_WORKSTATION');
444
                }
445
            } else {
446
                if (($selected == '-2' || $selected == 'ifone') && getDolGlobalString('MAIN_DEFAULT_WORKSTATION')) {
447
                    $selected = $user->fk_workstation;
448
                }
449
            }
450
        }
451
452
        $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($disabled ? ' disabled' : '') . ' id="' . $htmlname . '" name="' . ($htmlname . ($disabled ? '_disabled' : '')) . '">';
453
        if ($empty) {
454
            $out .= '<option value="-1">' . ($empty_label ? $empty_label : '&nbsp;') . '</option>';
455
        }
456
        foreach ($this->cache_workstations as $id => $arraytypes) {
457
            $label = $arraytypes['label'];
458
459
            $out .= '<option value="' . $id . '"';
460
            if ($selected == $id || (preg_match('/^ifone/', $selected) && $nbofworkstations == 1)) {
461
                $out .= ' selected';
462
            }
463
            $out .= ' data-html="' . dol_escape_htmltag($label) . '"';
464
            $out .= '>';
465
            $out .= $label;
466
            $out .= '</option>';
467
        }
468
        $out .= '</select>';
469
        if ($disabled) {
470
            $out .= '<input type="hidden" name="' . $htmlname . '" value="' . (($selected > 0) ? $selected : '') . '">';
471
        }
472
473
        $parameters = array(
474
            'selected' => $selected,
475
            'htmlname' => $htmlname,
476
            'filterstatus' => $filterstatus,
477
            'empty' => $empty,
478
            'disabled ' => $disabled,
479
            'fk_product' => $fk_product,
480
            'empty_label' => $empty_label,
481
            'forcecombo' => $forcecombo,
482
            'events' => $events,
483
            'morecss' => $morecss,
484
            'exclude' => $exclude,
485
            'showfullpath' => $showfullpath,
486
            'orderBy' => $orderBy
487
        );
488
489
        $reshook = $hookmanager->executeHooks('selectWorkstations', $parameters, $this);
490
        if ($reshook > 0) {
491
            $out = $hookmanager->resPrint;
492
        } elseif ($reshook == 0) {
493
            $out .= $hookmanager->resPrint;
494
        }
495
496
        return $out;
497
    }
498
499
    /**
500
     *    Display form to select warehouse
501
     *
502
     *    @param    string      $page        Page
503
     *    @param    string|int  $selected    Id of warehouse
504
     *    @param    string      $htmlname    Name of select html field
505
     *    @param    int         $addempty    1=Add an empty value in list, 2=Add an empty value in list only if there is more than 2 entries.
506
     *    @return   void
507
     */
508
    public function formSelectWarehouses($page, $selected = '', $htmlname = 'warehouse_id', $addempty = 0)
509
    {
510
        global $langs;
511
        if ($htmlname != "none") {
512
            print '<form method="POST" action="' . $page . '">';
513
            print '<input type="hidden" name="action" value="setwarehouse">';
514
            print '<input type="hidden" name="token" value="' . newToken() . '">';
515
            print '<table class="nobordernopadding">';
516
            print '<tr><td>';
517
            print $this->selectWarehouses($selected, $htmlname, '', $addempty);
518
            print '</td>';
519
            print '<td class="left"><input type="submit" class="button smallpaddingimp" value="' . $langs->trans("Modify") . '"></td>';
520
            print '</tr></table></form>';
521
        } else {
522
            if ($selected) {
523
                $warehousestatic = new Entrepot($this->db);
524
                $warehousestatic->fetch($selected);
525
                print $warehousestatic->getNomUrl();
526
            } else {
527
                print "&nbsp;";
528
            }
529
        }
530
    }
531
532
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
533
    /**
534
     *  Output a combo box with list of units
535
     *  Currently the units are not define in the DB
536
     *
537
     *  @param  string      $name               Name of HTML field
538
     *  @param  string      $measuring_style    Unit to show: weight, size, surface, volume, time
539
     *  @param  string      $selected            Preselected value
540
     *  @param  int         $adddefault         Add empty unit called "Default"
541
     *  @param  int         $mode               1=Use short label as value, 0=Use rowid
542
     *  @return void
543
     *  @deprecated
544
     */
545
    public function select_measuring_units($name = 'measuring_units', $measuring_style = '', $selected = '0', $adddefault = 0, $mode = 0)
546
    {
547
		//phpcs:enable
548
        print $this->selectMeasuringUnits($name, $measuring_style, $selected, $adddefault, $mode);
549
    }
550
551
    /**
552
     *  Return a combo box with list of units
553
     *  Units labels are defined in llx_c_units
554
     *
555
     *  @param  string      $name                Name of HTML field
556
     *  @param  string      $measuring_style     Unit to show: weight, size, surface, volume, time
557
     *  @param  string      $selected            Preselected value
558
     *  @param  int|string  $adddefault          1=Add empty unit called "Default", ''=Add empty value
559
     *  @param  int         $mode                1=Use short label as value, 0=Use rowid, 2=Use scale (power)
560
     *  @param  string      $morecss             More CSS
561
     *  @return string|-1
0 ignored issues
show
Documentation Bug introduced by
The doc comment string|-1 at position 2 could not be parsed: Unknown type name '-1' at position 2 in string|-1.
Loading history...
562
     */
563
    public function selectMeasuringUnits($name = 'measuring_units', $measuring_style = '', $selected = '0', $adddefault = 0, $mode = 0, $morecss = 'maxwidth125')
564
    {
565
        global $langs, $db;
566
567
        $langs->load("other");
568
569
        $return = '';
570
571
        // TODO Use a cache
572
        $measuringUnits = new CUnits($db);
573
574
        $filter = array();
575
        $filter['t.active'] = 1;
576
        if ($measuring_style) {
577
            $filter['t.unit_type'] = $measuring_style;
578
        }
579
580
        $result = $measuringUnits->fetchAll(
581
            '',
582
            '',
583
            0,
584
            0,
585
            $filter
586
        );
587
        if ($result < 0) {
588
            dol_print_error($db);
589
            return -1;
590
        } else {
591
            $return .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '" name="' . $name . '" id="' . $name . '">';
592
            if ($adddefault || $adddefault === '') {
593
                $return .= '<option value="0"' . ($selected === '0' ? ' selected' : '') . '>' . ($adddefault ? '(' . $langs->trans("Default") . ')' : '') . '</option>';
594
            }
595
596
            foreach ($measuringUnits->records as $lines) {
597
                $return .= '<option value="';
598
                if ($mode == 1) {
599
                    $return .= $lines->short_label;
600
                } elseif ($mode == 2) {
601
                    $return .= $lines->scale;
602
                } else {
603
                    $return .= $lines->id;
604
                }
605
                $return .= '"';
606
                if ($mode == 1 && $lines->short_label == $selected) {
607
                    $return .= ' selected';
608
                } elseif ($mode == 2 && $lines->scale == $selected) {
609
                    $return .= ' selected';
610
                } elseif ($mode == 0 && $lines->id == $selected) {
611
                    $return .= ' selected';
612
                }
613
                $return .= '>';
614
                if ($measuring_style == 'time') {
615
                    $return .= $langs->trans(ucfirst($lines->label));
616
                } else {
617
                    $return .= $langs->trans($lines->label);
618
                }
619
                $return .= '</option>';
620
            }
621
            $return .= '</select>';
622
        }
623
624
        $return .= ajax_combobox($name);
625
626
        return $return;
627
    }
628
629
    /**
630
     *  Return a combo box with list of units
631
     *  NAture of product labels are defined in llx_c_product_nature
632
     *
633
     *  @param  string      $name                Name of HTML field
634
     *  @param  string      $selected             Preselected value
635
     *  @param  int         $mode                1=Use label as value, 0=Use code
636
     *  @param  int         $showempty           1=show empty value, 0= no
637
     *  @return string|int
638
     */
639
    public function selectProductNature($name = 'finished', $selected = '', $mode = 0, $showempty = 1)
640
    {
641
        global $langs, $db;
642
643
        $langs->load('products');
644
645
        $return = '';
646
647
        // TODO Use a cache
648
        $productNature = new CProductNature($db);
649
650
        $filter = array();
651
        $filter['t.active'] = 1;
652
653
        $result = $productNature->fetchAll('', '', 0, 0, $filter);
654
655
        if ($result < 0) {
656
            dol_print_error($db);
657
            return -1;
658
        } else {
659
            $return .= '<select class="flat" name="' . $name . '" id="' . $name . '">';
660
            if ($showempty || ($selected == '' || $selected == '-1')) {
661
                $return .= '<option value="-1"';
662
                if ($selected == '' || $selected == '-1') {
663
                    $return .= ' selected';
664
                }
665
                $return .= '></option>';
666
            }
667
            if (!empty($productNature->records) && is_array($productNature->records)) {
668
                foreach ($productNature->records as $lines) {
669
                    $return .= '<option value="';
670
                    if ($mode == 1) {
671
                        $return .= $lines->label;
672
                    } else {
673
                        $return .= $lines->code;
674
                    }
675
676
                    $return .= '"';
677
678
                    if ($mode == 1 && $lines->label == $selected) {
679
                        $return .= ' selected';
680
                    } elseif ($lines->code == $selected) {
681
                        $return .= ' selected';
682
                    }
683
684
                    $return .= '>';
685
                    $return .= $langs->trans($lines->label);
686
                    $return .= '</option>';
687
                }
688
            }
689
            $return .= '</select>';
690
        }
691
692
        $return .= ajax_combobox($name);
693
694
        return $return;
695
    }
696
697
    /**
698
     *  Return list of lot numbers (stock from product_batch) with stock location and stock qty
699
     *
700
     *  @param  string|int  $selected   Id of preselected lot stock id ('' for no value, 'ifone'=select value if one value otherwise no value)
701
     *  @param  string  $htmlname       Name of html select html
702
     *  @param  string  $filterstatus   lot status filter, following comma separated filter options can be used
703
     *  @param  int     $empty          1=Can be empty, 0 if not
704
     *  @param  int     $disabled       1=Select is disabled
705
     *  @param  int     $fk_product     show lot numbers of product with id fk_product. All from objectLines if 0.
706
     *  @param  int     $fk_entrepot    filter lot numbers for warehouse with id fk_entrepot. All if 0.
707
     *  @param  array   $objectLines    Only cache lot numbers for products in lines of object. If no lines only for fk_product. If no fk_product, all.
708
     *  @param  string  $empty_label    Empty label if needed (only if $empty=1)
709
     *  @param  int     $forcecombo     1=Force combo iso ajax select2
710
     *  @param  array   $events         Events to add to select2
711
     *  @param  string  $morecss        Add more css classes to HTML select
712
     *
713
     *  @return string                  HTML select
714
     */
715
    public function selectLotStock($selected = '', $htmlname = 'batch_id', $filterstatus = '', $empty = 0, $disabled = 0, $fk_product = 0, $fk_entrepot = 0, $objectLines = array(), $empty_label = '', $forcecombo = 0, $events = array(), $morecss = 'minwidth200')
716
    {
717
        global $conf, $langs;
718
719
        dol_syslog(get_only_class($this) . "::selectLotStock $selected, $htmlname, $filterstatus, $empty, $disabled, $fk_product, $fk_entrepot, $empty_label, $forcecombo, $morecss", LOG_DEBUG);
720
721
        $out = '';
722
        $productIdArray = array();
723
        if (!is_array($objectLines) || !count($objectLines)) {
724
            if (!empty($fk_product) && $fk_product > 0) {
725
                $productIdArray[] = (int) $fk_product;
726
            }
727
        } else {
728
            foreach ($objectLines as $line) {
729
                if ($line->fk_product) {
730
                    $productIdArray[] = $line->fk_product;
731
                }
732
            }
733
        }
734
735
        $nboflot = $this->loadLotStock($productIdArray);
736
737
        if ($conf->use_javascript_ajax && !$forcecombo) {
738
            include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
739
            $comboenhancement = ajax_combobox($htmlname, $events);
740
            $out .= $comboenhancement;
741
        }
742
743
        $out .= '<select class="flat' . ($morecss ? ' ' . $morecss : '') . '"' . ($disabled ? ' disabled' : '') . ' id="' . $htmlname . '" name="' . ($htmlname . ($disabled ? '_disabled' : '')) . '">';
744
        if ($empty) {
745
            $out .= '<option value="-1">' . ($empty_label ? $empty_label : '&nbsp;') . '</option>';
746
        }
747
        if (!empty($fk_product) && $fk_product > 0) {
748
            $productIdArray = array((int) $fk_product); // only show lot stock for product
749
        } else {
750
            foreach ($this->cache_lot as $key => $value) {
751
                $productIdArray[] = $key;
752
            }
753
        }
754
755
        foreach ($productIdArray as $productId) {
756
            foreach ($this->cache_lot[$productId] as $id => $arraytypes) {
757
                if (empty($fk_entrepot) || $fk_entrepot == $arraytypes['entrepot_id']) {
758
                    $label = $arraytypes['entrepot_label'] . ' - ';
759
                    $label .= $arraytypes['batch'];
760
                    if ($arraytypes['qty'] <= 0) {
761
                        $label .= ' <span class=\'text-warning\'>(' . $langs->trans("Stock") . ' ' . $arraytypes['qty'] . ')</span>';
762
                    } else {
763
                        $label .= ' <span class=\'opacitymedium\'>(' . $langs->trans("Stock") . ' ' . $arraytypes['qty'] . ')</span>';
764
                    }
765
766
                    $out .= '<option value="' . $id . '"';
767
768
                    if ($selected == $id || ($selected == 'ifone' && $nboflot == 1)) {
769
                        $out .= ' selected';
770
                    }
771
                    $out .= ' data-html="' . dol_escape_htmltag($label) . '"';
772
                    $out .= '>';
773
                    $out .= $label;
774
                    $out .= '</option>';
775
                }
776
            }
777
        }
778
        $out .= '</select>';
779
        if ($disabled) {
780
            $out .= '<input type="hidden" name="' . $htmlname . '" value="' . (($selected > 0) ? $selected : '') . '">';
781
        }
782
783
        return $out;
784
    }
785
786
787
788
    /**
789
     *  Return list of lot numbers (stock from product_batch) for product and warehouse.
790
     *
791
     *  @param  string  $htmlname       Name of key that is inside attribute "list" of an input text field.
792
     *  @param  int     $empty          1=Can be empty, 0 if not
793
     *  @param  int     $fk_product     show lot numbers of product with id fk_product. All from objectLines if 0.
794
     *  @param  int     $fk_entrepot    filter lot numbers for warehouse with id fk_entrepot. All if 0.
795
     *  @param  array   $objectLines    Only cache lot numbers for products in lines of object. If no lines only for fk_product. If no fk_product, all.
796
     *  @return string                  HTML datalist
797
     */
798
    public function selectLotDataList($htmlname = 'batch_id', $empty = 0, $fk_product = 0, $fk_entrepot = 0, $objectLines = array())
799
    {
800
        global $langs, $hookmanager;
801
802
        dol_syslog(get_only_class($this) . "::selectLotDataList $htmlname, $empty, $fk_product, $fk_entrepot", LOG_DEBUG);
803
804
        $out = '';
805
        $productIdArray = array();
806
        if (!is_array($objectLines) || !count($objectLines)) {
807
            if (!empty($fk_product) && $fk_product > 0) {
808
                $productIdArray[] = (int) $fk_product;
809
            }
810
        } else {
811
            foreach ($objectLines as $line) {
812
                if ($line->fk_product) {
813
                    $productIdArray[] = $line->fk_product;
814
                }
815
            }
816
        }
817
818
        $nboflot = $this->loadLotStock($productIdArray);
819
820
        if (!empty($fk_product) && $fk_product > 0) {
821
            $productIdArray = array((int) $fk_product); // only show lot stock for product
822
        } else {
823
            foreach ($this->cache_lot as $key => $value) {
824
                $productIdArray[] = $key;
825
            }
826
        }
827
828
        if (empty($hookmanager)) {
829
            $hookmanager = new HookManager($this->db);
830
        }
831
        $hookmanager->initHooks(array('productdao'));
832
        $parameters = array('productIdArray' => $productIdArray, 'htmlname' => $htmlname);
833
        $reshook = $hookmanager->executeHooks('selectLotDataList', $parameters, $this);
834
        if ($reshook < 0) {
835
            return $hookmanager->error;
836
        } elseif ($reshook > 0) {
837
            return $hookmanager->resPrint;
838
        } else {
839
            $out .= $hookmanager->resPrint;
840
        }
841
842
        $out .= '<datalist id="' . $htmlname . '" >';
843
        foreach ($productIdArray as $productId) {
844
            if (array_key_exists($productId, $this->cache_lot)) {
845
                foreach ($this->cache_lot[$productId] as $id => $arraytypes) {
846
                    if (empty($fk_entrepot) || $fk_entrepot == $arraytypes['entrepot_id']) {
847
                        $label = $arraytypes['entrepot_label'] . ' - ';
848
                        $label .= $arraytypes['batch'];
849
                        $out .= '<option data-warehouse="' . dol_escape_htmltag($label) . '" value="' . $arraytypes['batch'] . '">(' . $langs->trans('Stock Total') . ': ' . $arraytypes['qty'] . ')</option>';
850
                    }
851
                }
852
            }
853
        }
854
        $out .= '</datalist>';
855
856
        return $out;
857
    }
858
859
860
    /**
861
     * Load in cache array list of lot available in stock from a given list of products
862
     *
863
     * @param   array   $productIdArray     array of product id's from who to get lot numbers. A
864
     *
865
     * @return  int                         Nb of loaded lines, 0 if nothing loaded, <0 if KO
866
     */
867
    private function loadLotStock($productIdArray = array())
868
    {
869
        global $conf, $langs;
870
871
        $cacheLoaded = false;
872
        if (empty($productIdArray)) {
873
            // only Load lot stock for given products
874
            $this->cache_lot = array();
875
            return 0;
876
        }
877
        if (count($productIdArray) && count($this->cache_lot)) {
878
            // check cache already loaded for product id's
879
            foreach ($productIdArray as $productId) {
880
                $cacheLoaded = !empty($this->cache_lot[$productId]) ? true : false;
881
            }
882
        }
883
        if ($cacheLoaded) {
884
            return count($this->cache_lot);
885
        } else {
886
            // clear cache
887
            $this->cache_lot = array();
888
            $productIdList = implode(',', $productIdArray);
889
890
            $batch_count = 0;
891
            global $hookmanager;
892
            if (empty($hookmanager)) {
893
                $hookmanager = new HookManager($this->db);
894
            }
895
            $hookmanager->initHooks(array('productdao'));
896
            $parameters = array('productIdList' => $productIdList);
897
            $reshook = $hookmanager->executeHooks('loadLotStock', $parameters, $this);
898
            if ($reshook < 0) {
899
                $this->error = $hookmanager->error;
900
                return -1;
901
            }
902
            if (!empty($hookmanager->resArray['batch_list']) && is_array($hookmanager->resArray['batch_list'])) {
903
                $this->cache_lot = $hookmanager->resArray['batch_list'];
904
                $batch_count = (int) $hookmanager->resArray['batch_count'];
905
            }
906
            if ($reshook > 0) {
907
                return $batch_count;
908
            }
909
910
            $sql = "SELECT pb.batch, pb.rowid, ps.fk_entrepot, pb.qty, e.ref as label, ps.fk_product";
911
            $sql .= " FROM " . $this->db->prefix() . "product_batch as pb";
912
            $sql .= " LEFT JOIN " . $this->db->prefix() . "product_stock as ps on ps.rowid = pb.fk_product_stock";
913
            $sql .= " LEFT JOIN " . $this->db->prefix() . "entrepot as e on e.rowid = ps.fk_entrepot AND e.entity IN (" . getEntity('stock') . ")";
914
            if (!empty($productIdList)) {
915
                $sql .= " WHERE ps.fk_product IN (" . $this->db->sanitize($productIdList) . ")";
916
            }
917
            $sql .= " ORDER BY e.ref, pb.batch";
918
919
            dol_syslog(get_only_class($this) . '::loadLotStock', LOG_DEBUG);
920
            $resql = $this->db->query($sql);
921
            if ($resql) {
922
                $num = $this->db->num_rows($resql);
923
                $i = 0;
924
                while ($i < $num) {
925
                    $obj = $this->db->fetch_object($resql);
926
                    $this->cache_lot[$obj->fk_product][$obj->rowid]['id'] = $obj->rowid;
927
                    $this->cache_lot[$obj->fk_product][$obj->rowid]['batch'] = $obj->batch;
928
                    $this->cache_lot[$obj->fk_product][$obj->rowid]['entrepot_id'] = $obj->fk_entrepot;
929
                    $this->cache_lot[$obj->fk_product][$obj->rowid]['entrepot_label'] = $obj->label;
930
                    $this->cache_lot[$obj->fk_product][$obj->rowid]['qty'] = $obj->qty;
931
                    $i++;
932
                }
933
934
                return $batch_count + $num;
935
            } else {
936
                dol_print_error($this->db);
937
                return -1;
938
            }
939
        }
940
    }
941
}
942