1 | <?php |
||||
2 | /** |
||||
3 | * JournalServiceTrait.php |
||||
4 | * Copyright (c) 2019 [email protected] |
||||
5 | * |
||||
6 | * This file is part of Firefly III (https://github.com/firefly-iii). |
||||
7 | * |
||||
8 | * This program is free software: you can redistribute it and/or modify |
||||
9 | * it under the terms of the GNU Affero General Public License as |
||||
10 | * published by the Free Software Foundation, either version 3 of the |
||||
11 | * License, or (at your option) any later version. |
||||
12 | * |
||||
13 | * This program is distributed in the hope that it will be useful, |
||||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
16 | * GNU Affero General Public License for more details. |
||||
17 | * |
||||
18 | * You should have received a copy of the GNU Affero General Public License |
||||
19 | * along with this program. If not, see <https://www.gnu.org/licenses/>. |
||||
20 | */ |
||||
21 | |||||
22 | declare(strict_types=1); |
||||
23 | |||||
24 | namespace FireflyIII\Services\Internal\Support; |
||||
25 | |||||
26 | use Exception; |
||||
27 | use FireflyIII\Exceptions\FireflyException; |
||||
28 | use FireflyIII\Factory\AccountMetaFactory; |
||||
29 | use FireflyIII\Factory\TagFactory; |
||||
30 | use FireflyIII\Models\Account; |
||||
31 | use FireflyIII\Models\AccountType; |
||||
32 | use FireflyIII\Models\Note; |
||||
33 | use FireflyIII\Models\TransactionJournal; |
||||
34 | use FireflyIII\Models\TransactionType; |
||||
35 | use FireflyIII\Repositories\Account\AccountRepositoryInterface; |
||||
36 | use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; |
||||
37 | use FireflyIII\Repositories\Category\CategoryRepositoryInterface; |
||||
38 | use FireflyIII\Support\NullArrayObject; |
||||
39 | use Log; |
||||
40 | |||||
41 | /** |
||||
42 | * Trait JournalServiceTrait |
||||
43 | * |
||||
44 | */ |
||||
45 | trait JournalServiceTrait |
||||
46 | { |
||||
47 | /** @var AccountRepositoryInterface */ |
||||
48 | private $accountRepository; |
||||
49 | /** @var BudgetRepositoryInterface */ |
||||
50 | private $budgetRepository; |
||||
51 | /** @var CategoryRepositoryInterface */ |
||||
52 | private $categoryRepository; |
||||
53 | /** @var TagFactory */ |
||||
54 | private $tagFactory; |
||||
55 | |||||
56 | |||||
57 | /** |
||||
58 | * @param string|null $amount |
||||
59 | * |
||||
60 | * @return string |
||||
61 | * @codeCoverageIgnore |
||||
62 | */ |
||||
63 | protected function getForeignAmount(?string $amount): ?string |
||||
64 | { |
||||
65 | if (null === $amount) { |
||||
66 | Log::debug('No foreign amount info in array. Return NULL'); |
||||
67 | |||||
68 | return null; |
||||
69 | } |
||||
70 | if ('' === $amount) { |
||||
71 | Log::debug('Foreign amount is empty string, return NULL.'); |
||||
72 | |||||
73 | return null; |
||||
74 | } |
||||
75 | if (0 === bccomp('0', $amount)) { |
||||
76 | Log::debug('Foreign amount is 0.0, return NULL.'); |
||||
77 | |||||
78 | return null; |
||||
79 | } |
||||
80 | Log::debug(sprintf('Foreign amount is %s', $amount)); |
||||
81 | |||||
82 | return $amount; |
||||
83 | } |
||||
84 | |||||
85 | /** |
||||
86 | * @param string $transactionType |
||||
87 | * @param string $direction |
||||
88 | * @param array $data |
||||
89 | * |
||||
90 | * @throws FireflyException |
||||
91 | * @return Account |
||||
92 | * @codeCoverageIgnore |
||||
93 | */ |
||||
94 | protected function getAccount(string $transactionType, string $direction, array $data): Account |
||||
95 | { |
||||
96 | // some debug logging: |
||||
97 | Log::debug(sprintf('Now in getAccount(%s)', $direction), $data); |
||||
98 | |||||
99 | // final result: |
||||
100 | $result = null; |
||||
0 ignored issues
–
show
Unused Code
introduced
by
Loading history...
|
|||||
101 | |||||
102 | // expected type of source account, in order of preference |
||||
103 | /** @var array $array */ |
||||
104 | $array = config('firefly.expected_source_types'); |
||||
105 | $expectedTypes = $array[$direction]; |
||||
106 | unset($array); |
||||
107 | |||||
108 | // and now try to find it, based on the type of transaction. |
||||
109 | $message = 'Based on the fact that the transaction is a %s, the %s account should be in: %s. Direction is %s.'; |
||||
110 | Log::debug(sprintf($message, $transactionType, $direction, implode(', ', $expectedTypes[$transactionType]), $direction)); |
||||
111 | |||||
112 | $result = $this->findAccountById($data, $expectedTypes[$transactionType]); |
||||
113 | $result = $this->findAccountByName($result, $data, $expectedTypes[$transactionType]); |
||||
114 | $result = $this->findAccountByIban($result, $data, $expectedTypes[$transactionType]); |
||||
115 | $result = $this->getCashAccount($result, $data, $expectedTypes[$transactionType]); |
||||
116 | $result = $this->createAccount($result, $data, $expectedTypes[$transactionType][0]); |
||||
117 | |||||
118 | return $result; |
||||
119 | } |
||||
120 | |||||
121 | /** |
||||
122 | * @param string $amount |
||||
123 | * |
||||
124 | * @return string |
||||
125 | * @throws FireflyException |
||||
126 | * @codeCoverageIgnore |
||||
127 | */ |
||||
128 | protected function getAmount(string $amount): string |
||||
129 | { |
||||
130 | if ('' === $amount) { |
||||
131 | throw new FireflyException(sprintf('The amount cannot be an empty string: "%s"', $amount)); |
||||
132 | } |
||||
133 | if (0 === bccomp('0', $amount)) { |
||||
134 | throw new FireflyException(sprintf('The amount seems to be zero: "%s"', $amount)); |
||||
135 | } |
||||
136 | |||||
137 | return $amount; |
||||
138 | } |
||||
139 | |||||
140 | /** |
||||
141 | * @param TransactionJournal $journal |
||||
142 | * @param NullArrayObject $data |
||||
143 | * |
||||
144 | * @codeCoverageIgnore |
||||
145 | */ |
||||
146 | protected function storeBudget(TransactionJournal $journal, NullArrayObject $data): void |
||||
147 | { |
||||
148 | if (TransactionType::WITHDRAWAL !== $journal->transactionType->type) { |
||||
149 | $journal->budgets()->sync([]); |
||||
150 | |||||
151 | return; |
||||
152 | } |
||||
153 | $budget = $this->budgetRepository->findBudget($data['budget_id'], $data['budget_name']); |
||||
154 | if (null !== $budget) { |
||||
155 | Log::debug(sprintf('Link budget #%d to journal #%d', $budget->id, $journal->id)); |
||||
156 | $journal->budgets()->sync([$budget->id]); |
||||
157 | |||||
158 | return; |
||||
159 | } |
||||
160 | // if the budget is NULL, sync empty. |
||||
161 | $journal->budgets()->sync([]); |
||||
162 | } |
||||
163 | |||||
164 | /** |
||||
165 | * @param TransactionJournal $journal |
||||
166 | * @param NullArrayObject $data |
||||
167 | * |
||||
168 | * @codeCoverageIgnore |
||||
169 | */ |
||||
170 | protected function storeCategory(TransactionJournal $journal, NullArrayObject $data): void |
||||
171 | { |
||||
172 | $category = $this->categoryRepository->findCategory($data['category_id'], $data['category_name']); |
||||
173 | if (null !== $category) { |
||||
174 | Log::debug(sprintf('Link category #%d to journal #%d', $category->id, $journal->id)); |
||||
175 | $journal->categories()->sync([$category->id]); |
||||
176 | |||||
177 | return; |
||||
178 | } |
||||
179 | // if the category is NULL, sync empty. |
||||
180 | $journal->categories()->sync([]); |
||||
181 | } |
||||
182 | |||||
183 | /** |
||||
184 | * @param TransactionJournal $journal |
||||
185 | * @param string $notes |
||||
186 | * |
||||
187 | * @codeCoverageIgnore |
||||
188 | */ |
||||
189 | protected function storeNotes(TransactionJournal $journal, ?string $notes): void |
||||
190 | { |
||||
191 | $notes = (string)$notes; |
||||
192 | $note = $journal->notes()->first(); |
||||
193 | if ('' !== $notes) { |
||||
194 | if (null === $note) { |
||||
195 | $note = new Note; |
||||
196 | $note->noteable()->associate($journal); |
||||
197 | } |
||||
198 | $note->text = $notes; |
||||
199 | $note->save(); |
||||
200 | Log::debug(sprintf('Stored notes for journal #%d', $journal->id)); |
||||
201 | |||||
202 | return; |
||||
203 | } |
||||
204 | if ('' === $notes && null !== $note) { |
||||
205 | // try to delete existing notes. |
||||
206 | try { |
||||
207 | $note->delete(); |
||||
208 | // @codeCoverageIgnoreStart |
||||
209 | } catch (Exception $e) { |
||||
210 | Log::debug(sprintf('Could not delete journal notes: %s', $e->getMessage())); |
||||
211 | } |
||||
212 | // @codeCoverageIgnoreEnd |
||||
213 | } |
||||
214 | } |
||||
215 | |||||
216 | /** |
||||
217 | * Link tags to journal. |
||||
218 | * |
||||
219 | * @param TransactionJournal $journal |
||||
220 | * @param array $tags |
||||
221 | * |
||||
222 | * @codeCoverageIgnore |
||||
223 | */ |
||||
224 | protected function storeTags(TransactionJournal $journal, ?array $tags): void |
||||
225 | { |
||||
226 | |||||
227 | $this->tagFactory->setUser($journal->user); |
||||
228 | $set = []; |
||||
229 | if (!is_array($tags)) { |
||||
230 | return; |
||||
231 | } |
||||
232 | foreach ($tags as $string) { |
||||
233 | $string = (string)$string; |
||||
234 | if ('' !== $string) { |
||||
235 | $tag = $this->tagFactory->findOrCreate($string); |
||||
236 | if (null !== $tag) { |
||||
237 | $set[] = $tag->id; |
||||
238 | } |
||||
239 | } |
||||
240 | } |
||||
241 | $journal->tags()->sync($set); |
||||
242 | } |
||||
243 | |||||
244 | /** |
||||
245 | * @param array $data |
||||
246 | * @param array $types |
||||
247 | * |
||||
248 | * @return Account|null |
||||
249 | */ |
||||
250 | private function findAccountById(array $data, array $types): ?Account |
||||
251 | { |
||||
252 | $search = null; |
||||
253 | // first attempt, find by ID. |
||||
254 | if (null !== $data['id']) { |
||||
255 | $search = $this->accountRepository->findNull($data['id']); |
||||
256 | if (null !== $search && in_array($search->accountType->type, $types, true)) { |
||||
257 | Log::debug( |
||||
258 | sprintf('Found "account_id" object: #%d, "%s" of type %s', $search->id, $search->name, $search->accountType->type) |
||||
259 | ); |
||||
260 | } |
||||
261 | } |
||||
262 | |||||
263 | return $search; |
||||
264 | } |
||||
265 | |||||
266 | /** |
||||
267 | * @param Account|null $account |
||||
268 | * @param array $data |
||||
269 | * @param array $types |
||||
270 | * |
||||
271 | * @return Account|null |
||||
272 | */ |
||||
273 | private function findAccountByName(?Account $account, array $data, array $types): ?Account |
||||
274 | { |
||||
275 | // second attempt, find by name. |
||||
276 | if (null === $account && null !== $data['name']) { |
||||
277 | Log::debug('Found nothing by account ID.'); |
||||
278 | // find by preferred type. |
||||
279 | $source = $this->accountRepository->findByName($data['name'], [$types[0]]); |
||||
280 | // or any expected type. |
||||
281 | $source = $source ?? $this->accountRepository->findByName($data['name'], $types); |
||||
282 | |||||
283 | if (null !== $source) { |
||||
284 | Log::debug(sprintf('Found "account_name" object: #%d, %s', $source->id, $source->name)); |
||||
285 | |||||
286 | $account = $source; |
||||
287 | } |
||||
288 | } |
||||
289 | |||||
290 | return $account; |
||||
291 | } |
||||
292 | |||||
293 | /** |
||||
294 | * @param Account|null $account |
||||
295 | * @param array $data |
||||
296 | * @param array $types |
||||
297 | * |
||||
298 | * @return Account|null |
||||
299 | */ |
||||
300 | private function findAccountByIban(?Account $account, array $data, array $types): ?Account |
||||
301 | { |
||||
302 | // third attempt, find by IBAN |
||||
303 | if (null === $account && null !== $data['iban']) { |
||||
304 | Log::debug('Found nothing by account name.'); |
||||
305 | // find by preferred type. |
||||
306 | $source = $this->accountRepository->findByIbanNull($data['iban'], [$types[0]]); |
||||
307 | // or any expected type. |
||||
308 | $source = $source ?? $this->accountRepository->findByIbanNull($data['iban'], $types); |
||||
309 | |||||
310 | if (null !== $source) { |
||||
311 | Log::debug(sprintf('Found "account_iban" object: #%d, %s', $source->id, $source->name)); |
||||
312 | |||||
313 | $account = $source; |
||||
314 | } |
||||
315 | } |
||||
316 | |||||
317 | return $account; |
||||
318 | } |
||||
319 | |||||
320 | /** |
||||
321 | * @param Account|null $account |
||||
322 | * @param array $data |
||||
323 | * @param array $types |
||||
324 | * |
||||
325 | * @return Account|null |
||||
326 | */ |
||||
327 | private function getCashAccount(?Account $account, array $data, array $types): ?Account |
||||
328 | { |
||||
329 | // return cash account. |
||||
330 | if (null === $account && null === $data['name'] |
||||
331 | && in_array(AccountType::CASH, $types, true)) { |
||||
332 | $account = $this->accountRepository->getCashAccount(); |
||||
333 | } |
||||
334 | |||||
335 | return $account; |
||||
336 | } |
||||
337 | |||||
338 | /** |
||||
339 | * @param Account|null $account |
||||
340 | * @param array $data |
||||
341 | * @param string $preferredType |
||||
342 | * |
||||
343 | * @throws FireflyException |
||||
344 | * @return Account |
||||
345 | */ |
||||
346 | private function createAccount(?Account $account, array $data, string $preferredType): Account |
||||
347 | { |
||||
348 | Log::debug('Now in createAccount()', $data); |
||||
349 | // return new account. |
||||
350 | if (null === $account) { |
||||
351 | $data['name'] = $data['name'] ?? '(no name)'; |
||||
352 | |||||
353 | // final attempt, create it. |
||||
354 | if (AccountType::ASSET === $preferredType) { |
||||
355 | throw new FireflyException('TransactionFactory: Cannot create asset account with these values', $data); |
||||
0 ignored issues
–
show
$data of type array is incompatible with the type integer expected by parameter $code of FireflyIII\Exceptions\Fi...xception::__construct() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
356 | } |
||||
357 | |||||
358 | $account = $this->accountRepository->store( |
||||
359 | [ |
||||
360 | 'account_type_id' => null, |
||||
361 | 'account_type' => $preferredType, |
||||
362 | 'name' => $data['name'], |
||||
363 | 'virtual_balance' => null, |
||||
364 | 'active' => true, |
||||
365 | 'iban' => $data['iban'], |
||||
366 | 'currency_id' => $data['currency_id'] ?? null, |
||||
367 | ] |
||||
368 | ); |
||||
369 | // store BIC |
||||
370 | if (null !== $data['bic']) { |
||||
371 | /** @var AccountMetaFactory $metaFactory */ |
||||
372 | $metaFactory = app(AccountMetaFactory::class); |
||||
373 | $metaFactory->create(['account_id' => $account->id, 'name' => 'BIC', 'data' => $data['bic']]); |
||||
374 | } |
||||
375 | // store account number |
||||
376 | if (null !== $data['number']) { |
||||
377 | /** @var AccountMetaFactory $metaFactory */ |
||||
378 | $metaFactory = app(AccountMetaFactory::class); |
||||
379 | $metaFactory->create(['account_id' => $account->id, 'name' => 'account_number', 'data' => $data['bic']]); |
||||
380 | } |
||||
381 | |||||
382 | } |
||||
383 | |||||
384 | return $account; |
||||
385 | } |
||||
386 | } |
||||
387 |