Passed
Push — master ( 4286a4...290de6 )
by Carlos
05:27
created

Asiento::delete()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 37
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 22
dl 0
loc 37
rs 8.6346
c 0
b 0
f 0
cc 7
nc 7
nop 0
1
<?php
2
/**
3
 * This file is part of FacturaScripts
4
 * Copyright (C) 2014-2021 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\Model;
21
22
use FacturaScripts\Core\Base\DataBase\DataBaseWhere;
23
use FacturaScripts\Dinamic\Model\Diario as DinDiario;
24
use FacturaScripts\Dinamic\Model\Ejercicio as DinEjercicio;
25
use FacturaScripts\Dinamic\Model\Partida as DinPartida;
26
use FacturaScripts\Dinamic\Model\RegularizacionImpuesto as DinRegularizacionImpuesto;
27
28
/**
29
 * The accounting entry. It is related to an exercise and consists of games.
30
 *
31
 * @author Carlos García Gómez  <[email protected]>
32
 * @author Artex Trading sa     <[email protected]>
33
 */
34
class Asiento extends Base\ModelOnChangeClass implements Base\GridModelInterface
35
{
36
37
    use Base\ModelTrait;
0 ignored issues
show
Bug introduced by
The trait FacturaScripts\Core\Model\Base\ModelTrait requires the property $name which is not provided by FacturaScripts\Core\Model\Asiento.
Loading history...
38
    use Base\ExerciseRelationTrait;
39
40
    const OPERATION_GENERAL = null;
41
    const OPERATION_OPENING = 'A';
42
    const OPERATION_CLOSING = 'C';
43
    const OPERATION_REGULARIZATION = 'R';
44
    const RENUMBER_LIMIT = 1000;
45
46
    /**
47
     * Accounting channel. For statisctics purpose.
48
     *
49
     * @var int
50
     */
51
    public $canal;
52
53
    /**
54
     * Accounting entry concept.
55
     *
56
     * @var string
57
     */
58
    public $concepto;
59
60
    /**
61
     * Document associated with the accounting entry.
62
     *
63
     * @var string
64
     */
65
    public $documento;
66
67
    /**
68
     * True if it is editable, but false.
69
     *
70
     * @var bool
71
     */
72
    public $editable;
73
74
    /**
75
     * Date of the accounting entry.
76
     *
77
     * @var string
78
     */
79
    public $fecha;
80
81
    /**
82
     * Primary key.
83
     *
84
     * @var int
85
     */
86
    public $idasiento;
87
88
    /**
89
     * Diary identifier.
90
     *
91
     * @var int
92
     */
93
    public $iddiario;
94
95
    /**
96
     * Foreign Key with Empresas table.
97
     *
98
     * @var int
99
     */
100
    public $idempresa;
101
102
    /**
103
     * Amount of the accounting entry.
104
     *
105
     * @var float|int
106
     */
107
    public $importe;
108
109
    /**
110
     * Accounting entry number. It will be modified when renumbering.
111
     *
112
     * @var string
113
     */
114
    public $numero;
115
116
    /**
117
     * It establishes whether the accounting entry is of a special operation:
118
     * - opening
119
     * - regularization
120
     * - closing
121
     *
122
     * @var string
123
     */
124
    public $operacion;
125
126
    /**
127
     * Accumulate the amounts of the detail in the document
128
     *
129
     * @param array $detail
130
     */
131
    public function accumulateAmounts(array &$detail)
132
    {
133
        $haber = isset($detail['haber']) ? (float)$detail['haber'] : 0.0;
134
        $this->importe += round($haber, (int)FS_NF0);
135
    }
136
137
    /**
138
     * Reset the values of all model properties.
139
     */
140
    public function clear()
141
    {
142
        parent::clear();
143
        $this->editable = true;
144
        $this->fecha = date(self::DATE_STYLE);
145
        $this->idempresa = $this->toolBox()->appSettings()->get('default', 'idempresa');
146
        $this->importe = 0.0;
147
        $this->numero = '';
148
        $this->operacion = self::OPERATION_GENERAL;
149
    }
150
151
    /**
152
     * Remove the accounting entry.
153
     *
154
     * @return bool
155
     */
156
    public function delete()
157
    {
158
        if (false === $this->getExercise()->isOpened()) {
159
            $this->toolBox()->i18nLog()->warning('closed-exercise', ['%exerciseName%' => $this->getExercise()->nombre]);
160
            return false;
161
        }
162
163
        $reg = new DinRegularizacionImpuesto();
164
        if ($reg->loadFechaInside($this->fecha) && $reg->bloquear) {
165
            $this->toolBox()->i18nLog()->warning('accounting-within-regularization');
166
            return false;
167
        }
168
169
        if (false === $this->isEditable()) {
170
            $this->toolBox()->i18nLog()->warning('non-editable-accounting-entry');
171
            return false;
172
        }
173
174
        // force delete lines to update subaccounts
175
        foreach ($this->getLines() as $line) {
176
            $line->delete();
177
        }
178
179
        if (false === parent::delete()) {
180
            return false;
181
        }
182
183
        // add audit log
184
        self::toolBox()::i18nLog(self::AUDIT_CHANNEL)->info('deleted-model', [
185
            '%model%' => $this->modelClassName(),
186
            '%key%' => $this->primaryColumnValue(),
187
            '%desc%' => $this->primaryDescription(),
188
            'model-class' => $this->modelClassName(),
189
            'model-code' => $this->primaryColumnValue(),
190
            'model-data' => $this->toArray()
191
        ]);
192
        return true;
193
    }
194
195
    /**
196
     * @return DinPartida[]
197
     */
198
    public function getLines()
199
    {
200
        $partida = new DinPartida();
201
        $where = [new DataBaseWhere('idasiento', $this->idasiento)];
202
        return $partida->all($where, ['codsubcuenta' => 'ASC'], 0, 0);
203
    }
204
205
    /**
206
     * @return DinPartida
207
     */
208
    public function getNewLine()
209
    {
210
        $partida = new DinPartida();
211
        $partida->concepto = $this->concepto;
212
        $partida->documento = $this->documento;
213
        $partida->idasiento = $this->primaryColumnValue();
214
        return $partida;
215
    }
216
217
    /**
218
     * Initializes the total fields
219
     */
220
    public function initTotals()
221
    {
222
        $this->importe = 0.0;
223
    }
224
225
    /**
226
     * This function is called when creating the model table. Returns the SQL
227
     * that will be executed after the creation of the table. Useful to insert values
228
     * default.
229
     *
230
     * @return string
231
     */
232
    public function install()
233
    {
234
        new DinDiario();
235
        new DinEjercicio();
236
237
        return parent::install();
238
    }
239
240
    /**
241
     * Returns TRUE if accounting entry is balanced.
242
     *
243
     * @return bool
244
     */
245
    public function isBalanced(): bool
246
    {
247
        $debe = 0.0;
248
        $haber = 0.0;
249
        foreach ($this->getLines() as $line) {
250
            $debe += $line->debe;
251
            $haber += $line->haber;
252
        }
253
254
        return $this->toolBox()->utils()->floatcmp($debe, $haber, FS_NF0, true);
255
    }
256
257
    /**
258
     * Returns the following code for the reported field or the primary key of the model.
259
     *
260
     * @param string $field
261
     * @param array $where
262
     *
263
     * @return int
264
     */
265
    public function newCode(string $field = '', array $where = [])
266
    {
267
        if ($field !== $this->primaryColumn()) {
268
            $where[] = new DataBaseWhere('codejercicio', $this->codejercicio);
269
        }
270
        return parent::newCode($field, $where);
271
    }
272
273
    /**
274
     * Returns the name of the column that is the model's primary key.
275
     *
276
     * @return string
277
     */
278
    public static function primaryColumn()
279
    {
280
        return 'idasiento';
281
    }
282
283
    /**
284
     * Returns the name of the column that describes the model, such as name, description...
285
     *
286
     * @return string
287
     */
288
    public function primaryDescriptionColumn()
289
    {
290
        return 'numero';
291
    }
292
293
    /**
294
     * Re-number the accounting entries of the open exercises.
295
     *
296
     * @param string $codejercicio
297
     *
298
     * @return bool
299
     */
300
    public function renumber($codejercicio = '')
301
    {
302
        $exerciseModel = new DinEjercicio();
303
        $where = empty($codejercicio) ? [] : [new DataBaseWhere('codejercicio', $codejercicio)];
304
        foreach ($exerciseModel->all($where) as $exe) {
305
            if (false === $exe->isOpened()) {
0 ignored issues
show
Bug introduced by
The method isOpened() does not exist on FacturaScripts\Core\Model\Base\ModelClass. It seems like you code against a sub-type of said class. However, the method does not exist in FacturaScripts\Core\Model\Base\Contact or FacturaScripts\Core\Model\Base\Address or FacturaScripts\Dinamic\Model\Base\ModelClass or FacturaScripts\Core\Model\Base\ModelOnChangeClass or FacturaScripts\Core\Model\Base\BankAccount or FacturaScripts\Core\Model\Base\Payment or FacturaScripts\Core\Model\Base\ReportAccounting or FacturaScripts\Dinamic\Model\Base\Contact or FacturaScripts\Core\Model\Base\ComercialContact or FacturaScripts\Dinamic\Model\Base\ComercialContact or FacturaScripts\Dinamic\Model\Base\Address or FacturaScripts\Core\Model\Base\Receipt or FacturaScripts\Core\Mode...se\BusinessDocumentLine or FacturaScripts\Dinamic\M...Base\ModelOnChangeClass or FacturaScripts\Core\Model\Base\BusinessDocument or FacturaScripts\Dinamic\Model\Base\Receipt or FacturaScripts\Dinamic\M...se\BusinessDocumentLine or FacturaScripts\Core\Mode...se\PurchaseDocumentLine or FacturaScripts\Core\Model\Base\SalesDocumentLine or FacturaScripts\Dinamic\M...se\PurchaseDocumentLine or FacturaScripts\Dinamic\M...\Base\SalesDocumentLine or FacturaScripts\Dinamic\Model\Base\BusinessDocument or FacturaScripts\Core\Model\Base\TransformerDocument or FacturaScripts\Core\Model\Base\PurchaseDocument or FacturaScripts\Dinamic\M...ase\TransformerDocument or FacturaScripts\Core\Model\Base\SalesDocument or FacturaScripts\Dinamic\Model\Base\PurchaseDocument or FacturaScripts\Dinamic\Model\Base\SalesDocument or FacturaScripts\Dinamic\Model\Base\BankAccount or FacturaScripts\Dinamic\Model\Base\Payment or FacturaScripts\Dinamic\Model\Base\ReportAccounting. Are you sure you never get one of those? ( Ignorable by Annotation )

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

305
            if (false === $exe->/** @scrutinizer ignore-call */ isOpened()) {
Loading history...
306
                continue;
307
            }
308
309
            $offset = 0;
310
            $number = 1;
311
            $sql = 'SELECT idasiento,numero,fecha FROM ' . static::tableName()
312
                . ' WHERE codejercicio = ' . self::$dataBase->var2str($exe->codejercicio)
313
                . ' ORDER BY codejercicio ASC, fecha ASC, idasiento ASC';
314
315
            $rows = self::$dataBase->selectLimit($sql, self::RENUMBER_LIMIT, $offset);
316
            while (!empty($rows)) {
317
                if (false === $this->renumberAccEntries($rows, $number)) {
318
                    $this->toolBox()->i18nLog()->warning('renumber-accounting-error', ['%exerciseCode%' => $exe->codejercicio]);
319
                    return false;
320
                }
321
322
                $offset += self::RENUMBER_LIMIT;
323
                $rows = self::$dataBase->selectLimit($sql, self::RENUMBER_LIMIT, $offset);
324
            }
325
        }
326
327
        return true;
328
    }
329
330
    /**
331
     * @return bool
332
     */
333
    public function save()
334
    {
335
        if (empty($this->codejercicio)) {
336
            $this->setDate($this->fecha);
337
        }
338
339
        if (false === $this->getExercise()->isOpened()) {
340
            $this->toolBox()->i18nLog()->warning('closed-exercise', ['%exerciseName%' => $this->getExercise()->nombre]);
341
            return false;
342
        }
343
344
        $reg = new DinRegularizacionImpuesto();
345
        if ($reg->loadFechaInside($this->fecha) && $reg->bloquear) {
346
            $this->toolBox()->i18nLog()->warning('accounting-within-regularization');
347
            return false;
348
        }
349
350
        if (false === $this->isEditable()) {
351
            $this->toolBox()->i18nLog()->warning('non-editable-accounting-entry');
352
            return false;
353
        }
354
355
        return parent::save();
356
    }
357
358
    /**
359
     * @param string $date
360
     *
361
     * @return bool
362
     */
363
    public function setDate($date)
364
    {
365
        $exercise = new DinEjercicio();
366
        $exercise->idempresa = $this->idempresa;
367
        if ($exercise->loadFromDate($date)) {
368
            $this->codejercicio = $exercise->codejercicio;
369
            $this->fecha = $date;
370
            return true;
371
        }
372
373
        return false;
374
    }
375
376
    /**
377
     * Returns the name of the table that uses this model.
378
     *
379
     * @return string
380
     */
381
    public static function tableName()
382
    {
383
        return 'asientos';
384
    }
385
386
    /**
387
     * Returns True if there is no errors on properties values.
388
     *
389
     * @return bool
390
     */
391
    public function test(): bool
392
    {
393
        $utils = $this->toolBox()->utils();
394
        $this->concepto = $utils->noHtml($this->concepto);
395
        $this->documento = $utils->noHtml($this->documento);
396
397
        if (strlen($this->concepto) == 0 || strlen($this->concepto) > 255) {
398
            $this->toolBox()->i18nLog()->warning('invalid-column-lenght', ['%column%' => 'concepto', '%min%' => '1', '%max%' => '255']);
399
            return false;
400
        }
401
402
        if (empty($this->canal)) {
403
            $this->canal = null;
404
        }
405
406
        return parent::test();
407
    }
408
409
    /**
410
     * Check if the accounting entry is editable.
411
     *
412
     * @return bool
413
     */
414
    protected function isEditable(): bool
415
    {
416
        return $this->editable || $this->previousData['editable'];
417
    }
418
419
    /**
420
     * @param string $field
421
     *
422
     * @return bool
423
     */
424
    protected function onChange($field)
425
    {
426
        switch ($field) {
427
            case 'codejercicio':
428
                $this->toolBox()->i18nLog()->warning('cant-change-accounting-entry-exercise');
429
                return false;
430
431
            case 'fecha':
432
                $this->setDate($this->fecha);
433
                if ($this->codejercicio != $this->previousData['codejercicio']) {
434
                    $this->toolBox()->i18nLog()->warning('cant-change-accounting-entry-exercise');
435
                    return false;
436
                }
437
                return true;
438
        }
439
440
        return parent::onChange($field);
441
    }
442
443
    /**
444
     * Update accounting entry numbers.
445
     *
446
     * @param array $entries
447
     * @param int $number
448
     *
449
     * @return bool
450
     */
451
    protected function renumberAccEntries(&$entries, &$number)
452
    {
453
        $sql = '';
454
        foreach ($entries as $row) {
455
            if (self::$dataBase->var2str($row['numero']) !== self::$dataBase->var2str($number)) {
456
                $sql .= 'UPDATE ' . static::tableName() . ' SET numero = ' . self::$dataBase->var2str($number)
457
                    . ' WHERE idasiento = ' . self::$dataBase->var2str($row['idasiento']) . ';';
458
            }
459
460
            ++$number;
461
        }
462
463
        return empty($sql) || self::$dataBase->exec($sql);
464
    }
465
466
    /**
467
     * Insert the model data in the database.
468
     *
469
     * @param array $values
470
     *
471
     * @return bool
472
     */
473
    protected function saveInsert(array $values = []): bool
474
    {
475
        $this->numero = $this->newCode('numero');
476
        return parent::saveInsert($values);
477
    }
478
479
    /**
480
     * @param array $fields
481
     */
482
    protected function setPreviousData(array $fields = [])
483
    {
484
        $more = ['codejercicio', 'editable', 'fecha'];
485
        parent::setPreviousData(array_merge($more, $fields));
486
    }
487
}
488