1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
/** |
4
|
|
|
* TransactionRequest.php |
5
|
|
|
* Copyright (c) 2018 [email protected] |
6
|
|
|
* |
7
|
|
|
* This file is part of Firefly III. |
8
|
|
|
* |
9
|
|
|
* Firefly III is free software: you can redistribute it and/or modify |
10
|
|
|
* it under the terms of the GNU General Public License as published by |
11
|
|
|
* the Free Software Foundation, either version 3 of the License, or |
12
|
|
|
* (at your option) any later version. |
13
|
|
|
* |
14
|
|
|
* Firefly III is distributed in the hope that it will be useful, |
15
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
16
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17
|
|
|
* GNU General Public License for more details. |
18
|
|
|
* |
19
|
|
|
* You should have received a copy of the GNU General Public License |
20
|
|
|
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>. |
21
|
|
|
*/ |
22
|
|
|
|
23
|
|
|
|
24
|
|
|
namespace FireflyIII\Api\V1\Requests; |
25
|
|
|
|
26
|
|
|
use FireflyIII\Exceptions\FireflyException; |
27
|
|
|
use FireflyIII\Models\Account; |
28
|
|
|
use FireflyIII\Models\AccountType; |
29
|
|
|
use FireflyIII\Models\Transaction; |
30
|
|
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface; |
31
|
|
|
use FireflyIII\Rules\BelongsUser; |
32
|
|
|
use Illuminate\Validation\Validator; |
33
|
|
|
|
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Class TransactionRequest |
37
|
|
|
*/ |
38
|
|
|
class TransactionRequest extends Request |
39
|
|
|
{ |
40
|
|
|
/** |
41
|
|
|
* @return bool |
42
|
|
|
*/ |
43
|
|
|
public function authorize(): bool |
44
|
|
|
{ |
45
|
|
|
// Only allow authenticated users |
46
|
|
|
return auth()->check(); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @return array |
51
|
|
|
*/ |
52
|
|
|
public function getAll(): array |
53
|
|
|
{ |
54
|
|
|
$data = [ |
55
|
|
|
// basic fields for journal: |
56
|
|
|
'type' => $this->string('type'), |
57
|
|
|
'date' => $this->date('date'), |
58
|
|
|
'description' => $this->string('description'), |
59
|
|
|
'piggy_bank_id' => $this->integer('piggy_bank_id'), |
60
|
|
|
'piggy_bank_name' => $this->string('piggy_bank_name'), |
61
|
|
|
'bill_id' => $this->integer('bill_id'), |
62
|
|
|
'bill_name' => $this->string('bill_name'), |
63
|
|
|
'tags' => explode(',', $this->string('tags')), |
64
|
|
|
|
65
|
|
|
// then, custom fields for journal |
66
|
|
|
'interest_date' => $this->date('interest_date'), |
67
|
|
|
'book_date' => $this->date('book_date'), |
68
|
|
|
'process_date' => $this->date('process_date'), |
69
|
|
|
'due_date' => $this->date('due_date'), |
70
|
|
|
'payment_date' => $this->date('payment_date'), |
71
|
|
|
'invoice_date' => $this->date('invoice_date'), |
72
|
|
|
'internal_reference' => $this->string('internal_reference'), |
73
|
|
|
'notes' => $this->string('notes'), |
74
|
|
|
|
75
|
|
|
// then, transactions (see below). |
76
|
|
|
'transactions' => [], |
77
|
|
|
|
78
|
|
|
]; |
79
|
|
|
foreach ($this->get('transactions') as $index => $transaction) { |
80
|
|
|
$array = [ |
81
|
|
|
'description' => $transaction['description'] ?? null, |
82
|
|
|
'amount' => $transaction['amount'], |
83
|
|
|
'currency_id' => isset($transaction['currency_id']) ? (int)$transaction['currency_id'] : null, |
84
|
|
|
'currency_code' => $transaction['currency_code'] ?? null, |
85
|
|
|
'foreign_amount' => $transaction['foreign_amount'] ?? null, |
86
|
|
|
'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? (int)$transaction['foreign_currency_id'] : null, |
87
|
|
|
'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null, |
88
|
|
|
'budget_id' => isset($transaction['budget_id']) ? (int)$transaction['budget_id'] : null, |
89
|
|
|
'budget_name' => $transaction['budget_name'] ?? null, |
90
|
|
|
'category_id' => isset($transaction['category_id']) ? (int)$transaction['category_id'] : null, |
91
|
|
|
'category_name' => $transaction['category_name'] ?? null, |
92
|
|
|
'source_id' => isset($transaction['source_id']) ? (int)$transaction['source_id'] : null, |
93
|
|
|
'source_name' => isset($transaction['source_name']) ? (string)$transaction['source_name'] : null, |
94
|
|
|
'destination_id' => isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null, |
95
|
|
|
'destination_name' => isset($transaction['destination_name']) ? (string)$transaction['destination_name'] : null, |
96
|
|
|
'reconciled' => $transaction['reconciled'] ?? false, |
97
|
|
|
'identifier' => $index, |
98
|
|
|
]; |
99
|
|
|
$data['transactions'][] = $array; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
return $data; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @return array |
107
|
|
|
*/ |
108
|
|
|
public function rules(): array |
109
|
|
|
{ |
110
|
|
|
$rules = [ |
111
|
|
|
// basic fields for journal: |
112
|
|
|
'type' => 'required|in:withdrawal,deposit,transfer', |
113
|
|
|
'date' => 'required|date', |
114
|
|
|
'description' => 'between:1,255', |
115
|
|
|
'piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser], |
116
|
|
|
'piggy_bank_name' => ['between:1,255', 'nullable', new BelongsUser], |
117
|
|
|
'bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser], |
118
|
|
|
'bill_name' => ['between:1,255', 'nullable', new BelongsUser], |
119
|
|
|
'tags' => 'between:1,255', |
120
|
|
|
|
121
|
|
|
// then, custom fields for journal |
122
|
|
|
'interest_date' => 'date|nullable', |
123
|
|
|
'book_date' => 'date|nullable', |
124
|
|
|
'process_date' => 'date|nullable', |
125
|
|
|
'due_date' => 'date|nullable', |
126
|
|
|
'payment_date' => 'date|nullable', |
127
|
|
|
'invoice_date' => 'date|nullable', |
128
|
|
|
'internal_reference' => 'min:1,max:255|nullable', |
129
|
|
|
'notes' => 'min:1,max:50000|nullable', |
130
|
|
|
|
131
|
|
|
// transaction rules (in array for splits): |
132
|
|
|
'transactions.*.description' => 'nullable|between:1,255', |
133
|
|
|
'transactions.*.amount' => 'required|numeric|more:0', |
134
|
|
|
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|required_without:transactions.*.currency_code', |
135
|
|
|
'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code|required_without:transactions.*.currency_id', |
136
|
|
|
'transactions.*.foreign_amount' => 'numeric|more:0', |
137
|
|
|
'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id', |
138
|
|
|
'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code', |
139
|
|
|
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser], |
140
|
|
|
'transactions.*.budget_name' => ['between:1,255', 'nullable', new BelongsUser], |
141
|
|
|
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser], |
142
|
|
|
'transactions.*.category_name' => 'between:1,255|nullable', |
143
|
|
|
'transactions.*.reconciled' => 'boolean|nullable', |
144
|
|
|
// basic rules will be expanded later. |
145
|
|
|
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser], |
146
|
|
|
'transactions.*.source_name' => 'between:1,255|nullable', |
147
|
|
|
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser], |
148
|
|
|
'transactions.*.destination_name' => 'between:1,255|nullable', |
149
|
|
|
]; |
150
|
|
|
|
151
|
|
|
switch ($this->method()) { |
152
|
|
|
default: |
153
|
|
|
break; |
154
|
|
|
case 'PUT': |
|
|
|
|
155
|
|
|
case 'PATCH': |
|
|
|
|
156
|
|
|
unset($rules['type'], $rules['piggy_bank_id'], $rules['piggy_bank_name']); |
157
|
|
|
break; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
return $rules; |
161
|
|
|
|
162
|
|
|
|
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Configure the validator instance. |
167
|
|
|
* |
168
|
|
|
* @param Validator $validator |
169
|
|
|
* |
170
|
|
|
* @return void |
171
|
|
|
* @throws \FireflyIII\Exceptions\FireflyException |
172
|
|
|
*/ |
173
|
|
|
public function withValidator(Validator $validator): void |
174
|
|
|
{ |
175
|
|
|
$validator->after( |
176
|
|
|
function (Validator $validator) { |
177
|
|
|
$this->atLeastOneTransaction($validator); |
178
|
|
|
$this->checkValidDescriptions($validator); |
179
|
|
|
$this->equalToJournalDescription($validator); |
180
|
|
|
$this->emptySplitDescriptions($validator); |
181
|
|
|
$this->foreignCurrencyInformation($validator); |
182
|
|
|
$this->validateAccountInformation($validator); |
183
|
|
|
$this->validateSplitAccounts($validator); |
184
|
|
|
} |
185
|
|
|
); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Throws an error when this asset account is invalid. |
190
|
|
|
* |
191
|
|
|
* @param Validator $validator |
192
|
|
|
* @param int|null $accountId |
193
|
|
|
* @param null|string $accountName |
194
|
|
|
* @param string $idField |
195
|
|
|
* @param string $nameField |
196
|
|
|
* |
197
|
|
|
* @return null|Account |
198
|
|
|
*/ |
199
|
|
|
protected function assetAccountExists(Validator $validator, ?int $accountId, ?string $accountName, string $idField, string $nameField): ?Account |
200
|
|
|
{ |
201
|
|
|
|
202
|
|
|
$accountId = (int)$accountId; |
203
|
|
|
$accountName = (string)$accountName; |
204
|
|
|
// both empty? hard exit. |
205
|
|
|
if ($accountId < 1 && strlen($accountName) === 0) { |
206
|
|
|
$validator->errors()->add($idField, trans('validation.filled', ['attribute' => $idField])); |
|
|
|
|
207
|
|
|
|
208
|
|
|
return null; |
209
|
|
|
} |
210
|
|
|
// ID belongs to user and is asset account: |
211
|
|
|
/** @var AccountRepositoryInterface $repository */ |
212
|
|
|
$repository = app(AccountRepositoryInterface::class); |
213
|
|
|
$repository->setUser(auth()->user()); |
214
|
|
|
$set = $repository->getAccountsById([$accountId]); |
215
|
|
|
if ($set->count() === 1) { |
216
|
|
|
/** @var Account $first */ |
217
|
|
|
$first = $set->first(); |
218
|
|
|
if ($first->accountType->type !== AccountType::ASSET) { |
|
|
|
|
219
|
|
|
$validator->errors()->add($idField, trans('validation.belongs_user')); |
220
|
|
|
|
221
|
|
|
return null; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
// we ignore the account name at this point. |
225
|
|
|
return $first; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
$account = $repository->findByNameNull($accountName, [AccountType::ASSET]); |
|
|
|
|
229
|
|
|
if (null === $account) { |
230
|
|
|
$validator->errors()->add($nameField, trans('validation.belongs_user')); |
231
|
|
|
|
232
|
|
|
return null; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
return $account; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Adds an error to the validator when there are no transactions in the array of data. |
240
|
|
|
* |
241
|
|
|
* @param Validator $validator |
242
|
|
|
*/ |
243
|
|
|
protected function atLeastOneTransaction(Validator $validator): void |
244
|
|
|
{ |
245
|
|
|
$data = $validator->getData(); |
246
|
|
|
$transactions = $data['transactions'] ?? []; |
247
|
|
|
// need at least one transaction |
248
|
|
|
if (count($transactions) === 0) { |
249
|
|
|
$validator->errors()->add('description', trans('validation.at_least_one_transaction')); |
|
|
|
|
250
|
|
|
} |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Adds an error to the "description" field when the user has submitted no descriptions and no |
255
|
|
|
* journal description. |
256
|
|
|
* |
257
|
|
|
* @param Validator $validator |
258
|
|
|
*/ |
259
|
|
|
protected function checkValidDescriptions(Validator $validator) |
260
|
|
|
{ |
261
|
|
|
$data = $validator->getData(); |
262
|
|
|
$transactions = $data['transactions'] ?? []; |
263
|
|
|
$journalDescription = (string)($data['description'] ?? ''); |
264
|
|
|
$validDescriptions = 0; |
265
|
|
|
foreach ($transactions as $index => $transaction) { |
266
|
|
|
if (strlen((string)($transaction['description'] ?? '')) > 0) { |
267
|
|
|
$validDescriptions++; |
268
|
|
|
} |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
// no valid descriptions and empty journal description? error. |
272
|
|
|
if ($validDescriptions === 0 && strlen($journalDescription) === 0) { |
273
|
|
|
$validator->errors()->add('description', trans('validation.filled', ['attribute' => trans('validation.attributes.description')])); |
|
|
|
|
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Adds an error to the validator when the user submits a split transaction (more than 1 transactions) |
280
|
|
|
* but does not give them a description. |
281
|
|
|
* |
282
|
|
|
* @param Validator $validator |
283
|
|
|
*/ |
284
|
|
|
protected function emptySplitDescriptions(Validator $validator): void |
285
|
|
|
{ |
286
|
|
|
$data = $validator->getData(); |
287
|
|
|
$transactions = $data['transactions'] ?? []; |
288
|
|
|
foreach ($transactions as $index => $transaction) { |
289
|
|
|
$description = (string)($transaction['description'] ?? ''); |
290
|
|
|
// filled description is mandatory for split transactions. |
291
|
|
|
if (count($transactions) > 1 && strlen($description) === 0) { |
292
|
|
|
$validator->errors()->add( |
293
|
|
|
'transactions.' . $index . '.description', |
294
|
|
|
trans('validation.filled', ['attribute' => trans('validation.attributes.transaction_description')]) |
|
|
|
|
295
|
|
|
); |
296
|
|
|
} |
297
|
|
|
} |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Adds an error to the validator when any transaction descriptions are equal to the journal description. |
302
|
|
|
* |
303
|
|
|
* @param Validator $validator |
304
|
|
|
*/ |
305
|
|
|
protected function equalToJournalDescription(Validator $validator): void |
306
|
|
|
{ |
307
|
|
|
$data = $validator->getData(); |
308
|
|
|
$transactions = $data['transactions'] ?? []; |
309
|
|
|
$journalDescription = (string)($data['description'] ?? ''); |
310
|
|
|
foreach ($transactions as $index => $transaction) { |
311
|
|
|
$description = (string)($transaction['description'] ?? ''); |
312
|
|
|
// description cannot be equal to journal description. |
313
|
|
|
if ($description === $journalDescription) { |
314
|
|
|
$validator->errors()->add('transactions.' . $index . '.description', trans('validation.equal_description')); |
|
|
|
|
315
|
|
|
} |
316
|
|
|
} |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* If the transactions contain foreign amounts, there must also be foreign currency information. |
321
|
|
|
* |
322
|
|
|
* @param Validator $validator |
323
|
|
|
*/ |
324
|
|
|
protected function foreignCurrencyInformation(Validator $validator): void |
325
|
|
|
{ |
326
|
|
|
$data = $validator->getData(); |
327
|
|
|
$transactions = $data['transactions'] ?? []; |
328
|
|
|
foreach ($transactions as $index => $transaction) { |
329
|
|
|
// must have currency info. |
330
|
|
|
if (isset($transaction['foreign_amount']) |
331
|
|
|
&& !(isset($transaction['foreign_currency_id']) |
332
|
|
|
|| isset($transaction['foreign_currency_code']))) { |
333
|
|
|
$validator->errors()->add( |
334
|
|
|
'transactions.' . $index . '.foreign_amount', |
335
|
|
|
trans('validation.require_currency_info') |
|
|
|
|
336
|
|
|
); |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* Throws an error when the given opposing account (of type $type) is invalid. |
343
|
|
|
* Empty data is allowed, system will default to cash. |
344
|
|
|
* |
345
|
|
|
* @param Validator $validator |
346
|
|
|
* @param string $type |
347
|
|
|
* @param int|null $accountId |
348
|
|
|
* @param null|string $accountName |
349
|
|
|
* @param string $idField |
350
|
|
|
* |
351
|
|
|
* @return null|Account |
352
|
|
|
*/ |
353
|
|
|
protected function opposingAccountExists(Validator $validator, string $type, ?int $accountId, ?string $accountName, string $idField): ?Account |
354
|
|
|
{ |
355
|
|
|
$accountId = (int)$accountId; |
356
|
|
|
$accountName = (string)$accountName; |
357
|
|
|
// both empty? done! |
|
|
|
|
358
|
|
|
if ($accountId < 1 && strlen($accountName) === 0) { |
359
|
|
|
return null; |
360
|
|
|
} |
361
|
|
|
if ($accountId !== 0) { |
362
|
|
|
// ID belongs to user and is $type account: |
363
|
|
|
/** @var AccountRepositoryInterface $repository */ |
364
|
|
|
$repository = app(AccountRepositoryInterface::class); |
365
|
|
|
$repository->setUser(auth()->user()); |
366
|
|
|
$set = $repository->getAccountsById([$accountId]); |
367
|
|
|
if ($set->count() === 1) { |
368
|
|
|
/** @var Account $first */ |
369
|
|
|
$first = $set->first(); |
370
|
|
|
if ($first->accountType->type !== $type) { |
|
|
|
|
371
|
|
|
$validator->errors()->add($idField, trans('validation.belongs_user')); |
|
|
|
|
372
|
|
|
|
373
|
|
|
return null; |
374
|
|
|
} |
375
|
|
|
|
376
|
|
|
// we ignore the account name at this point. |
377
|
|
|
return $first; |
378
|
|
|
} |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
// not having an opposing account by this name is NOT a problem. |
382
|
|
|
return null; |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
/** |
386
|
|
|
* Validates the given account information. Switches on given transaction type. |
387
|
|
|
* |
388
|
|
|
* @param Validator $validator |
389
|
|
|
* |
390
|
|
|
* @throws FireflyException |
391
|
|
|
*/ |
392
|
|
|
protected function validateAccountInformation(Validator $validator): void |
393
|
|
|
{ |
394
|
|
|
$data = $validator->getData(); |
395
|
|
|
$transactions = $data['transactions'] ?? []; |
396
|
|
|
if (!isset($data['type'])) { |
397
|
|
|
// the journal may exist in the request: |
398
|
|
|
/** @var Transaction $transaction */ |
399
|
|
|
$transaction = $this->route()->parameter('transaction'); |
400
|
|
|
if (null === $transaction) { |
401
|
|
|
return; // @codeCoverageIgnore |
402
|
|
|
} |
403
|
|
|
$data['type'] = strtolower($transaction->transactionJournal->transactionType->type); |
|
|
|
|
404
|
|
|
} |
405
|
|
|
foreach ($transactions as $index => $transaction) { |
406
|
|
|
$sourceId = isset($transaction['source_id']) ? (int)$transaction['source_id'] : null; |
407
|
|
|
$sourceName = $transaction['source_name'] ?? null; |
408
|
|
|
$destinationId = isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null; |
409
|
|
|
$destinationName = $transaction['destination_name'] ?? null; |
410
|
|
|
$sourceAccount = null; |
411
|
|
|
$destinationAccount = null; |
412
|
|
|
switch ($data['type']) { |
413
|
|
|
case 'withdrawal': |
|
|
|
|
414
|
|
|
$idField = 'transactions.' . $index . '.source_id'; |
415
|
|
|
$nameField = 'transactions.' . $index . '.source_name'; |
416
|
|
|
$sourceAccount = $this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField); |
417
|
|
|
$idField = 'transactions.' . $index . '.destination_id'; |
418
|
|
|
$destinationAccount = $this->opposingAccountExists($validator, AccountType::EXPENSE, $destinationId, $destinationName, $idField); |
419
|
|
|
break; |
420
|
|
|
case 'deposit': |
|
|
|
|
421
|
|
|
$idField = 'transactions.' . $index . '.source_id'; |
422
|
|
|
$sourceAccount = $this->opposingAccountExists($validator, AccountType::REVENUE, $sourceId, $sourceName, $idField); |
423
|
|
|
|
424
|
|
|
$idField = 'transactions.' . $index . '.destination_id'; |
425
|
|
|
$nameField = 'transactions.' . $index . '.destination_name'; |
426
|
|
|
$destinationAccount = $this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField); |
427
|
|
|
break; |
428
|
|
|
case 'transfer': |
|
|
|
|
429
|
|
|
$idField = 'transactions.' . $index . '.source_id'; |
430
|
|
|
$nameField = 'transactions.' . $index . '.source_name'; |
431
|
|
|
$sourceAccount = $this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField); |
432
|
|
|
|
433
|
|
|
$idField = 'transactions.' . $index . '.destination_id'; |
434
|
|
|
$nameField = 'transactions.' . $index . '.destination_name'; |
435
|
|
|
$destinationAccount = $this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField); |
436
|
|
|
break; |
437
|
|
|
default: |
438
|
|
|
// @codeCoverageIgnoreStart |
439
|
|
|
throw new FireflyException( |
440
|
|
|
sprintf('The validator cannot handle transaction type "%s" in validateAccountInformation().', $data['type']) |
441
|
|
|
); |
442
|
|
|
// @codeCoverageIgnoreEnd |
443
|
|
|
|
444
|
|
|
} |
445
|
|
|
// add some errors in case of same account submitted: |
446
|
|
|
if (null !== $sourceAccount && null !== $destinationAccount && $sourceAccount->id === $destinationAccount->id) { |
447
|
|
|
$validator->errors()->add($idField, trans('validation.source_equals_destination')); |
|
|
|
|
448
|
|
|
} |
449
|
|
|
} |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
/** |
453
|
|
|
* @param Validator $validator |
454
|
|
|
* |
455
|
|
|
* @throws FireflyException |
456
|
|
|
*/ |
457
|
|
|
protected function validateSplitAccounts(Validator $validator) |
458
|
|
|
{ |
459
|
|
|
$data = $validator->getData(); |
460
|
|
|
$count = isset($data['transactions']) ? count($data['transactions']) : 0; |
461
|
|
|
if ($count < 2) { |
462
|
|
|
return; |
463
|
|
|
} |
464
|
|
|
// this is pretty much impossible: |
465
|
|
|
// @codeCoverageIgnoreStart |
466
|
|
|
if (!isset($data['type'])) { |
467
|
|
|
// the journal may exist in the request: |
468
|
|
|
/** @var Transaction $transaction */ |
469
|
|
|
$transaction = $this->route()->parameter('transaction'); |
470
|
|
|
if (null === $transaction) { |
471
|
|
|
return; |
472
|
|
|
} |
473
|
|
|
$data['type'] = strtolower($transaction->transactionJournal->transactionType->type); |
|
|
|
|
474
|
|
|
} |
475
|
|
|
// @codeCoverageIgnoreEnd |
476
|
|
|
|
477
|
|
|
// collect all source ID's and destination ID's, if present: |
478
|
|
|
$sources = []; |
479
|
|
|
$destinations = []; |
480
|
|
|
|
481
|
|
|
foreach ($data['transactions'] as $transaction) { |
482
|
|
|
$sources[] = isset($transaction['source_id']) ? (int)$transaction['source_id'] : 0; |
483
|
|
|
$destinations[] = isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : 0; |
484
|
|
|
} |
485
|
|
|
$destinations = array_unique($destinations); |
486
|
|
|
$sources = array_unique($sources); |
487
|
|
|
// switch on type: |
488
|
|
|
switch ($data['type']) { |
489
|
|
|
case 'withdrawal': |
|
|
|
|
490
|
|
|
if (count($sources) > 1) { |
491
|
|
|
$validator->errors()->add('transactions.0.source_id', trans('validation.all_accounts_equal')); |
|
|
|
|
492
|
|
|
} |
493
|
|
|
break; |
494
|
|
|
case 'deposit': |
|
|
|
|
495
|
|
|
if (count($destinations) > 1) { |
496
|
|
|
$validator->errors()->add('transactions.0.destination_id', trans('validation.all_accounts_equal')); |
497
|
|
|
} |
498
|
|
|
break; |
499
|
|
|
case 'transfer': |
|
|
|
|
500
|
|
|
if (count($sources) > 1 || count($destinations) > 1) { |
501
|
|
|
$validator->errors()->add('transactions.0.source_id', trans('validation.all_accounts_equal')); |
502
|
|
|
$validator->errors()->add('transactions.0.destination_id', trans('validation.all_accounts_equal')); |
503
|
|
|
} |
504
|
|
|
break; |
505
|
|
|
default: |
506
|
|
|
// @codeCoverageIgnoreStart |
507
|
|
|
throw new FireflyException( |
508
|
|
|
sprintf('The validator cannot handle transaction type "%s" in validateSplitAccounts().', $data['type']) |
509
|
|
|
); |
510
|
|
|
// @codeCoverageIgnoreEnd |
511
|
|
|
} |
512
|
|
|
} |
513
|
|
|
|
514
|
|
|
} |
515
|
|
|
|
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.