Test Failed
Push — master ( 9c7f87...697172 )
by Carlos
08:19
created

SalesModalHTML::modalClientes()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 45
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 37
nc 6
nop 4
dl 0
loc 45
rs 9.328
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of FacturaScripts
4
 * Copyright (C) 2021-2024 Carlos Garcia Gomez <[email protected]>
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as
8
 * published by the Free Software Foundation, either version 3 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18
 */
19
20
namespace FacturaScripts\Core\Base\AjaxForms;
21
22
use FacturaScripts\Core\Base\ControllerPermissions;
23
use FacturaScripts\Core\Base\DataBase;
24
use FacturaScripts\Core\Base\DataBase\DataBaseWhere;
25
use FacturaScripts\Core\Base\Translator;
26
use FacturaScripts\Core\Cache;
27
use FacturaScripts\Core\Model\Base\SalesDocument;
28
use FacturaScripts\Core\Model\User;
29
use FacturaScripts\Core\Tools;
30
use FacturaScripts\Dinamic\Model\AtributoValor;
31
use FacturaScripts\Dinamic\Model\Cliente;
32
use FacturaScripts\Dinamic\Model\Fabricante;
33
use FacturaScripts\Dinamic\Model\Familia;
34
use FacturaScripts\Dinamic\Model\RoleAccess;
35
36
/**
37
 * Description of SalesModalHTML
38
 *
39
 * @author Carlos Garcia Gomez <[email protected]>
40
 */
