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

FormProduct::get_parent_path()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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