Files
firefly-iii/app/Factory/PiggyBankFactory.php

310 lines
13 KiB
PHP
Raw Normal View History

2018-02-19 19:44:46 +01:00
<?php
2022-12-29 19:41:57 +01:00
2018-02-19 19:44:46 +01:00
/**
* PiggyBankFactory.php
2020-02-16 14:00:57 +01:00
* Copyright (c) 2019 james@firefly-iii.org
2018-02-19 19:44:46 +01:00
*
* This file is part of Firefly III (https://github.com/firefly-iii).
2018-02-19 19:44:46 +01:00
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
2018-02-19 19:44:46 +01:00
*
* This program is distributed in the hope that it will be useful,
2018-02-19 19:44:46 +01:00
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
2018-02-19 19:44:46 +01:00
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
2018-02-19 19:44:46 +01:00
*/
2018-05-11 10:08:34 +02:00
declare(strict_types=1);
2018-02-19 19:44:46 +01:00
namespace FireflyIII\Factory;
2021-03-28 11:46:23 +02:00
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\Account;
2025-04-20 21:01:23 +02:00
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
2024-12-01 18:16:48 +01:00
use FireflyIII\Exceptions\FireflyException;
2018-02-19 19:44:46 +01:00
use FireflyIII\Models\PiggyBank;
2024-12-01 18:16:48 +01:00
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
2024-12-07 08:05:29 +01:00
use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups;
2024-12-01 18:16:48 +01:00
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
2018-02-19 19:44:46 +01:00
use FireflyIII\User;
2024-12-01 18:16:48 +01:00
use Illuminate\Database\QueryException;
2024-12-15 08:50:57 +01:00
use Illuminate\Support\Facades\Log;
2018-02-19 19:44:46 +01:00
use function Safe\json_encode;
2018-02-19 19:44:46 +01:00
/**
* Class PiggyBankFactory
*/
class PiggyBankFactory
{
2024-12-07 08:05:29 +01:00
use CreatesObjectGroups;
2024-12-14 20:16:08 +01:00
2025-06-07 06:31:14 +02:00
public User $user;
private AccountRepositoryInterface $accountRepository;
private CurrencyRepositoryInterface $currencyRepository;
2024-12-01 18:16:48 +01:00
private PiggyBankRepositoryInterface $piggyBankRepository;
2024-12-14 17:32:03 +01:00
public function __construct()
{
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
2024-12-14 17:32:03 +01:00
$this->piggyBankRepository = app(PiggyBankRepositoryInterface::class);
}
2025-01-04 07:10:37 +01:00
public function setUser(User $user): void
{
$this->user = $user;
$this->currencyRepository->setUser($user);
$this->accountRepository->setUser($user);
$this->piggyBankRepository->setUser($user);
}
2024-12-01 18:16:48 +01:00
/**
* Store a piggy bank or come back with an exception.
*/
2024-12-14 20:16:08 +01:00
public function store(array $data): PiggyBank
{
2024-12-14 17:32:03 +01:00
$piggyBankData = $data;
2024-12-01 18:16:48 +01:00
// unset some fields
2024-12-14 20:16:08 +01:00
unset($piggyBankData['object_group_title'], $piggyBankData['transaction_currency_code'], $piggyBankData['transaction_currency_id'], $piggyBankData['accounts'], $piggyBankData['object_group_id'], $piggyBankData['notes']);
2024-12-01 18:16:48 +01:00
// validate amount:
2025-06-07 06:31:14 +02:00
if (array_key_exists('target_amount', $piggyBankData) && '' === (string)$piggyBankData['target_amount']) {
2024-12-01 18:16:48 +01:00
$piggyBankData['target_amount'] = '0';
}
$piggyBankData['start_date_tz'] = $piggyBankData['start_date']?->format('e');
$piggyBankData['target_date_tz'] = $piggyBankData['target_date']?->format('e');
$piggyBankData['account_id'] = null;
2024-12-01 18:16:48 +01:00
$piggyBankData['transaction_currency_id'] = $this->getCurrency($data)->id;
$piggyBankData['order'] = 131337;
2024-12-01 18:16:48 +01:00
try {
/** @var PiggyBank $piggyBank */
$piggyBank = PiggyBank::createQuietly($piggyBankData);
2024-12-01 18:16:48 +01:00
} catch (QueryException $e) {
app('log')->error(sprintf('Could not store piggy bank: %s', $e->getMessage()), $piggyBankData);
throw new FireflyException('400005: Could not store new piggy bank.', 0, $e);
}
$piggyBank = $this->setOrder($piggyBank, $data);
2024-12-01 18:16:48 +01:00
$this->linkToAccountIds($piggyBank, $data['accounts']);
$this->piggyBankRepository->updateNote($piggyBank, $data['notes']);
$objectGroupTitle = $data['object_group_title'] ?? '';
2024-12-01 18:16:48 +01:00
if ('' !== $objectGroupTitle) {
$objectGroup = $this->findOrCreateObjectGroup($objectGroupTitle);
if ($objectGroup instanceof ObjectGroup) {
2024-12-01 18:16:48 +01:00
$piggyBank->objectGroups()->sync([$objectGroup->id]);
}
}
// try also with ID
$objectGroupId = (int)($data['object_group_id'] ?? 0);
2024-12-01 18:16:48 +01:00
if (0 !== $objectGroupId) {
$objectGroup = $this->findObjectGroupById($objectGroupId);
if ($objectGroup instanceof ObjectGroup) {
2024-12-01 18:16:48 +01:00
$piggyBank->objectGroups()->sync([$objectGroup->id]);
}
}
Log::debug('Touch piggy bank');
$piggyBank->encrypted = false;
$piggyBank->save();
$piggyBank->touch();
2024-12-01 18:16:48 +01:00
return $piggyBank;
}
2024-12-22 08:43:12 +01:00
private function getCurrency(array $data): TransactionCurrency
{
// currency:
2025-08-01 13:48:32 +02:00
$primaryCurrency = app('amount')->getPrimaryCurrency();
$currency = null;
2024-12-22 08:43:12 +01:00
if (array_key_exists('transaction_currency_code', $data)) {
2025-06-07 06:31:14 +02:00
$currency = $this->currencyRepository->findByCode((string)($data['transaction_currency_code'] ?? ''));
2024-12-22 08:43:12 +01:00
}
if (array_key_exists('transaction_currency_id', $data)) {
2025-06-07 06:31:14 +02:00
$currency = $this->currencyRepository->find((int)($data['transaction_currency_id'] ?? 0));
2024-12-22 08:43:12 +01:00
}
2025-08-01 13:48:32 +02:00
$currency ??= $primaryCurrency;
2024-12-22 08:43:12 +01:00
return $currency;
}
2018-02-19 19:44:46 +01:00
public function find(?int $piggyBankId, ?string $piggyBankName): ?PiggyBank
{
$piggyBankId = (int)$piggyBankId;
2025-06-07 06:31:14 +02:00
$piggyBankName = (string)$piggyBankName;
if ('' === $piggyBankName && 0 === $piggyBankId) {
2018-02-19 19:44:46 +01:00
return null;
}
// first find by ID:
if ($piggyBankId > 0) {
$piggyBank = PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.id', $piggyBankId)
->first(['piggy_banks.*'])
;
2018-04-02 14:42:07 +02:00
if (null !== $piggyBank) {
2018-02-19 19:44:46 +01:00
return $piggyBank;
}
}
// then find by name:
2019-02-13 17:38:41 +01:00
if ('' !== $piggyBankName) {
2023-12-20 19:35:52 +01:00
/** @var null|PiggyBank $piggyBank */
2018-03-01 20:54:50 +01:00
$piggyBank = $this->findByName($piggyBankName);
2018-04-02 14:42:07 +02:00
if (null !== $piggyBank) {
2018-02-19 19:44:46 +01:00
return $piggyBank;
}
}
return null;
}
2018-03-01 20:54:50 +01:00
public function findByName(string $name): ?PiggyBank
{
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.name', $name)
->first(['piggy_banks.*'])
;
2018-03-01 20:54:50 +01:00
}
2024-12-14 20:16:08 +01:00
private function setOrder(PiggyBank $piggyBank, array $data): PiggyBank
{
2024-12-01 18:16:48 +01:00
$this->resetOrder();
$order = $this->getMaxOrder() + 1;
2024-12-01 18:16:48 +01:00
if (array_key_exists('order', $data)) {
$order = $data['order'];
}
$piggyBank->order = $order;
$piggyBank->saveQuietly();
2024-12-01 18:16:48 +01:00
return $piggyBank;
}
public function resetOrder(): void
2024-12-01 18:16:48 +01:00
{
// TODO duplicate code
$set = PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->with(
[
'objectGroups',
]
)
->orderBy('piggy_banks.order', 'ASC')->get(['piggy_banks.*'])
;
2024-12-01 18:16:48 +01:00
$current = 1;
foreach ($set as $piggyBank) {
if ($piggyBank->order !== $current) {
app('log')->debug(sprintf('Piggy bank #%d ("%s") was at place %d but should be on %d', $piggyBank->id, $piggyBank->name, $piggyBank->order, $current));
$piggyBank->order = $current;
$piggyBank->save();
}
++$current;
}
}
private function getMaxOrder(): int
{
2025-06-07 06:31:14 +02:00
return (int)$this->piggyBankRepository->getPiggyBanks()->max('order');
2024-12-01 18:16:48 +01:00
}
2024-12-14 20:16:08 +01:00
public function linkToAccountIds(PiggyBank $piggyBank, array $accounts): void
{
2024-12-15 08:50:57 +01:00
Log::debug(sprintf('Linking piggy bank #%d to %d accounts.', $piggyBank->id, count($accounts)), $accounts);
// collect current current_amount so the sync does not remove them.
// TODO this is a tedious check. Feels like a hack.
$toBeLinked = [];
2025-06-07 06:31:14 +02:00
$oldSavedAmount = $this->piggyBankRepository->getCurrentAmount($piggyBank);
2024-12-22 08:43:12 +01:00
foreach ($piggyBank->accounts as $account) {
2025-03-05 20:12:44 +01:00
Log::debug(sprintf('Checking account #%d', $account->id));
2024-12-22 08:43:12 +01:00
foreach ($accounts as $info) {
2025-03-05 20:12:44 +01:00
Log::debug(sprintf(' Checking other account #%d', $info['account_id']));
2025-06-07 06:31:14 +02:00
if ((int)$account->id === (int)$info['account_id']) {
2025-03-05 20:12:44 +01:00
$toBeLinked[$account->id] = ['current_amount' => $account->pivot->current_amount ?? '0'];
Log::debug(sprintf('Prefilled for account #%d with amount %s', $account->id, $account->pivot->current_amount ?? '0'));
2024-12-15 08:50:57 +01:00
}
}
}
2024-12-01 18:16:48 +01:00
/** @var array $info */
2024-12-14 20:16:08 +01:00
foreach ($accounts as $info) {
2025-06-07 06:31:14 +02:00
$account = $this->accountRepository->find((int)($info['account_id'] ?? 0));
if (!$account instanceof Account) {
2025-06-07 06:31:14 +02:00
Log::debug(sprintf('Account #%d not found, skipping.', (int)($info['account_id'] ?? 0)));
2024-12-01 18:16:48 +01:00
continue;
}
2025-03-05 20:12:44 +01:00
if (array_key_exists('current_amount', $info) && null !== $info['current_amount']) {
2025-04-20 21:01:23 +02:00
// an amount is set, first check out if there is a difference with the previous amount.
$previous = $toBeLinked[$account->id]['current_amount'] ?? '0';
$diff = bcsub($info['current_amount'], $previous);
2025-04-20 21:01:23 +02:00
// create event for difference.
if (0 !== bccomp($diff, '0')) {
Log::debug(sprintf('[a] Will save event for difference %s (previous value was %s)', $diff, $previous));
event(new ChangedAmount($piggyBank, $diff, null, null));
}
2024-12-14 17:32:03 +01:00
$toBeLinked[$account->id] = ['current_amount' => $info['current_amount']];
2025-03-05 20:12:44 +01:00
Log::debug(sprintf('[a] Will link account #%d with amount %s', $account->id, $info['current_amount']));
}
if (array_key_exists('current_amount', $info) && null === $info['current_amount']) {
2025-04-20 21:01:23 +02:00
// an amount is set, first check out if there is a difference with the previous amount.
$previous = $toBeLinked[$account->id]['current_amount'] ?? '0';
$diff = bcsub('0', $previous);
2025-04-20 21:01:23 +02:00
// create event for difference.
if (0 !== bccomp($diff, '0')) {
Log::debug(sprintf('[b] Will save event for difference %s (previous value was %s)', $diff, $previous));
event(new ChangedAmount($piggyBank, $diff, null, null));
}
// no amount set, use previous amount or go to ZERO.
2025-03-05 20:12:44 +01:00
$toBeLinked[$account->id] = ['current_amount' => $toBeLinked[$account->id]['current_amount'] ?? '0'];
Log::debug(sprintf('[b] Will link account #%d with amount %s', $account->id, $toBeLinked[$account->id]['current_amount'] ?? '0'));
2025-04-20 21:01:23 +02:00
// create event:
Log::debug('linkToAccountIds: Trigger change for positive amount [b].');
2025-09-07 06:25:26 +02:00
event(new ChangedAmount($piggyBank, $toBeLinked[$account->id]['current_amount'] ?? '0', null, null));
2024-12-14 17:32:03 +01:00
}
2024-12-14 20:16:08 +01:00
if (!array_key_exists('current_amount', $info)) {
2024-12-15 08:50:57 +01:00
$toBeLinked[$account->id] ??= [];
Log::debug(sprintf('Will link account #%d with info: ', $account->id), $toBeLinked[$account->id]);
2024-12-14 17:32:03 +01:00
}
2024-12-01 18:16:48 +01:00
}
Log::debug(sprintf('Link information: %s', json_encode($toBeLinked)));
2025-04-20 21:01:23 +02:00
if (0 !== count($toBeLinked)) {
2025-06-07 06:31:14 +02:00
Log::debug('Syncing accounts to piggy bank.');
2025-04-20 21:01:23 +02:00
$piggyBank->accounts()->sync($toBeLinked);
2025-06-07 06:31:14 +02:00
$piggyBank->refresh();
$newSavedAmount = $this->piggyBankRepository->getCurrentAmount($piggyBank);
Log::debug(sprintf('Old saved amount: %s, new saved amount is %s', $oldSavedAmount, $newSavedAmount));
if (0 !== bccomp($oldSavedAmount, $newSavedAmount)) {
Log::debug('Amount changed, will create event for it.');
// create event for difference.
event(new ChangedAmount($piggyBank, bcsub($newSavedAmount, $oldSavedAmount), null, null));
}
2025-04-20 21:01:23 +02:00
}
if (0 === count($toBeLinked)) {
Log::warning('No accounts to link to piggy bank, will not change whatever is there now.');
}
2024-12-01 18:16:48 +01:00
}
2018-03-05 19:35:58 +01:00
}