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) { |
|
|
|
|
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
|
|
|
|