Completed
Push — master ( b43734...838a39 )
by Carlos
06:00 queued 18s
created

Core/Lib/AccountingEntryTools.php (1 issue)

Severity
1
<?php
2
/**
3
 * This file is part of FacturaScripts
4
 * Copyright (C) 2019 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
namespace FacturaScripts\Core\Lib;
20
21
use FacturaScripts\Core\Base\DataBase\DataBaseWhere;
22
use FacturaScripts\Core\Base\ToolBox;
23
use FacturaScripts\Core\Lib\ExtendedController\GridView;
24
use FacturaScripts\Core\Model\ModelView\SubcuentaSaldo;
25
use FacturaScripts\Dinamic\Lib\Accounting\AccountingAccounts;
26
use FacturaScripts\Dinamic\Model\Cliente;
27
use FacturaScripts\Dinamic\Model\Impuesto;
28
use FacturaScripts\Dinamic\Model\Proveedor;
29
use FacturaScripts\Dinamic\Model\Subcuenta;
30
31
/**
32
 * A set of tools to recalculate accounting entries.
33
 *
34
 * @author Artex Trading sa     <[email protected]>
35
 * @author Carlos García Gómez  <[email protected]>
36
 */
37
class AccountingEntryTools
38
{
39
40
    const TYPE_TAX_NONE = 0;
41
    const TYPE_TAX_INPUT = 1;
42
    const TYPE_TAX_OUTPUT = 2;
43
44
    /**
45
     *
46
     * @var SubAccountTools
47
     */
48
    protected $subAccountTools;
49
50
    /**
51
     * Class constructor
52
     */
53
    public function __construct()
54
    {
55
        $this->subAccountTools = new SubAccountTools();
56
    }
57
58
    /**
59
     * Load data and balances from subaccount
60
     *
61
     * @param string $exercise
62
     * @param string $codeSubAccount
63
     * @param int    $channel
64
     *
65
     * @return array
66
     */
67
    public function getAccountData($exercise, $codeSubAccount, $channel): array
68
    {
69
        $result = [
70
            'subaccount' => $codeSubAccount,
71
            'description' => '',
72
            'specialaccount' => '',
73
            'balance' => 0.00,
74
            'detail' => [0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00]
75
        ];
76
77
        if (empty($exercise) || empty($codeSubAccount)) {
78
            return $result;
79
        }
80
81
        $where = [
82
            new DataBaseWhere('codsubcuenta', $codeSubAccount),
83
            new DataBaseWhere('codejercicio', $exercise)
84
        ];
85
86
        $subAccount = new Subcuenta();
87
        if ($subAccount->loadFromCode(null, $where)) {
88
            $result['description'] = $subAccount->descripcion;
89
            $result['specialaccount'] = $subAccount->getSpecialAccountCode();
90
            $result['hasvat'] = $this->subAccountTools->hasTax($result['specialaccount']);
91
92
            if ($this->toolBox()->appSettings()->get('default', 'balancegraphic', false)) {
93
                $balance = new SubcuentaSaldo();
94
                $result['balance'] = $balance->setSubAccountBalance($subAccount->idsubcuenta, $channel, $result['detail']);
95
                $result['balance'] = $this->toolBox()->coins()->format($result['balance']);
96
            }
97
        }
98
99
        return $result;
100
    }
101
102
    /**
103
     * Calculate data document
104
     *
105
     * @param GridView $view
106
     * @param array    $data
107
     *
108
     * @return array
109
     */
110
    public function recalculate($view, &$data): array
111
    {
112
        $result = [
113
            'total' => 0.00,
114
            'unbalance' => 0.00,
115
            'lines' => [],
116
            'subaccount' => [],
117
            'vat' => []
118
        ];
119
120
        if (isset($data['lines'])) {
121
            // Prepare lines data
122
            $lines = $view->processFormLines($data['lines']);
123
124
            // Recalculate lines data and amounts
125
            $totalCredit = $totalDebit = 0.00;
126
            $result['lines'] = $this->recalculateLines($lines, $totalCredit, $totalDebit);
127
            $this->calculateAmounts($result, $totalCredit, $totalDebit);
128
129
            // If only change subaccount, search for subaccount data
130
            if (count($data['changes']) === 1 && $data['changes'][0][1] === 'codsubcuenta') {
131
                $index = $data['changes'][0][0];
132
                $line = &$result['lines'][$index];
133
                $exercise = $data['document']['codejercicio'];
134
                $channel = $data['document']['canal'];
135
                $result['subaccount'] = $this->getAccountData($exercise, $line['codsubcuenta'], $channel);
136
                $result['vat'] = $this->recalculateVatRegister($line, $data['document'], (string) $result['subaccount']['specialaccount'], $result['unbalance']);
137
            }
138
        }
139
        $result['hasvat'] = !empty($result['vat']);
140
        return $result;
141
    }
142
143
    /**
144
     * Calculate unbalance and total imports
145
     *
146
     * @param array $data
147
     * @param float $credit
148
     * @param float $debit
149
     */
150
    protected function calculateAmounts(array &$data, float $credit, float $debit)
151
    {
152
        $unbalance = round(($credit - $debit), (int) FS_NF0);
153
        $index = count($data['lines']) - 1;
154
        $line = &$data['lines'][$index];
155
        $lineDebit = (double) $line['debe'] ?? 0.00;
156
        $lineCredit = (double) $line['haber'] ?? 0.00;
157
158
        if (($lineDebit + $lineCredit) === 0.00 && $index > 0) {
0 ignored issues
show
The condition $lineDebit + $lineCredit === 0.0 is always false.
Loading history...
159
            $offsetting = $data['lines'][$index - 1]['codcontrapartida'] ?? null;
160
            // if the sub-account is the same as the previous offsetting
161
            if ($line['codsubcuenta'] === $offsetting) {
162
                $field = $unbalance < 0 ? 'debe' : 'haber';
163
                $line[$field] = abs($unbalance);
164
                $unbalance = 0.00;
165
            }
166
        }
167
168
        $data['unbalance'] = $unbalance;
169
        $data['total'] = ($credit > $debit) ? round($credit, (int) FS_NF0) : round($debit, (int) FS_NF0);
170
    }
171
172
    /**
173
     * Auto complete data for new account line
174
     *
175
     * @param array $line
176
     * @param array $previousLine
177
     */
178
    protected function checkEmptyValues(array &$line, array $previousLine)
179
    {
180
        if (isset($line['codsubcuenta']) && isset($previousLine['codsubcuenta'])) {
181
            if (empty($line['concepto'])) {
182
                $line['concepto'] = $previousLine['concepto'];
183
            }
184
185
            if (empty($line['codcontrapartida']) && !empty($line['codsubcuenta'])) {
186
                // TODO: [Fix] Go through previous lines in search of the offsetting. The sub-account that uses the offsetting for the first time is needed
187
                $line['codcontrapartida'] = ($line['codsubcuenta'] === $previousLine['codcontrapartida'])
188
                    ? $previousLine['codsubcuenta']
189
                    : $previousLine['codcontrapartida'];
190
            }
191
        }
192
    }
193
194
    /**
195
     * Get VAT information for a sub-account
196
     *
197
     * @param string $exercise
198
     * @param string $codeSubAccount
199
     *
200
     * @return array
201
     */
202
    protected function getAccountVatID($exercise, $codeSubAccount): array
203
    {
204
        $result = ['group' => '', 'code' => '', 'description' => '', 'id' => '', 'surcharge' => false];
205
        if (empty($exercise) || empty($codeSubAccount)) {
206
            return $result;
207
        }
208
209
        $where = [
210
            new DataBaseWhere('codsubcuenta', $codeSubAccount),
211
            new DataBaseWhere('codejercicio', $exercise)
212
        ];
213
214
        $subAccount = new Subcuenta();
215
        if ($subAccount->loadFromCode(null, $where)) {
216
            $result['group'] = $subAccount->getSpecialAccountCode();
217
            switch ($result['group']) {
218
                case AccountingAccounts::SPECIAL_CUSTOMER_ACCOUNT:
219
                    $client = new Cliente();
220
                    $this->setBusinessData($client, $codeSubAccount, $result);
221
                    break;
222
223
                case AccountingAccounts::SPECIAL_CREDITOR_ACCOUNT:
224
                case AccountingAccounts::SPECIAL_SUPPLIER_ACCOUNT:
225
                    $supplier = new Proveedor();
226
                    $this->setBusinessData($supplier, $codeSubAccount, $result);
227
                    break;
228
            }
229
        }
230
        return $result;
231
    }
232
233
    /**
234
     * Calculate data lines and credit/debit imports
235
     *
236
     * @param array $lines
237
     * @param float $totalCredit
238
     * @param float $totalDebit
239
     *
240
     * @return array
241
     */
242
    protected function recalculateLines(array $lines, float &$totalCredit, float &$totalDebit): array
243
    {
244
        // Work variables
245
        $result = [];
246
        $previous = null;
247
        $totalCredit = 0.00;
248
        $totalDebit = 0.00;
249
250
        // Check lines data
251
        foreach ($lines as $item) {
252
            if (empty($item['codsubcuenta'])) {
253
                $result[] = $item;
254
                continue;
255
            }
256
257
            // check empty imports
258
            if (empty($item['debe'])) {
259
                $item['debe'] = 0.00;
260
            }
261
            if (empty($item['haber'])) {
262
                $item['haber'] = 0.00;
263
            }
264
265
            // copy previous values to empty fields
266
            if (!empty($previous)) {
267
                $this->checkEmptyValues($item, $previous);
268
            }
269
            $previous = $item;
270
271
            // Acumulate imports
272
            $totalCredit += $item['debe'];
273
            $totalDebit += $item['haber'];
274
            $result[] = $item;
275
        }
276
        return $result;
277
    }
278
279
    /**
280
     *
281
     * @param string $specialAccount
282
     * @return int
283
     */
284
    private function getTypeVat($specialAccount)
285
    {
286
        if (empty($specialAccount)) {
287
            return self::TYPE_TAX_NONE;
288
        }
289
290
        if ($this->subAccountTools->isInputTax($specialAccount)) {
291
            return self::TYPE_TAX_INPUT;
292
        }
293
294
        if ($this->subAccountTools->isOutputTax($specialAccount)) {
295
            return self::TYPE_TAX_OUTPUT;
296
        }
297
298
        return self::TYPE_TAX_NONE;
299
    }
300
301
    /**
302
     * Calculate Vat Register data
303
     *
304
     * @param array  $line
305
     * @param array  $document
306
     * @param string $specialAccount
307
     * @param float  $base
308
     *
309
     * @return array
310
     */
311
    protected function recalculateVatRegister(array &$line, array $document, string $specialAccount, float $base): array
312
    {
313
        $typeVat = $this->getTypeVat($specialAccount);
314
        if ($typeVat === self::TYPE_TAX_NONE) {
315
            $line['cifnif'] = null;
316
            $line['documento'] = null;
317
            $line['baseimponible'] = null;
318
            $line['iva'] = null;
319
            $line['recargo'] = null;
320
            return [];
321
        }
322
323
        $vatModel = new Impuesto();
324
        $vat = $typeVat == self::TYPE_TAX_INPUT
325
            ? $vatModel->inputVatFromSubAccount($line['codsubcuenta'])
326
            : $vatModel->outputVatFromSubAccount($line['codsubcuenta']);
327
328
        $result = $this->getAccountVatID($document['codejercicio'], $line['codcontrapartida']);
329
330
        $line['documento'] = $document['documento'] ?? null;
331
        $line['cifnif'] = $result['id'];
332
        $line['iva'] = $vat->iva;
333
        $line['recargo'] = $result['surcharge'] ? $vat->recargo : 0.00;
334
        $line['baseimponible'] = ($result['group'] === AccountingAccounts::SPECIAL_CUSTOMER_ACCOUNT) ? ($base * -1) : $base;
335
        return $result;
336
    }
337
338
    /**
339
     *
340
     * @param Cliente|Proveedor $model
341
     * @param string            $codeSubAccount
342
     * @param array             $values
343
     */
344
    private function setBusinessData($model, $codeSubAccount, &$values)
345
    {
346
        $where = [new DataBaseWhere('codsubcuenta', $codeSubAccount)];
347
        $supplier = new Proveedor();
348
        if ($supplier->loadFromCode(null, $where)) {
349
            $values['code'] = $supplier->codproveedor;
350
            $values['description'] = $supplier->nombre;
351
            $values['id'] = $supplier->cifnif;
352
            $values['surcharge'] = ($model->regimeniva == RegimenIVA::TAX_SYSTEM_SURCHARGE);
353
        }
354
    }
355
356
    /**
357
     *
358
     * @return ToolBox
359
     */
360
    private function toolBox()
361
    {
362
        return new ToolBox();
363
    }
364
}
365