41
class SalesModalHTML
42
{
43
    /** @var string */
44
    protected static $codalmacen;
45
46
    /** @var string */
47
    protected static $codcliente;
48
49
    /** @var string */
50
    protected static $codfabricante;
51
52
    /** @var string */
53
    protected static $codfamilia;
54
55
    /** @var array */
56
    protected static $idatributovalores = [];
57
58
    /** @var string */
59
    protected static $orden;
60
61
    /** @var string */
62
    protected static $query;
63
64
    /** @var bool */
65
    protected static $vendido;
66
67
    public static function apply(SalesDocument &$model, array $formData)
68
    {
69
        self::$codalmacen = $model->codalmacen;
70
        self::$codcliente = $model->codcliente;
71
        self::$codfabricante = $formData['fp_codfabricante'] ?? '';
72
        self::$codfamilia = $formData['fp_codfamilia'] ?? '';
73
        self::$orden = $formData['fp_orden'] ?? 'ref_asc';
74
        self::$vendido = (bool)($formData['fp_vendido'] ?? false);
75
        self::$query = isset($formData['fp_query']) ?
76
            Tools::noHtml(mb_strtolower($formData['fp_query'], 'UTF8')) : '';
77
    }
78
79
    public static function render(SalesDocument $model, string $url, User $user, ControllerPermissions $permissions): string
80
    {
81
        self::$codalmacen = $model->codalmacen;
82
83
        $i18n = new Translator();
0 ignored issues
show
Deprecated Code introduced by
The class FacturaScripts\Core\Base\Translator has been deprecated: since FacturaScripts 2023.06 ( Ignorable by Annotation )

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

83
        $i18n = /** @scrutinizer ignore-deprecated */ new Translator();
Loading history...
84
        return $model->editable ? static::modalClientes($i18n, $url, $user, $permissions) . static::modalProductos($i18n) : '';
85
    }
86
87
    public static function renderProductList(): string
88
    {
89
        $tbody = '';
90
        $i18n = new Translator();
0 ignored issues
show
Deprecated Code introduced by
The class FacturaScripts\Core\Base\Translator has been deprecated: since FacturaScripts 2023.06 ( Ignorable by Annotation )

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

90
        $i18n = /** @scrutinizer ignore-deprecated */ new Translator();
Loading history...
91
        foreach (static::getProducts() as $row) {
92
            $cssClass = $row['nostock'] ? 'table-info clickableRow' : ($row['disponible'] > 0 ? 'clickableRow' : 'table-warning clickableRow');
93
            $description = Tools::textBreak($row['descripcion'], 120)
94
                . static::idatributovalor($row['idatributovalor1'])
95
                . static::idatributovalor($row['idatributovalor2'])
96
                . static::idatributovalor($row['idatributovalor3'])
97
                . static::idatributovalor($row['idatributovalor4']);
98
            $tbody .= '<tr class="' . $cssClass . '" onclick="$(\'#findProductModal\').modal(\'hide\');'
99
                . ' return salesFormAction(\'add-product\', \'' . $row['referencia'] . '\');">'
100
                . '<td><b>' . $row['referencia'] . '</b> ' . $description . '</td>'
101
                . '<td class="text-right">' . str_replace(' ', '&nbsp;', Tools::money($row['precio'])) . '</td>';
102
103
            if (self::$vendido) {
104
                $tbody .= '<td class="text-right">' . str_replace(' ', '&nbsp;', Tools::money($row['ultimo_precio'])) . '</td>';
105
            }
106
107
            $tbody .= '<td class="text-right">' . $row['disponible'] . '</td>'
108
                . '</tr>';
109
        }
110
111
        if (empty($tbody)) {
112
            $tbody .= '<tr class="table-warning"><td colspan="4">' . $i18n->trans('no-data') . '</td></tr>';
113
        }
114
115
        $extraTh = self::$vendido ?
116
            '<th class="text-right">' . $i18n->trans('last-price-sale') . '</th>' :
117
            '';
118
        return '<table class="table table-hover mb-0">'
119
            . '<thead>'
120
            . '<tr>'
121
            . '<th>' . $i18n->trans('product') . '</th>'
122
            . '<th class="text-right">' . $i18n->trans('price') . '</th>'
123
            . $extraTh
124
            . '<th class="text-right">' . $i18n->trans('stock') . '</th>'
125
            . '</tr>'
126
            . '</thead>'
127
            . '<tbody>' . $tbody . '</tbody>'
128
            . '</table>';
129
    }
130
131
    protected static function fabricantes(Translator $i18n): string
132
    {
133
        $fabricante = new Fabricante();
134
        $options = '<option value="">' . $i18n->trans('manufacturer') . '</option>'
135
            . '<option value="">------</option>';
136
        foreach ($fabricante->all([], ['nombre' => 'ASC'], 0, 0) as $man) {
137
            $options .= '<option value="' . $man->codfabricante . '">' . $man->nombre . '</option>';
138
        }
139
140
        return '<select name="fp_codfabricante" class="form-control" onchange="return salesFormAction(\'find-product\', \'0\');">'
141
            . $options . '</select>';
142
    }
143
144
    protected static function familias(Translator $i18n): string
145
    {
146
        $options = '<option value="">' . $i18n->trans('family') . '</option>'
147
            . '<option value="">------</option>';
148
149
        $familia = new Familia();
150
        $where = [new DataBaseWhere('madre', null, 'IS')];
151
        $orderBy = ['descripcion' => 'ASC'];
152
        foreach ($familia->all($where, $orderBy, 0, 0) as $fam) {
153
            $options .= '<option value="' . $fam->codfamilia . '">' . $fam->descripcion . '</option>';
154
155
            // añadimos las subfamilias de forma recursiva
156
            $options .= static::subfamilias($fam, $i18n);
157
        }
158
159
        return '<select name="fp_codfamilia" class="form-control" onchange="return salesFormAction(\'find-product\', \'0\');">'
160
            . $options . '</select>';
161
    }
162
163
    protected static function getClientes(User $user, ControllerPermissions $permissions): array
164
    {
165
        // buscamos en caché
166
        $cacheKey = 'model-Cliente-sales-modal-' . $user->nick;
167
        $clientes = Cache::get($cacheKey);
168
        if (is_array($clientes)) {
169
            return $clientes;
170
        }
171
172
        // ¿El usuario tiene permiso para ver todos los clientes?
173
        $showAll = false;
174
        foreach (RoleAccess::allFromUser($user->nick, 'EditCliente') as $access) {
175
            if (false === $access->onlyownerdata) {
176
                $showAll = true;
177
            }
178
        }
179
180
        // consultamos la base de datos
181
        $cliente = new Cliente();
182
        $where = [new DataBaseWhere('fechabaja', null, 'IS')];
183
        if ($permissions->onlyOwnerData && !$showAll) {
184
            $where[] = new DataBaseWhere('codagente', $user->codagente);
185
            $where[] = new DataBaseWhere('codagente', null, 'IS NOT');
186
        }
187
        $clientes = $cliente->all($where, ['LOWER(nombre)' => 'ASC']);
188
189
        // guardamos en caché
190
        Cache::set($cacheKey, $clientes);
191
192
        return $clientes;
193
    }
194
195
    protected static function getProducts(): array
196
    {
197
        $dataBase = new DataBase();
198
        $sql = 'SELECT v.referencia, p.descripcion, v.idatributovalor1, v.idatributovalor2, v.idatributovalor3,'
199
            . ' v.idatributovalor4, v.precio, COALESCE(s.disponible, 0) as disponible, p.nostock'
200
            . ' FROM variantes v'
201
            . ' LEFT JOIN productos p ON v.idproducto = p.idproducto'
202
            . ' LEFT JOIN stocks s ON v.referencia = s.referencia AND s.codalmacen = ' . $dataBase->var2str(self::$codalmacen)
203
            . ' WHERE p.sevende = true AND p.bloqueado = false';
204
205
        if (self::$codfabricante) {
206
            $sql .= ' AND codfabricante = ' . $dataBase->var2str(self::$codfabricante);
207
        }
208
209
        if (self::$codfamilia) {
210
            $codFamilias = [$dataBase->var2str(self::$codfamilia)];
211
212
            // buscamos las subfamilias
213
            $familia = new Familia();
214
            if ($familia->loadFromCode(self::$codfamilia)) {
215
                foreach ($familia->getSubfamilias() as $fam) {
216
                    $codFamilias[] = $dataBase->var2str($fam->codfamilia);
217
                }
218
            }
219
220
            $sql .= ' AND codfamilia IN (' . implode(',', $codFamilias) . ')';
221
        }
222
223
        if (self::$vendido) {
224
            $sql .= ' AND v.referencia IN (SELECT referencia FROM lineasfacturascli'
225
                . ' LEFT JOIN facturascli ON lineasfacturascli.idfactura = facturascli.idfactura'
226
                . ' WHERE codcliente = ' . $dataBase->var2str(self::$codcliente) . ')';
227
        }
228
229
        if (self::$query) {
230
            $words = explode(' ', self::$query);
231
            if (count($words) === 1) {
232
                $sql .= " AND (LOWER(v.codbarras) = " . $dataBase->var2str(self::$query)
233
                    . " OR LOWER(v.referencia) LIKE '%" . self::$query . "%'"
234
                    . " OR LOWER(p.descripcion) LIKE '%" . self::$query . "%')";
235
            } elseif (count($words) > 1) {
236
                $sql .= " AND (LOWER(v.referencia) LIKE '%" . self::$query . "%' OR (";
237
                foreach ($words as $wc => $word) {
238
                    $sql .= $wc > 0 ?
239
                        " AND LOWER(p.descripcion) LIKE '%" . $word . "%'" :
240
                        "LOWER(p.descripcion) LIKE '%" . $word . "%'";
241
                }
242
                $sql .= "))";
243
            }
244
        }
245
246
        switch (self::$orden) {
247
            case 'desc_asc':
248
                $sql .= " ORDER BY 2 ASC";
249
                break;
250
251
            case 'price_desc':
252
                $sql .= " ORDER BY 7 DESC";
253
                break;
254
255
            case 'ref_asc':
256
                $sql .= " ORDER BY 1 ASC";
257
                break;
258
259
            case 'stock_desc':
260
                $sql .= " ORDER BY 8 DESC";
261
                break;
262
        }
263
264
        $results = $dataBase->selectLimit($sql);
265
        if (self::$vendido) {
266
            static::setProductsLastPrice($dataBase, $results);
267
        }
268
269
        return $results;
270
    }
271
272
    protected static function idatributovalor(?int $id): string
273
    {
274
        if (empty($id)) {
275
            return '';
276
        }
277
278
        if (!isset(self::$idatributovalores[$id])) {
279
            $attValor = new AtributoValor();
280
            $attValor->loadFromCode($id);
281
            self::$idatributovalores[$id] = $attValor->descripcion;
282
        }
283
284
        return ', ' . self::$idatributovalores[$id];
285
    }
286
287
    protected static function modalClientes(Translator $i18n, string $url, User $user, ControllerPermissions $permissions): string
288
    {
289
        $trs = '';
290
291
292
        foreach (static::getClientes($user, $permissions) as $cli) {
293
            $name = ($cli->nombre === $cli->razonsocial) ? $cli->nombre : $cli->nombre . ' <small>(' . $cli->razonsocial . ')</span>';
294
            $trs .= '<tr class="clickableRow" onclick="document.forms[\'salesForm\'][\'codcliente\'].value = \''
295
                . $cli->codcliente . '\'; $(\'#findCustomerModal\').modal(\'hide\'); salesFormAction(\'set-customer\', \'0\'); return false;">'
296
                . '<td><i class="fas fa-user fa-fw"></i> ' . $name . '</td>'
297
                . '</tr>';
298
        }
299
300
        $linkAgent = '';
301
        if ($user->codagente) {
302
            $linkAgent = '&codagente=' . $user->codagente;
303
        }
304
305
        return '<div class="modal" id="findCustomerModal" tabindex="-1" aria-hidden="true">'
306
            . '<div class="modal-dialog modal-dialog-scrollable">'
307
            . '<div class="modal-content">'
308
            . '<div class="modal-header">'
309
            . '<h5 class="modal-title"><i class="fas fa-users fa-fw"></i> ' . $i18n->trans('customers') . '</h5>'
310
            . '<button type="button" class="close" data-dismiss="modal" aria-label="Close">'
311
            . '<span aria-hidden="true">&times;</span>'
312
            . '</button>'
313
            . '</div>'
314
            . '<div class="modal-body p-0">'
315
            . '<div class="p-3">'
316
            . '<div class="input-group">'
317
            . '<input type="text" id="findCustomerInput" class="form-control" placeholder="' . $i18n->trans('search') . '" />'
318
            . '<div class="input-group-apend">'
319
            . '<button type="button" class="btn btn-primary"><i class="fas fa-search"></i></button>'
320
            . '</div>'
321
            . '</div>'
322
            . '</div>'
323
            . '<table class="table table-hover mb-0">' . $trs . '</table></div>'
324
            . '<div class="modal-footer bg-light">'
325
            . '<a href="EditCliente?return=' . urlencode($url) . $linkAgent . '" class="btn btn-block btn-success">'
326
            . '<i class="fas fa-plus fa-fw"></i> ' . $i18n->trans('new')
327
            . '</a>'
328
            . '</div>'
329
            . '</div>'
330
            . '</div>'
331
            . '</div>';
332
    }
333
334
    protected static function modalProductos(Translator $i18n): string
335
    {
336
        return '<div class="modal" id="findProductModal" tabindex="-1" aria-hidden="true">'
337
            . '<div class="modal-dialog modal-xl">'
338
            . '<div class="modal-content">'
339
            . '<div class="modal-header">'
340
            . '<h5 class="modal-title"><i class="fas fa-cubes fa-fw"></i> ' . $i18n->trans('products') . '</h5>'
341
            . '<button type="button" class="close" data-dismiss="modal" aria-label="Close">'
342
            . '<span aria-hidden="true">&times;</span>'
343
            . '</button>'
344
            . '</div>'
345
            . '<div class="modal-body">'
346
            . '<div class="form-row">'
347
            . '<div class="col-sm mb-2">'
348
            . '<div class="input-group">'
349
            . '<input type="text" name="fp_query" class="form-control" id="productModalInput" placeholder="' . $i18n->trans('search')
350
            . '" onkeyup="return salesFormActionWait(\'find-product\', \'0\', event);"/>'
351
            . '<div class="input-group-append">'
352
            . '<button class="btn btn-primary btn-spin-action" type="button" onclick="return salesFormAction(\'find-product\', \'0\');">'
353
            . '<i class="fas fa-search"></i></button>'
354
            . '</div>'
355
            . '</div>'
356
            . '</div>'
357
            . '<div class="col-sm mb-2">' . static::fabricantes($i18n) . '</div>'
358
            . '<div class="col-sm mb-2">' . static::familias($i18n) . '</div>'
359
            . '<div class="col-sm mb-2">' . static::orden($i18n) . '</div>'
360
            . '</div>'
361
            . '<div class="form-row">'
362
            . '<div class="col-sm">'
363
            . '<div class="form-check">'
364
            . '<input type="checkbox" name="fp_vendido" value="1" class="form-check-input" id="vendido" onchange="return salesFormAction(\'find-product\', \'0\');">'
365
            . '<label class="form-check-label" for="vendido">' . $i18n->trans('previously-sold-to-customer') . '</label>'
366
            . '</div>'
367
            . '</div>'
368
            . '</div>'
369
            . '</div>'
370
            . '<div class="table-responsive" id="findProductList">' . static::renderProductList() . '</div>'
371
            . '</div>'
372
            . '</div>'
373
            . '</div>';
374
    }
375
376
    protected static function orden(Translator $i18n): string
377
    {
378
        return '<div class="input-group">'
379
            . '<div class="input-group-prepend"><span class="input-group-text"><i class="fas fa-sort-amount-down-alt"></i></span></div>'
380
            . '<select name="fp_orden" class="form-control" onchange="return salesFormAction(\'find-product\', \'0\');">'
381
            . '<option value="">' . $i18n->trans('sort') . '</option>'
382
            . '<option value="">------</option>'
383
            . '<option value="ref_asc">' . $i18n->trans('reference') . '</option>'
384
            . '<option value="desc_asc">' . $i18n->trans('description') . '</option>'
385
            . '<option value="price_desc">' . $i18n->trans('price') . '</option>'
386
            . '<option value="stock_desc">' . $i18n->trans('stock') . '</option>'
387
            . '</select>'
388
            . '</div>';
389
    }
390
391
    protected static function setProductsLastPrice(DataBase $db, array &$items): void
392
    {
393
        foreach ($items as $key => $item) {
394
            // obtenemos el último precio en facturas de este cliente
395
            $sql = 'SELECT pvpunitario FROM lineasfacturascli l'
396
                . ' LEFT JOIN facturascli f ON f.idfactura = l.idfactura'
397
                . ' WHERE f.codcliente = ' . $db->var2str(self::$codcliente)
398
                . ' AND l.referencia = ' . $db->var2str($item['referencia'])
399
                . ' ORDER BY f.fecha DESC';
400
            foreach ($db->selectLimit($sql, 1) as $row) {
401
                $items[$key]['ultimo_precio'] = $row['pvpunitario'];
402
                continue 2;
403
            }
404
405
            // no hay facturas, asignamos el último precio de venta
406
            $items[$key]['ultimo_precio'] = $item['precio'];
407
        }
408
    }
409
410
    private static function subfamilias(Familia $family, Translator $i18n, int $level = 1): string
411
    {
412
        $options = '';
413
        foreach ($family->getSubfamilias() as $fam) {
414
            $options .= '<option value="' . $fam->codfamilia . '">'
415
                . str_repeat('-', $level) . ' ' . $fam->descripcion
416
                . '</option>';
417
418
            // añadimos las subfamilias de forma recursiva
419
            $options .= static::subfamilias($fam, $i18n, $level + 1);
420
        }
421
422
        return $options;
423
    }
424
}
425