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 | * @return Account |
||
91 | * @codeCoverageIgnore |
||
92 | */ |
||
93 | protected function getAccount(string $transactionType, string $direction, array $data): Account |
||
94 | { |
||
95 | // some debug logging: |
||
96 | Log::debug(sprintf('Now in getAccount(%s)', $direction), $data); |
||
97 | |||
98 | // final result: |
||
99 | $result = null; |
||
100 | |||
101 | // expected type of source account, in order of preference |
||
102 | /** @var array $array */ |
||
103 | $array = config('firefly.expected_source_types'); |
||
104 | $expectedTypes = $array[$direction]; |
||
105 | unset($array); |
||
106 | |||
107 | // and now try to find it, based on the type of transaction. |
||
108 | $message = 'Based on the fact that the transaction is a %s, the %s account should be in: %s'; |
||
109 | Log::debug(sprintf($message, $transactionType, $direction, implode(', ', $expectedTypes[$transactionType]))); |
||
110 | |||
111 | // first attempt, find by ID. |
||
112 | if (null !== $data['id']) { |
||
113 | $search = $this->accountRepository->findNull($data['id']); |
||
114 | if (null !== $search && in_array($search->accountType->type, $expectedTypes[$transactionType], true)) { |
||
115 | Log::debug( |
||
116 | sprintf('Found "account_id" object for %s: #%d, "%s" of type %s', $direction, $search->id, $search->name, $search->accountType->type) |
||
117 | ); |
||
118 | $result = $search; |
||
119 | } |
||
120 | } |
||
121 | |||
122 | // second attempt, find by name. |
||
123 | if (null === $result && null !== $data['name']) { |
||
124 | Log::debug('Found nothing by account ID.'); |
||
125 | // find by preferred type. |
||
126 | $source = $this->accountRepository->findByName($data['name'], [$expectedTypes[$transactionType][0]]); |
||
127 | // or any expected type. |
||
128 | $source = $source ?? $this->accountRepository->findByName($data['name'], $expectedTypes[$transactionType]); |
||
129 | |||
130 | if (null !== $source) { |
||
131 | Log::debug(sprintf('Found "account_name" object for %s: #%d, %s', $direction, $source->id, $source->name)); |
||
132 | |||
133 | $result = $source; |
||
134 | } |
||
135 | } |
||
136 | |||
137 | // third attempt, find by IBAN |
||
138 | if (null === $result && null !== $data['iban']) { |
||
139 | Log::debug('Found nothing by account name.'); |
||
140 | // find by preferred type. |
||
141 | $source = $this->accountRepository->findByIbanNull($data['iban'], [$expectedTypes[$transactionType][0]]); |
||
142 | // or any expected type. |
||
143 | $source = $source ?? $this->accountRepository->findByIbanNull($data['iban'], $expectedTypes[$transactionType]); |
||
144 | |||
145 | if (null !== $source) { |
||
146 | Log::debug(sprintf('Found "account_iban" object for %s: #%d, %s', $direction, $source->id, $source->name)); |
||
147 | |||
148 | $result = $source; |
||
149 | } |
||
150 | } |
||
151 | |||
152 | // return cash account. |
||
153 | if (null === $result && null === $data['name'] |
||
154 | && in_array(AccountType::CASH, $expectedTypes[$transactionType], true)) { |
||
155 | $result = $this->accountRepository->getCashAccount(); |
||
156 | } |
||
157 | |||
158 | // return new account. |
||
159 | if (null === $result) { |
||
160 | $data['name'] = $data['name'] ?? '(no name)'; |
||
161 | // final attempt, create it. |
||
162 | $preferredType = $expectedTypes[$transactionType][0]; |
||
163 | if (AccountType::ASSET === $preferredType) { |
||
164 | throw new FireflyException('TransactionFactory: Cannot create asset account with these values', $data); |
||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
165 | } |
||
166 | |||
167 | $result = $this->accountRepository->store( |
||
168 | [ |
||
169 | 'account_type_id' => null, |
||
170 | 'account_type' => $preferredType, |
||
171 | 'name' => $data['name'], |
||
172 | 'active' => true, |
||
173 | 'iban' => $data['iban'], |
||
174 | ] |
||
175 | ); |
||
176 | // store BIC |
||
177 | if (null !== $data['bic']) { |
||
178 | /** @var AccountMetaFactory $metaFactory */ |
||
179 | $metaFactory = app(AccountMetaFactory::class); |
||
180 | $metaFactory->create(['account_id' => $result->id, 'name' => 'BIC', 'data' => $data['bic']]); |
||
181 | } |
||
182 | // store account number |
||
183 | if (null !== $data['number']) { |
||
184 | /** @var AccountMetaFactory $metaFactory */ |
||
185 | $metaFactory = app(AccountMetaFactory::class); |
||
186 | $metaFactory->create(['account_id' => $result->id, 'name' => 'account_number', 'data' => $data['bic']]); |
||
187 | } |
||
188 | } |
||
189 | |||
190 | return $result; |
||
191 | } |
||
192 | |||
193 | /** |
||
194 | * @param string $amount |
||
195 | * |
||
196 | * @return string |
||
197 | * @throws FireflyException |
||
198 | * @codeCoverageIgnore |
||
199 | */ |
||
200 | protected function getAmount(string $amount): string |
||
201 | { |
||
202 | if ('' === $amount) { |
||
203 | throw new FireflyException(sprintf('The amount cannot be an empty string: "%s"', $amount)); |
||
204 | } |
||
205 | if (0 === bccomp('0', $amount)) { |
||
206 | throw new FireflyException(sprintf('The amount seems to be zero: "%s"', $amount)); |
||
207 | } |
||
208 | |||
209 | return $amount; |
||
210 | } |
||
211 | |||
212 | /** |
||
213 | * @param TransactionJournal $journal |
||
214 | * @param NullArrayObject $data |
||
215 | * |
||
216 | * @codeCoverageIgnore |
||
217 | */ |
||
218 | protected function storeBudget(TransactionJournal $journal, NullArrayObject $data): void |
||
219 | { |
||
220 | if (TransactionType::WITHDRAWAL !== $journal->transactionType->type) { |
||
221 | $journal->budgets()->sync([]); |
||
222 | |||
223 | return; |
||
224 | } |
||
225 | $budget = $this->budgetRepository->findBudget($data['budget_id'], $data['budget_name']); |
||
226 | if (null !== $budget) { |
||
227 | Log::debug(sprintf('Link budget #%d to journal #%d', $budget->id, $journal->id)); |
||
228 | $journal->budgets()->sync([$budget->id]); |
||
229 | |||
230 | return; |
||
231 | } |
||
232 | // if the budget is NULL, sync empty. |
||
233 | $journal->budgets()->sync([]); |
||
234 | } |
||
235 | |||
236 | /** |
||
237 | * @param TransactionJournal $journal |
||
238 | * @param NullArrayObject $data |
||
239 | * |
||
240 | * @codeCoverageIgnore |
||
241 | */ |
||
242 | protected function storeCategory(TransactionJournal $journal, NullArrayObject $data): void |
||
243 | { |
||
244 | $category = $this->categoryRepository->findCategory($data['category_id'], $data['category_name']); |
||
245 | if (null !== $category) { |
||
246 | Log::debug(sprintf('Link category #%d to journal #%d', $category->id, $journal->id)); |
||
247 | $journal->categories()->sync([$category->id]); |
||
248 | |||
249 | return; |
||
250 | } |
||
251 | // if the category is NULL, sync empty. |
||
252 | $journal->categories()->sync([]); |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * @param TransactionJournal $journal |
||
257 | * @param string $notes |
||
258 | * |
||
259 | * @codeCoverageIgnore |
||
260 | */ |
||
261 | protected function storeNotes(TransactionJournal $journal, ?string $notes): void |
||
262 | { |
||
263 | $notes = (string)$notes; |
||
264 | $note = $journal->notes()->first(); |
||
265 | if ('' !== $notes) { |
||
266 | if (null === $note) { |
||
267 | $note = new Note; |
||
268 | $note->noteable()->associate($journal); |
||
269 | } |
||
270 | $note->text = $notes; |
||
271 | $note->save(); |
||
272 | Log::debug(sprintf('Stored notes for journal #%d', $journal->id)); |
||
273 | |||
274 | return; |
||
275 | } |
||
276 | if ('' === $notes && null !== $note) { |
||
277 | // try to delete existing notes. |
||
278 | try { |
||
279 | $note->delete(); |
||
280 | // @codeCoverageIgnoreStart |
||
281 | } catch (Exception $e) { |
||
282 | Log::debug(sprintf('Could not delete journal notes: %s', $e->getMessage())); |
||
283 | } |
||
284 | // @codeCoverageIgnoreEnd |
||
285 | } |
||
286 | } |
||
287 | |||
288 | /** |
||
289 | * Link tags to journal. |
||
290 | * |
||
291 | * @param TransactionJournal $journal |
||
292 | * @param array $tags |
||
293 | * |
||
294 | * @codeCoverageIgnore |
||
295 | */ |
||
296 | protected function storeTags(TransactionJournal $journal, ?array $tags): void |
||
297 | { |
||
298 | |||
299 | $this->tagFactory->setUser($journal->user); |
||
300 | $set = []; |
||
301 | if (!is_array($tags)) { |
||
302 | return; |
||
303 | } |
||
304 | foreach ($tags as $string) { |
||
305 | $string = (string)$string; |
||
306 | if ('' !== $string) { |
||
307 | $tag = $this->tagFactory->findOrCreate($string); |
||
308 | if (null !== $tag) { |
||
309 | $set[] = $tag->id; |
||
310 | } |
||
311 | } |
||
312 | } |
||
313 | $journal->tags()->sync($set); |
||
314 | } |
||
315 | |||
316 | |||
317 | } |
||
318 |