Multi account piggy banks.

This commit is contained in:
James Cole
2024-12-14 17:32:03 +01:00
parent fb6c67fa04
commit 6a62f781e9
24 changed files with 572 additions and 404 deletions

View File

@@ -72,6 +72,7 @@ class UpgradeDatabase extends Command
'firefly-iii:create-group-memberships', 'firefly-iii:create-group-memberships',
'firefly-iii:upgrade-group-information', 'firefly-iii:upgrade-group-information',
'firefly-iii:upgrade-currency-preferences', 'firefly-iii:upgrade-currency-preferences',
'firefly-iii:upgrade-multi-piggies',
'firefly-iii:correct-database', 'firefly-iii:correct-database',
]; ];
$args = []; $args = [];

View File

@@ -93,7 +93,7 @@ class UpgradeMultiPiggyBanks extends Command
{ {
$this->repository->setUser($piggyBank->account->user); $this->repository->setUser($piggyBank->account->user);
$this->accountRepository->setUser($piggyBank->account->user); $this->accountRepository->setUser($piggyBank->account->user);
$repetition = $this->repository->getRepetition($piggyBank); $repetition = $this->repository->getRepetition($piggyBank, true);
$currency = $this->accountRepository->getAccountCurrency($piggyBank->account) ?? app('amount')->getDefaultCurrencyByUserGroup($piggyBank->account->user->userGroup); $currency = $this->accountRepository->getAccountCurrency($piggyBank->account) ?? app('amount')->getDefaultCurrencyByUserGroup($piggyBank->account->user->userGroup);
// update piggy bank to have a currency. // update piggy bank to have a currency.

View File

@@ -42,12 +42,22 @@ class PiggyBankFactory
public User $user { public User $user {
set(User $value) { set(User $value) {
$this->user = $value; $this->user = $value;
$this->currencyRepository->setUser($value);
$this->accountRepository->setUser($value);
$this->piggyBankRepository->setUser($value);
} }
} }
private CurrencyRepositoryInterface $currencyRepository; private CurrencyRepositoryInterface $currencyRepository;
private AccountRepositoryInterface $accountRepository; private AccountRepositoryInterface $accountRepository;
private PiggyBankRepositoryInterface $piggyBankRepository; private PiggyBankRepositoryInterface $piggyBankRepository;
public function __construct()
{
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->piggyBankRepository = app(PiggyBankRepositoryInterface::class);
}
/** /**
* Store a piggy bank or come back with an exception. * Store a piggy bank or come back with an exception.
* *
@@ -56,12 +66,7 @@ class PiggyBankFactory
* @return PiggyBank * @return PiggyBank
*/ */
public function store(array $data): PiggyBank { public function store(array $data): PiggyBank {
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->piggyBankRepository = app(PiggyBankRepositoryInterface::class);
$this->currencyRepository->setUser($this->user);
$this->accountRepository->setUser($this->user);
$this->piggyBankRepository->setUser($this->user);
$piggyBankData =$data; $piggyBankData =$data;
// unset some fields // unset some fields
@@ -202,14 +207,23 @@ class PiggyBankFactory
} }
private function linkToAccountIds(PiggyBank $piggyBank, array $accounts): void { public function linkToAccountIds(PiggyBank $piggyBank, array $accounts): void {
$toBeLinked = [];
/** @var array $info */ /** @var array $info */
foreach($accounts as $info) { foreach($accounts as $info) {
$account = $this->accountRepository->find((int)($info['account_id'] ?? 0)); $account = $this->accountRepository->find((int)($info['account_id'] ?? 0));
if(null === $account) { if(null === $account) {
continue; continue;
} }
$piggyBank->accounts()->syncWithoutDetaching([$account->id => ['current_amount' => $info['current_amount'] ?? '0']]); if(array_key_exists('current_amount',$info)) {
$toBeLinked[$account->id] = ['current_amount' => $info['current_amount']];
//$piggyBank->accounts()->syncWithoutDetaching([$account->id => ['current_amount' => $info['current_amount'] ?? '0']]);
}
if(!array_key_exists('current_amount', $info)) {
$toBeLinked[$account->id] = [];
//$piggyBank->accounts()->syncWithoutDetaching([$account->id]);
} }
} }
$piggyBank->accounts()->sync($toBeLinked);
}
} }

View File

@@ -26,12 +26,14 @@ namespace FireflyIII\Http\Controllers\PiggyBank;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View; use Illuminate\View\View;
/** /**
@@ -69,16 +71,26 @@ class AmountController extends Controller
*/ */
public function add(PiggyBank $piggyBank) public function add(PiggyBank $piggyBank)
{ {
$leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, today(config('app.timezone'))); $accounts = [];
$savedSoFar = $this->piggyRepos->getCurrentAmount($piggyBank); $total = '0';
$maxAmount = $leftOnAccount; $totalSaved = $this->piggyRepos->getCurrentAmount($piggyBank);
if (0 !== bccomp($piggyBank->target_amount, '0')) { $leftToSave = bcsub($piggyBank->target_amount, $totalSaved);
$leftToSave = bcsub($piggyBank->target_amount, $savedSoFar); foreach ($piggyBank->accounts as $account) {
$maxAmount = min($leftOnAccount, $leftToSave); $leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, $account, today(config('app.timezone'))->endOfDay());
$savedSoFar = $this->piggyRepos->getCurrentAmount($piggyBank, $account);
$maxAmount = 0 === bccomp($piggyBank->target_amount, '0') ? $leftToSave : min($leftOnAccount, $leftToSave);
$accounts[] = [
'account' => $account,
'left_on_account' => $leftOnAccount,
'saved_so_far' => $savedSoFar,
'left_to_save' => $leftToSave,
'max_amount' => $maxAmount,
];
$total = bcadd($total, $leftOnAccount);
} }
$currency = $this->accountRepos->getAccountCurrency($piggyBank->account) ?? app('amount')->getDefaultCurrency(); $total = (float) $total; // intentional float.
return view('piggy-banks.add', compact('piggyBank', 'maxAmount', 'currency')); return view('piggy-banks.add', compact('piggyBank', 'accounts', 'total'));
} }
/** /**
@@ -90,17 +102,23 @@ class AmountController extends Controller
{ {
/** @var Carbon $date */ /** @var Carbon $date */
$date = session('end', today(config('app.timezone'))); $date = session('end', today(config('app.timezone')));
$leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, $date); $accounts = [];
$savedSoFar = $this->piggyRepos->getCurrentAmount($piggyBank); $total = '0';
$maxAmount = $leftOnAccount; foreach ($piggyBank->accounts as $account) {
$leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, $account, $date);
if (0 !== bccomp($piggyBank->target_amount, '0')) { $savedSoFar = $this->piggyRepos->getCurrentAmount($piggyBank, $account);
$leftToSave = bcsub($piggyBank->target_amount, $savedSoFar); $leftToSave = bcsub($piggyBank->target_amount, $savedSoFar);
$maxAmount = min($leftOnAccount, $leftToSave); $accounts[] = [
'account' => $account,
'left_on_account' => $leftOnAccount,
'saved_so_far' => $savedSoFar,
'left_to_save' => $leftToSave,
'max_amount' => 0 === bccomp($piggyBank->target_amount, '0') ? $leftOnAccount : min($leftOnAccount, $leftToSave),
];
$total = bcadd($total, $leftOnAccount);
} }
$currency = $this->accountRepos->getAccountCurrency($piggyBank->account) ?? app('amount')->getDefaultCurrency();
return view('piggy-banks.add-mobile', compact('piggyBank', 'maxAmount', 'currency')); return view('piggy-banks.add-mobile', compact('piggyBank', 'total', 'accounts'));
} }
/** /**
@@ -108,32 +126,47 @@ class AmountController extends Controller
*/ */
public function postAdd(Request $request, PiggyBank $piggyBank): RedirectResponse public function postAdd(Request $request, PiggyBank $piggyBank): RedirectResponse
{ {
$amount = $request->get('amount') ?? '0'; $data = $request->all();
$currency = $this->accountRepos->getAccountCurrency($piggyBank->account) ?? app('amount')->getDefaultCurrency(); $amounts = $data['amount'] ?? [];
// if amount is negative, make positive and continue: $total = '0';
Log::debug('Start with loop.');
/** @var Account $account */
foreach ($piggyBank->accounts as $account) {
$amount = (string) ($amounts[$account->id] ?? '0');
if ('' === $amount || 0 === bccomp($amount, '0')) {
continue;
}
if (-1 === bccomp($amount, '0')) { if (-1 === bccomp($amount, '0')) {
$amount = bcmul($amount, '-1'); $amount = bcmul($amount, '-1');
} }
if ($this->piggyRepos->canAddAmount($piggyBank, $amount)) {
$this->piggyRepos->addAmount($piggyBank, $amount); // small check to see if the $amount is not more than the total "left to save" value
session()->flash( $currentAmount = $this->piggyRepos->getCurrentAmount($piggyBank);
'success', $leftToSave = 0 === bccomp($piggyBank->target_amount, '0') ? '0' : bcsub($piggyBank->target_amount, $currentAmount);
(string)trans( if (bccomp($amount, $leftToSave) > 0 && 0 !== bccomp($leftToSave, '0')) {
'firefly.added_amount_to_piggy', Log::debug(sprintf('Amount "%s" is more than left to save "%s". Using left to save.', $amount, $leftToSave));
['amount' => app('amount')->formatAnything($currency, $amount, false), 'name' => $piggyBank->name] $amount = $leftToSave;
) }
);
$canAddAmount = $this->piggyRepos->canAddAmount($piggyBank, $account, $amount);
if ($canAddAmount) {
$this->piggyRepos->addAmount($piggyBank, $account, $amount);
$total = bcadd($total, $amount);
}
$piggyBank->refresh();
}
if (0 !== bccomp($total, '0')) {
session()->flash('success', (string) trans('firefly.added_amount_to_piggy', ['amount' => app('amount')->formatAnything($piggyBank->transactionCurrency, $total, false), 'name' => $piggyBank->name]));
app('preferences')->mark(); app('preferences')->mark();
return redirect(route('piggy-banks.index')); return redirect(route('piggy-banks.index'));
} }
app('log')->error(sprintf('Cannot add %s because canAddAmount returned false.', $total));
app('log')->error('Cannot add '.$amount.' because canAddAmount returned false.');
session()->flash( session()->flash(
'error', 'error',
(string) trans( (string) trans(
'firefly.cannot_add_amount_piggy', 'firefly.cannot_add_amount_piggy',
['amount' => app('amount')->formatAnything($currency, $amount, false), 'name' => e($piggyBank->name)] ['amount' => app('amount')->formatAnything($piggyBank->transactionCurrency, $total, false), 'name' => e($piggyBank->name)]
) )
); );
@@ -145,32 +178,43 @@ class AmountController extends Controller
*/ */
public function postRemove(Request $request, PiggyBank $piggyBank): RedirectResponse public function postRemove(Request $request, PiggyBank $piggyBank): RedirectResponse
{ {
$amount = $request->get('amount') ?? '0'; $amounts = $request->get('amount') ?? [];
$currency = $this->accountRepos->getAccountCurrency($piggyBank->account) ?? app('amount')->getDefaultCurrency(); if (!is_array($amounts)) {
// if amount is negative, make positive and continue: $amounts = [];
}
$total = '0';
/** @var Account $account */
foreach ($piggyBank->accounts as $account) {
$amount = (string) ($amounts[$account->id] ?? '0');
if ('' === $amount || 0 === bccomp($amount, '0')) {
continue;
}
if (-1 === bccomp($amount, '0')) { if (-1 === bccomp($amount, '0')) {
$amount = bcmul($amount, '-1'); $amount = bcmul($amount, '-1');
} }
if ($this->piggyRepos->canRemoveAmount($piggyBank, $amount)) { if ($this->piggyRepos->canRemoveAmount($piggyBank, $account, $amount)) {
$this->piggyRepos->removeAmount($piggyBank, $amount); $this->piggyRepos->removeAmount($piggyBank, $account, $amount);
$total = bcadd($total, $amount);
}
}
if (0 !== bccomp($total, '0')) {
session()->flash( session()->flash(
'success', 'success',
(string) trans( (string) trans(
'firefly.removed_amount_from_piggy', 'firefly.removed_amount_from_piggy',
['amount' => app('amount')->formatAnything($currency, $amount, false), 'name' => $piggyBank->name] ['amount' => app('amount')->formatAnything($piggyBank->transactionCurrency, $total, false), 'name' => $piggyBank->name]
) )
); );
app('preferences')->mark(); app('preferences')->mark();
return redirect(route('piggy-banks.index')); return redirect(route('piggy-banks.index'));
} }
$amount = (string)$request->get('amount');
session()->flash( session()->flash(
'error', 'error',
(string) trans( (string) trans(
'firefly.cannot_remove_from_piggy', 'firefly.cannot_remove_from_piggy',
['amount' => app('amount')->formatAnything($currency, $amount, false), 'name' => e($piggyBank->name)] ['amount' => app('amount')->formatAnything($piggyBank->transactionCurrency, $total, false), 'name' => e($piggyBank->name)]
) )
); );
@@ -184,10 +228,14 @@ class AmountController extends Controller
*/ */
public function remove(PiggyBank $piggyBank) public function remove(PiggyBank $piggyBank)
{ {
$repetition = $this->piggyRepos->getRepetition($piggyBank); $accounts = [];
$currency = $this->accountRepos->getAccountCurrency($piggyBank->account) ?? app('amount')->getDefaultCurrency(); foreach ($piggyBank->accounts as $account) {
$accounts[] = [
return view('piggy-banks.remove', compact('piggyBank', 'repetition', 'currency')); 'account' => $account,
'saved_so_far' => $this->piggyRepos->getCurrentAmount($piggyBank, $account),
];
}
return view('piggy-banks.remove', compact('piggyBank', 'accounts'));
} }
/** /**
@@ -197,9 +245,14 @@ class AmountController extends Controller
*/ */
public function removeMobile(PiggyBank $piggyBank) public function removeMobile(PiggyBank $piggyBank)
{ {
$repetition = $this->piggyRepos->getRepetition($piggyBank); $accounts = [];
$currency = $this->accountRepos->getAccountCurrency($piggyBank->account) ?? app('amount')->getDefaultCurrency(); foreach ($piggyBank->accounts as $account) {
$accounts[] = [
'account' => $account,
'saved_so_far' => $this->piggyRepos->getCurrentAmount($piggyBank, $account),
];
}
return view('piggy-banks.remove-mobile', compact('piggyBank', 'repetition', 'currency')); return view('piggy-banks.remove-mobile', compact('piggyBank', 'accounts'));
} }
} }

View File

@@ -79,22 +79,21 @@ class EditController extends Controller
// Flash some data to fill the form. // Flash some data to fill the form.
$targetDate = $piggyBank->target_date?->format('Y-m-d'); $targetDate = $piggyBank->target_date?->format('Y-m-d');
$startDate = $piggyBank->start_date?->format('Y-m-d'); $startDate = $piggyBank->start_date?->format('Y-m-d');
$currency = $this->accountRepository->getAccountCurrency($piggyBank->account);
if (null === $currency) {
$currency = app('amount')->getDefaultCurrency();
}
$preFilled = [ $preFilled = [
'name' => $piggyBank->name, 'name' => $piggyBank->name,
'account_id' => $piggyBank->account_id, 'target_amount' => app('steam')->bcround($piggyBank->target_amount, $piggyBank->transactionCurrency->decimal_places),
'targetamount' => app('steam')->bcround($piggyBank->target_amount, $currency->decimal_places), 'target_date' => $targetDate,
'targetdate' => $targetDate, 'start_date' => $startDate,
'startdate' => $startDate, 'accounts' => [],
'object_group' => null !== $piggyBank->objectGroups->first() ? $piggyBank->objectGroups->first()->title : '', 'object_group' => null !== $piggyBank->objectGroups->first() ? $piggyBank->objectGroups->first()->title : '',
'notes' => null === $note ? '' : $note->text, 'notes' => null === $note ? '' : $note->text,
]; ];
foreach($piggyBank->accounts as $account) {
$preFilled['accounts'][] = $account->id;
}
if (0 === bccomp($piggyBank->target_amount, '0')) { if (0 === bccomp($piggyBank->target_amount, '0')) {
$preFilled['targetamount'] = ''; $preFilled['target_amount'] = '';
} }
session()->flash('preFilled', $preFilled); session()->flash('preFilled', $preFilled);

View File

@@ -83,6 +83,7 @@ class ShowController extends Controller
$subTitle = $piggyBank->name; $subTitle = $piggyBank->name;
$attachments = $this->piggyRepos->getAttachments($piggyBank); $attachments = $this->piggyRepos->getAttachments($piggyBank);
return view('piggy-banks.show', compact('piggyBank', 'events', 'subTitle', 'piggy', 'attachments')); return view('piggy-banks.show', compact('piggyBank', 'events', 'subTitle', 'piggy', 'attachments'));
} }
} }

View File

@@ -24,6 +24,8 @@ declare(strict_types=1);
namespace FireflyIII\Http\Requests; namespace FireflyIII\Http\Requests;
use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Rules\IsValidPositiveAmount; use FireflyIII\Rules\IsValidPositiveAmount;
use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Support\Request\ConvertsDataTypes;
@@ -44,15 +46,22 @@ class PiggyBankUpdateRequest extends FormRequest
*/ */
public function getPiggyBankData(): array public function getPiggyBankData(): array
{ {
return [ $accounts = $this->get('accounts');
$data = [
'name' => $this->convertString('name'), 'name' => $this->convertString('name'),
'startdate' => $this->getCarbonDate('startdate'), 'start_date' => $this->getCarbonDate('start_date'),
'account_id' => $this->convertInteger('account_id'), 'target_amount' => trim($this->convertString('target_amount')),
'targetamount' => trim($this->convertString('targetamount')), 'target_date' => $this->getCarbonDate('target_date'),
'targetdate' => $this->getCarbonDate('targetdate'),
'notes' => $this->stringWithNewlines('notes'), 'notes' => $this->stringWithNewlines('notes'),
'object_group_title' => $this->convertString('object_group'), 'object_group_title' => $this->convertString('object_group'),
]; ];
if (!is_array($accounts)) {
$accounts = [];
}
foreach ($accounts as $item) {
$data['accounts'][] = ['account_id' => (int) $item];
}
return $data;
} }
/** /**
@@ -65,10 +74,11 @@ class PiggyBankUpdateRequest extends FormRequest
return [ return [
'name' => sprintf('required|min:1|max:255|uniquePiggyBankForUser:%d', $piggy->id), 'name' => sprintf('required|min:1|max:255|uniquePiggyBankForUser:%d', $piggy->id),
'account_id' => 'required|belongsToUser:accounts', 'accounts' => 'required|array',
'targetamount' => ['nullable', new IsValidPositiveAmount()], 'accounts.*' => 'required|belongsToUser:accounts',
'startdate' => 'date', 'target_amount' => ['nullable', new IsValidPositiveAmount()],
'targetdate' => 'date|nullable', 'start_date' => 'date',
'target_date' => 'date|nullable',
'order' => 'integer|max:32768|min:1', 'order' => 'integer|max:32768|min:1',
'object_group' => 'min:0|max:255', 'object_group' => 'min:0|max:255',
'notes' => 'min:1|max:32768|nullable', 'notes' => 'min:1|max:32768|nullable',
@@ -76,9 +86,49 @@ class PiggyBankUpdateRequest extends FormRequest
} }
public function withValidator(Validator $validator): void public function withValidator(Validator $validator): void
{ { // need to have more than one account.
// accounts need to have the same currency or be multi-currency(?).
$validator->after(
function (Validator $validator): void {
// validate start before end only if both are there.
$data = $validator->getData();
$currency = $this->getCurrencyFromData($data);
if (array_key_exists('accounts', $data) && is_array($data['accounts'])) {
$repository = app(AccountRepositoryInterface::class);
$types = config('firefly.piggy_bank_account_types');
foreach ($data['accounts'] as $value) {
$accountId = (int) $value;
$account = $repository->find($accountId);
if (null !== $account) {
// check currency here.
$accountCurrency = $repository->getAccountCurrency($account);
$isMultiCurrency = $repository->getMetaValue($account, 'is_multi_currency');
if ($accountCurrency->id !== $currency->id && 'true' !== $isMultiCurrency) {
$validator->errors()->add('accounts', trans('validation.invalid_account_currency'));
}
$type = $account->accountType->type;
if (!in_array($type, $types, true)) {
$validator->errors()->add('accounts', trans('validation.invalid_account_type'));
}
}
}
}
}
);
if ($validator->fails()) { if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
} }
} }
private function getCurrencyFromData(array $data): TransactionCurrency
{
$currencyId = (int) ($data['transaction_currency_id'] ?? 0);
$currency = TransactionCurrency::find($currencyId);
if (null === $currency) {
return app('amount')->getDefaultCurrency();
}
return $currency;
}
} }

View File

@@ -32,6 +32,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Database\Eloquent\Relations\MorphToMany;
@@ -159,9 +160,9 @@ class Account extends Model
return $this->morphToMany(ObjectGroup::class, 'object_groupable'); return $this->morphToMany(ObjectGroup::class, 'object_groupable');
} }
public function piggyBanks(): HasMany public function piggyBanks(): BelongsToMany
{ {
return $this->hasMany(PiggyBank::class); return $this->belongsToMany(PiggyBank::class);
} }
public function scopeAccountTypeIn(EloquentBuilder $query, array $types): void public function scopeAccountTypeIn(EloquentBuilder $query, array $types): void

View File

@@ -27,12 +27,14 @@ namespace FireflyIII\Repositories\PiggyBank;
use FireflyIII\Events\Model\PiggyBank\ChangedAmount; use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\PiggyBankFactory; use FireflyIII\Factory\PiggyBankFactory;
use FireflyIII\Models\Account;
use FireflyIII\Models\Note; use FireflyIII\Models\Note;
use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankRepetition; use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups; use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups;
use FireflyIII\Support\Facades\Amount; use FireflyIII\Support\Facades\Amount;
use Illuminate\Support\Facades\Log;
/** /**
* Trait ModifiesPiggyBanks * Trait ModifiesPiggyBanks
@@ -55,30 +57,42 @@ trait ModifiesPiggyBanks
} }
} }
public function removeAmount(PiggyBank $piggyBank, string $amount, ?TransactionJournal $journal = null): bool public function removeAmount(PiggyBank $piggyBank,Account $account, string $amount, ?TransactionJournal $journal = null): bool
{ {
$repetition = $this->getRepetition($piggyBank); $currentAmount = $this->getCurrentAmount($piggyBank, $account);
if (null === $repetition) { $pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
return false; $pivot->current_amount = bcsub($currentAmount, $amount);
} $pivot->save();
$repetition->current_amount = bcsub($repetition->current_amount, $amount);
$repetition->save();
app('log')->debug('addAmount [a]: Trigger change for negative amount.'); app('log')->debug('removeAmount [a]: Trigger change for negative amount.');
event(new ChangedAmount($piggyBank, bcmul($amount, '-1'), $journal, null)); event(new ChangedAmount($piggyBank, bcmul($amount, '-1'), $journal, null));
return true; return true;
} }
public function addAmount(PiggyBank $piggyBank, string $amount, ?TransactionJournal $journal = null): bool public function removeAmountFromAll(PiggyBank $piggyBank, string $amount): void
{ {
$repetition = $this->getRepetition($piggyBank); foreach($piggyBank->accounts as $account) {
if (null === $repetition) { $current = $account->pivot->current_amount;
return false; // if this account contains more than the amount, remove the amount and return.
if (1 === bccomp($current, $amount)) {
$this->removeAmount($piggyBank, $account, $amount);
return;
} }
$currentAmount = $repetition->current_amount ?? '0'; // if this account contains less than the amount, remove the current amount, update the amount and continue.
$repetition->current_amount = bcadd($currentAmount, $amount); if (bccomp($current, $amount) < 1) {
$repetition->save(); $this->removeAmount($piggyBank, $account, $current);
$amount = bcsub($amount, $current);
}
}
}
public function addAmount(PiggyBank $piggyBank, Account $account, string $amount, ?TransactionJournal $journal = null): bool
{
$currentAmount = $this->getCurrentAmount($piggyBank, $account);
$pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
$pivot->current_amount = bcadd($currentAmount, $amount);
$pivot->save();
app('log')->debug('addAmount [b]: Trigger change for positive amount.'); app('log')->debug('addAmount [b]: Trigger change for positive amount.');
event(new ChangedAmount($piggyBank, $amount, $journal, null)); event(new ChangedAmount($piggyBank, $amount, $journal, null));
@@ -86,37 +100,36 @@ trait ModifiesPiggyBanks
return true; return true;
} }
public function canAddAmount(PiggyBank $piggyBank, string $amount): bool public function canAddAmount(PiggyBank $piggyBank, Account $account, string $amount): bool
{ {
$today = today(config('app.timezone')); Log::debug('Now in canAddAmount');
$leftOnAccount = $this->leftOnAccount($piggyBank, $today); $today = today(config('app.timezone'))->endOfDay();
$savedSoFar = $this->getRepetition($piggyBank)->current_amount; $leftOnAccount = $this->leftOnAccount($piggyBank, $account, $today);
$savedSoFar = $this->getCurrentAmount($piggyBank);
$maxAmount = $leftOnAccount; $maxAmount = $leftOnAccount;
$leftToSave = null;
app('log')->debug(sprintf('Left on account: %s on %s', $leftOnAccount, $today->format('Y-m-d H:i:s')));
app('log')->debug(sprintf('Saved so far: %s', $savedSoFar));
if (0 !== bccomp($piggyBank->target_amount, '0')) { if (0 !== bccomp($piggyBank->target_amount, '0')) {
$leftToSave = bcsub($piggyBank->target_amount, $savedSoFar); $leftToSave = bcsub($piggyBank->target_amount, $savedSoFar);
$maxAmount = 1 === bccomp($leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount; $maxAmount = 1 === bccomp($leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount;
app('log')->debug(sprintf('Left to save: %s', $leftToSave));
app('log')->debug(sprintf('Maximum amount: %s', $maxAmount));
} }
$compare = bccomp($amount, $maxAmount); $compare = bccomp($amount, $maxAmount);
$result = $compare <= 0; $result = $compare <= 0;
app('log')->debug(sprintf('Left on account: %s on %s', $leftOnAccount, $today->format('Y-m-d'))); app('log')->debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true)));
app('log')->debug(sprintf('Saved so far: %s', $savedSoFar));
app('log')->debug(sprintf('Left to save: %s', $leftToSave));
app('log')->debug(sprintf('Maximum amount: %s', $maxAmount));
app('log')->debug(sprintf('Compare <= 0? %d, so %s', $compare, var_export($result, true)));
return $result; return $result;
} }
public function canRemoveAmount(PiggyBank $piggyBank, string $amount): bool public function canRemoveAmount(PiggyBank $piggyBank, Account $account, string $amount): bool
{ {
$repetition = $this->getRepetition($piggyBank); $savedSoFar = $this->getCurrentAmount($piggyBank, $account);
if (null === $repetition) {
return false;
}
$savedSoFar = $repetition->current_amount;
return bccomp($amount, $savedSoFar) <= 0; return bccomp($amount, $savedSoFar) <= 0;
} }
@@ -244,17 +257,24 @@ trait ModifiesPiggyBanks
$this->setOrder($piggyBank, $newOrder); $this->setOrder($piggyBank, $newOrder);
} }
// update the accounts
$factory = new PiggyBankFactory();
$factory->user = $this->user;
$factory->linkToAccountIds($piggyBank, $data['accounts']);
// if the piggy bank is now smaller than the current relevant rep, // if the piggy bank is now smaller than the current relevant rep,
// remove money from the rep. // remove money from the rep.
$repetition = $this->getRepetition($piggyBank); $currentAmount = $this->getCurrentAmount($piggyBank);
if (null !== $repetition && $repetition->current_amount > $piggyBank->target_amount && 0 !== bccomp($piggyBank->target_amount, '0')) { if (1 === bccomp($currentAmount, '100') && 0 !== bccomp($piggyBank->target_amount, '0')) {
$difference = bcsub($piggyBank->target_amount, $repetition->current_amount); $difference = bcsub($piggyBank->target_amount, $currentAmount);
// an amount will be removed, create "negative" event: // an amount will be removed, create "negative" event:
event(new ChangedAmount($piggyBank, $difference, null, null)); event(new ChangedAmount($piggyBank, $difference, null, null));
$repetition->current_amount = $piggyBank->target_amount; // question is, from which account(s) to remove the difference?
$repetition->save(); // solution: just start from the top until there is no more money left to remove.
$this->removeAmountFromAll($piggyBank, app('steam')->positive($difference));
} }
// update using name: // update using name:
@@ -295,22 +315,19 @@ trait ModifiesPiggyBanks
if (array_key_exists('name', $data) && '' !== $data['name']) { if (array_key_exists('name', $data) && '' !== $data['name']) {
$piggyBank->name = $data['name']; $piggyBank->name = $data['name'];
} }
if (array_key_exists('account_id', $data) && 0 !== $data['account_id']) { if (array_key_exists('target_amount', $data) && '' !== $data['target_amount']) {
$piggyBank->account_id = (int)$data['account_id']; $piggyBank->target_amount = $data['target_amount'];
} }
if (array_key_exists('targetamount', $data) && '' !== $data['targetamount']) { if (array_key_exists('target_amount', $data) && '' === $data['target_amount']) {
$piggyBank->target_amount = $data['targetamount'];
}
if (array_key_exists('targetamount', $data) && '' === $data['targetamount']) {
$piggyBank->target_amount = '0'; $piggyBank->target_amount = '0';
} }
if (array_key_exists('targetdate', $data) && '' !== $data['targetdate']) { if (array_key_exists('target_date', $data) && '' !== $data['target_date']) {
$piggyBank->target_date = $data['targetdate']; $piggyBank->target_date = $data['target_date'];
$piggyBank->target_date_tz = $data['targetdate']?->format('e'); $piggyBank->target_date_tz = $data['target_date']?->format('e');
} }
if (array_key_exists('startdate', $data)) { if (array_key_exists('start_date', $data)) {
$piggyBank->start_date = $data['startdate']; $piggyBank->start_date = $data['start_date'];
$piggyBank->start_date_tz = $data['targetdate']?->format('e'); $piggyBank->start_date_tz = $data['target_date']?->format('e');
} }
$piggyBank->save(); $piggyBank->save();

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Repositories\PiggyBank;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\PiggyBankFactory; use FireflyIII\Factory\PiggyBankFactory;
use FireflyIII\Models\Account;
use FireflyIII\Models\Attachment; use FireflyIII\Models\Attachment;
use FireflyIII\Models\Note; use FireflyIII\Models\Note;
use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBank;
@@ -114,22 +115,28 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
/** /**
* Get current amount saved in piggy bank. * Get current amount saved in piggy bank.
*/ */
public function getCurrentAmount(PiggyBank $piggyBank): string public function getCurrentAmount(PiggyBank $piggyBank, ?Account $account = null): string
{ {
$sum = '0'; $sum = '0';
foreach ($piggyBank->accounts as $account) { foreach ($piggyBank->accounts as $current) {
$amount = (string) $account->pivot->current_amount; if(null !== $account && $account->id !== $current->id) {
continue;
}
$amount = (string) $current->pivot->current_amount;
$amount = '' === $amount ? '0' : $amount; $amount = '' === $amount ? '0' : $amount;
$sum = bcadd($sum, $amount); $sum = bcadd($sum, $amount);
} }
Log::debug(sprintf('Current amount in piggy bank #%d ("%s") is %s', $piggyBank->id, $piggyBank->name, $sum));
return $sum; return $sum;
} }
public function getRepetition(PiggyBank $piggyBank): ?PiggyBankRepetition public function getRepetition(PiggyBank $piggyBank, bool $overrule = false): ?PiggyBankRepetition
{ {
if (false === $overrule) {
throw new FireflyException('[b] Piggy bank repetitions are EOL.'); throw new FireflyException('[b] Piggy bank repetitions are EOL.');
}
Log::warning('Piggy bank repetitions are EOL.');
return $piggyBank->piggyBankRepetitions()->first(); return $piggyBank->piggyBankRepetitions()->first();
} }
@@ -286,8 +293,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
'objectGroups', 'objectGroups',
] ]
) )
->orderBy('piggy_banks.order', 'ASC')->get(['piggy_banks.*']) ->orderBy('piggy_banks.order', 'ASC')->distinct()->get(['piggy_banks.*']);
;
} }
/** /**
@@ -320,21 +326,22 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
/** /**
* Get for piggy account what is left to put in piggies. * Get for piggy account what is left to put in piggies.
*/ */
public function leftOnAccount(PiggyBank $piggyBank, Carbon $date): string public function leftOnAccount(PiggyBank $piggyBank, Account $account, Carbon $date): string
{ {
$balance = app('steam')->balanceIgnoreVirtual($piggyBank->account, $date); Log::debug(sprintf('leftOnAccount("%s","%s","%s")', $piggyBank->name, $account->name, $date->format('Y-m-d H:i:s')));
$balance = app('steam')->balanceConvertedIgnoreVirtual($account, $date, $piggyBank->transactionCurrency);
Log::debug(sprintf('Balance is: %s', $balance));
/** @var Collection $piggies */ /** @var Collection $piggies */
$piggies = $piggyBank->account->piggyBanks; $piggies = $account->piggyBanks;
/** @var PiggyBank $current */ /** @var PiggyBank $current */
foreach ($piggies as $current) { foreach ($piggies as $current) {
$repetition = $this->getRepetition($current); $amount = $this->getCurrentAmount($current, $account);
if (null !== $repetition) { $balance = bcsub($balance, $amount);
$balance = bcsub($balance, $repetition->current_amount); Log::debug(sprintf('Piggy bank: #%d with amount %s, balance is now %s', $current->id, $amount, $balance));
} }
} Log::debug(sprintf('Final balance is: %s', $balance));
return $balance; return $balance;
} }
@@ -345,8 +352,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
$search->whereLike('piggy_banks.name', sprintf('%%%s%%', $query)); $search->whereLike('piggy_banks.name', sprintf('%%%s%%', $query));
} }
$search->orderBy('piggy_banks.order', 'ASC') $search->orderBy('piggy_banks.order', 'ASC')
->orderBy('piggy_banks.name', 'ASC') ->orderBy('piggy_banks.name', 'ASC');
;
return $search->take($limit)->get(); return $search->take($limit)->get();
} }

View File

@@ -25,6 +25,7 @@ namespace FireflyIII\Repositories\PiggyBank;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankRepetition; use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
@@ -37,13 +38,13 @@ use Illuminate\Support\Collection;
*/ */
interface PiggyBankRepositoryInterface interface PiggyBankRepositoryInterface
{ {
public function addAmount(PiggyBank $piggyBank, string $amount, ?TransactionJournal $journal = null): bool; public function addAmount(PiggyBank $piggyBank, Account $account, string $amount, ?TransactionJournal $journal = null): bool;
public function addAmountToRepetition(PiggyBankRepetition $repetition, string $amount, TransactionJournal $journal): void; public function addAmountToRepetition(PiggyBankRepetition $repetition, string $amount, TransactionJournal $journal): void;
public function canAddAmount(PiggyBank $piggyBank, string $amount): bool; public function canAddAmount(PiggyBank $piggyBank, Account $account, string $amount): bool;
public function canRemoveAmount(PiggyBank $piggyBank, string $amount): bool; public function canRemoveAmount(PiggyBank $piggyBank, Account $account, string $amount): bool;
/** /**
* Destroy piggy bank. * Destroy piggy bank.
@@ -68,7 +69,10 @@ interface PiggyBankRepositoryInterface
/** /**
* Get current amount saved in piggy bank. * Get current amount saved in piggy bank.
*/ */
public function getCurrentAmount(PiggyBank $piggyBank): string; public function getCurrentAmount(PiggyBank $piggyBank, ?Account $account = null): string;
/**
* Get current amount saved in piggy bank.
*/
/** /**
* Get all events. * Get all events.
@@ -97,7 +101,7 @@ interface PiggyBankRepositoryInterface
*/ */
public function getPiggyBanksWithAmount(): Collection; public function getPiggyBanksWithAmount(): Collection;
public function getRepetition(PiggyBank $piggyBank): ?PiggyBankRepetition; public function getRepetition(PiggyBank $piggyBank, bool $overrule = false): ?PiggyBankRepetition;
/** /**
* Returns the suggested amount the user should save per month, or "". * Returns the suggested amount the user should save per month, or "".
@@ -107,9 +111,10 @@ interface PiggyBankRepositoryInterface
/** /**
* Get for piggy account what is left to put in piggies. * Get for piggy account what is left to put in piggies.
*/ */
public function leftOnAccount(PiggyBank $piggyBank, Carbon $date): string; public function leftOnAccount(PiggyBank $piggyBank,Account $account, Carbon $date): string;
public function removeAmount(PiggyBank $piggyBank, string $amount, ?TransactionJournal $journal = null): bool; public function removeAmount(PiggyBank $piggyBank, Account $account, string $amount, ?TransactionJournal $journal = null): bool;
public function removeAmountFromAll(PiggyBank $piggyBank, string $amount): void;
public function removeObjectGroup(PiggyBank $piggyBank): PiggyBank; public function removeObjectGroup(PiggyBank $piggyBank): PiggyBank;

View File

@@ -44,7 +44,7 @@ class Steam
*/ */
public function balanceIgnoreVirtual(Account $account, Carbon $date): string public function balanceIgnoreVirtual(Account $account, Carbon $date): string
{ {
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__)); throw new FireflyException('Deprecated method balanceIgnoreVirtual.');
/** @var AccountRepositoryInterface $repository */ /** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
$repository->setUser($account->user); $repository->setUser($account->user);
@@ -54,8 +54,7 @@ class Steam
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $currencyId) ->where('transactions.transaction_currency_id', $currencyId)
->get(['transactions.amount'])->toArray() ->get(['transactions.amount'])->toArray();
;
$nativeBalance = $this->sumTransactions($transactions, 'amount'); $nativeBalance = $this->sumTransactions($transactions, 'amount');
// get all balances in foreign currency: // get all balances in foreign currency:
@@ -64,14 +63,33 @@ class Steam
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.foreign_currency_id', $currencyId) ->where('transactions.foreign_currency_id', $currencyId)
->where('transactions.transaction_currency_id', '!=', $currencyId) ->where('transactions.transaction_currency_id', '!=', $currencyId)
->get(['transactions.foreign_amount'])->toArray() ->get(['transactions.foreign_amount'])->toArray();
;
$foreignBalance = $this->sumTransactions($transactions, 'foreign_amount'); $foreignBalance = $this->sumTransactions($transactions, 'foreign_amount');
return bcadd($nativeBalance, $foreignBalance); return bcadd($nativeBalance, $foreignBalance);
} }
public function balanceConvertedIgnoreVirtual(Account $account, Carbon $date, TransactionCurrency $currency): string
{
$balance = $this->balanceConverted($account, $date, $currency);
$virtual = null === $account->virtual_balance ? '0' : $account->virtual_balance;
// currency of account
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($account->user);
$accountCurrency = $repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
if ($accountCurrency->id !== $currency->id && 0 !== bccomp($virtual, '0')) {
// convert amount to given currency.
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__));
$converter = new ExchangeRateConverter();
$virtual = $converter->convert($accountCurrency, $currency, $date, $virtual);
}
return bcsub($balance, $virtual);
}
public function sumTransactions(array $transactions, string $key): string public function sumTransactions(array $transactions, string $key): string
{ {
$sum = '0'; $sum = '0';
@@ -140,8 +158,7 @@ class Steam
'transactions.foreign_currency_id', 'transactions.foreign_currency_id',
\DB::raw('SUM(transactions.foreign_amount) AS modified_foreign'), \DB::raw('SUM(transactions.foreign_amount) AS modified_foreign'),
] ]
) );
;
$currentBalance = $startBalance; $currentBalance = $startBalance;
@@ -187,8 +204,7 @@ class Steam
->orderBy('transaction_journals.date', 'desc') ->orderBy('transaction_journals.date', 'desc')
->orderBy('transaction_journals.order', 'asc') ->orderBy('transaction_journals.order', 'asc')
->orderBy('transaction_journals.description', 'desc') ->orderBy('transaction_journals.description', 'desc')
->orderBy('transactions.amount', 'desc') ->orderBy('transactions.amount', 'desc');
;
if (null !== $currency) { if (null !== $currency) {
$query->where('transactions.transaction_currency_id', $currency->id); $query->where('transactions.transaction_currency_id', $currency->id);
$query->limit(1); $query->limit(1);
@@ -241,8 +257,7 @@ class Steam
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $currency->id) ->where('transactions.transaction_currency_id', $currency->id)
->get(['transactions.amount'])->toArray() ->get(['transactions.amount'])->toArray();
;
$nativeBalance = $this->sumTransactions($transactions, 'amount'); $nativeBalance = $this->sumTransactions($transactions, 'amount');
// get all balances in foreign currency: // get all balances in foreign currency:
$transactions = $account->transactions() $transactions = $account->transactions()
@@ -250,8 +265,7 @@ class Steam
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.foreign_currency_id', $currency->id) ->where('transactions.foreign_currency_id', $currency->id)
->where('transactions.transaction_currency_id', '!=', $currency->id) ->where('transactions.transaction_currency_id', '!=', $currency->id)
->get(['transactions.foreign_amount'])->toArray() ->get(['transactions.foreign_amount'])->toArray();
;
$foreignBalance = $this->sumTransactions($transactions, 'foreign_amount'); $foreignBalance = $this->sumTransactions($transactions, 'foreign_amount');
$balance = bcadd($nativeBalance, $foreignBalance); $balance = bcadd($nativeBalance, $foreignBalance);
$virtual = null === $account->virtual_balance ? '0' : $account->virtual_balance; $virtual = null === $account->virtual_balance ? '0' : $account->virtual_balance;
@@ -310,8 +324,7 @@ class Steam
'transactions.foreign_currency_id', 'transactions.foreign_currency_id',
'transactions.foreign_amount', 'transactions.foreign_amount',
] ]
)->toArray() )->toArray();
;
// loop the set and convert if necessary: // loop the set and convert if necessary:
$currentBalance = $startBalance; $currentBalance = $startBalance;
@@ -425,16 +438,14 @@ class Steam
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $currency->id) ->where('transactions.transaction_currency_id', $currency->id)
->whereNull('transactions.foreign_currency_id') ->whereNull('transactions.foreign_currency_id')
->get(['transaction_journals.date', 'transactions.amount'])->toArray() ->get(['transaction_journals.date', 'transactions.amount'])->toArray();
;
Log::debug(sprintf('%d transaction(s) in set #1', count($new[0]))); Log::debug(sprintf('%d transaction(s) in set #1', count($new[0])));
$existing[] = $account->transactions() // 2 $existing[] = $account->transactions() // 2
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $native->id) ->where('transactions.transaction_currency_id', $native->id)
->whereNull('transactions.foreign_currency_id') ->whereNull('transactions.foreign_currency_id')
->get(['transactions.amount'])->toArray() ->get(['transactions.amount'])->toArray();
;
Log::debug(sprintf('%d transaction(s) in set #2', count($existing[0]))); Log::debug(sprintf('%d transaction(s) in set #2', count($existing[0])));
$new[] = $account->transactions() // 3 $new[] = $account->transactions() // 3
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
@@ -442,16 +453,14 @@ class Steam
->where('transactions.transaction_currency_id', '!=', $currency->id) ->where('transactions.transaction_currency_id', '!=', $currency->id)
->where('transactions.transaction_currency_id', '!=', $native->id) ->where('transactions.transaction_currency_id', '!=', $native->id)
->whereNull('transactions.foreign_currency_id') ->whereNull('transactions.foreign_currency_id')
->get(['transaction_journals.date', 'transactions.amount'])->toArray() ->get(['transaction_journals.date', 'transactions.amount'])->toArray();
;
Log::debug(sprintf('%d transactions in set #3', count($new[1]))); Log::debug(sprintf('%d transactions in set #3', count($new[1])));
$existing[] = $account->transactions() // 4 $existing[] = $account->transactions() // 4
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.foreign_currency_id', $native->id) ->where('transactions.foreign_currency_id', $native->id)
->whereNotNull('transactions.foreign_amount') ->whereNotNull('transactions.foreign_amount')
->get(['transactions.foreign_amount'])->toArray() ->get(['transactions.foreign_amount'])->toArray();
;
Log::debug(sprintf('%d transactions in set #4', count($existing[1]))); Log::debug(sprintf('%d transactions in set #4', count($existing[1])));
$new[] = $account->transactions()// 5 $new[] = $account->transactions()// 5
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
@@ -459,8 +468,7 @@ class Steam
->where('transactions.transaction_currency_id', $currency->id) ->where('transactions.transaction_currency_id', $currency->id)
->where('transactions.foreign_currency_id', '!=', $native->id) ->where('transactions.foreign_currency_id', '!=', $native->id)
->whereNotNull('transactions.foreign_amount') ->whereNotNull('transactions.foreign_amount')
->get(['transaction_journals.date', 'transactions.amount'])->toArray() ->get(['transaction_journals.date', 'transactions.amount'])->toArray();
;
Log::debug(sprintf('%d transactions in set #5', count($new[2]))); Log::debug(sprintf('%d transactions in set #5', count($new[2])));
$new[] = $account->transactions()// 6 $new[] = $account->transactions()// 6
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
@@ -468,8 +476,7 @@ class Steam
->where('transactions.transaction_currency_id', '!=', $currency->id) ->where('transactions.transaction_currency_id', '!=', $currency->id)
->where('transactions.foreign_currency_id', '!=', $native->id) ->where('transactions.foreign_currency_id', '!=', $native->id)
->whereNotNull('transactions.foreign_amount') ->whereNotNull('transactions.foreign_amount')
->get(['transaction_journals.date', 'transactions.amount'])->toArray() ->get(['transaction_journals.date', 'transactions.amount'])->toArray();
;
Log::debug(sprintf('%d transactions in set #6', count($new[3]))); Log::debug(sprintf('%d transactions in set #6', count($new[3])));
// process both sets of transactions. Of course, no need to convert set "existing". // process both sets of transactions. Of course, no need to convert set "existing".
@@ -644,8 +651,7 @@ class Steam
$query = $account->transactions() $query = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->groupBy('transactions.transaction_currency_id') ->groupBy('transactions.transaction_currency_id');
;
$balances = $query->get(['transactions.transaction_currency_id', \DB::raw('SUM(transactions.amount) as sum_for_currency')]); // @phpstan-ignore-line $balances = $query->get(['transactions.transaction_currency_id', \DB::raw('SUM(transactions.amount) as sum_for_currency')]); // @phpstan-ignore-line
$return = []; $return = [];

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\TransactionRules\Actions;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray; use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray;
use FireflyIII\Events\TriggeredAuditLog; use FireflyIII\Events\TriggeredAuditLog;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\RuleAction; use FireflyIII\Models\RuleAction;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
@@ -81,6 +82,7 @@ class UpdatePiggybank implements ActionInterface
if ($source->account_id === $piggyBank->account_id) { if ($source->account_id === $piggyBank->account_id) {
app('log')->debug('Piggy bank account is linked to source, so remove amount from piggy bank.'); app('log')->debug('Piggy bank account is linked to source, so remove amount from piggy bank.');
throw new FireflyException('Reference the correct account here.');
$this->removeAmount($piggyBank, $journal, $journalObj, $destination->amount); $this->removeAmount($piggyBank, $journal, $journalObj, $destination->amount);
event( event(
@@ -161,6 +163,7 @@ class UpdatePiggybank implements ActionInterface
} }
// make sure we can remove amount: // make sure we can remove amount:
throw new FireflyException('Reference the correct account here.');
if (false === $repository->canRemoveAmount($piggyBank, $amount)) { if (false === $repository->canRemoveAmount($piggyBank, $amount)) {
app('log')->warning(sprintf('Cannot remove %s from piggy bank.', $amount)); app('log')->warning(sprintf('Cannot remove %s from piggy bank.', $amount));
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_remove_from_piggy', ['amount' => $amount, 'name' => $piggyBank->name]))); event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_remove_from_piggy', ['amount' => $amount, 'name' => $piggyBank->name])));
@@ -169,6 +172,7 @@ class UpdatePiggybank implements ActionInterface
} }
app('log')->debug(sprintf('Will now remove %s from piggy bank.', $amount)); app('log')->debug(sprintf('Will now remove %s from piggy bank.', $amount));
throw new FireflyException('Reference the correct account here.');
$repository->removeAmount($piggyBank, $amount, $journal); $repository->removeAmount($piggyBank, $amount, $journal);
} }
@@ -199,6 +203,7 @@ class UpdatePiggybank implements ActionInterface
} }
// make sure we can add amount: // make sure we can add amount:
throw new FireflyException('Reference the correct account here.');
if (false === $repository->canAddAmount($piggyBank, $amount)) { if (false === $repository->canAddAmount($piggyBank, $amount)) {
app('log')->warning(sprintf('Cannot add %s to piggy bank.', $amount)); app('log')->warning(sprintf('Cannot add %s to piggy bank.', $amount));
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_add_to_piggy', ['amount' => $amount, 'name' => $piggyBank->name]))); event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_add_to_piggy', ['amount' => $amount, 'name' => $piggyBank->name])));

View File

@@ -78,7 +78,7 @@ class PiggyBankTransformer extends AbstractTransformer
// get currently saved amount: // get currently saved amount:
$currency = $piggyBank->transactionCurrency; $currency = $piggyBank->transactionCurrency;
$currentAmount = app('steam')->bcround($this->piggyRepos->getCurrentAmount($piggyBank), $currency->decimal_places); $currentAmount = $this->piggyRepos->getCurrentAmount($piggyBank);
// Amounts, depending on 0.0 state of target amount // Amounts, depending on 0.0 state of target amount
$percentage = null; $percentage = null;

View File

@@ -106,6 +106,10 @@ class User extends Authenticatable
return $this->hasMany(Account::class); return $this->hasMany(Account::class);
} }
public function piggyBanks() {
throw new FireflyException('Method no longer supported.');
}
/** /**
* Link to attachments * Link to attachments
*/ */

View File

@@ -28,8 +28,8 @@ return [
'slack' => ['enabled' => true, 'ui_configurable' => 1], 'slack' => ['enabled' => true, 'ui_configurable' => 1],
'ntfy' => ['enabled' => true, 'ui_configurable' => 1], 'ntfy' => ['enabled' => true, 'ui_configurable' => 1],
'pushover' => ['enabled' => true, 'ui_configurable' => 1], 'pushover' => ['enabled' => true, 'ui_configurable' => 1],
'gotify' => ['enabled' => false, 'ui_configurable' => 0], // 'gotify' => ['enabled' => false, 'ui_configurable' => 0],
'pushbullet' => ['enabled' => false, 'ui_configurable' => 0], // 'pushbullet' => ['enabled' => false, 'ui_configurable' => 0],
], ],
'notifications' => [ 'notifications' => [
'user' => [ 'user' => [

View File

@@ -25,9 +25,9 @@
<td style="text-align: right;"> <td style="text-align: right;">
{% if event.amount < 0 %} {% if event.amount < 0 %}
<span class="text-danger money-negative">{{ trans('firefly.removed_amount', {amount: formatAmountByAccount(event.piggyBank.account, event.amount, false)})|raw }}</span> <span class="text-danger money-negative">{{ trans('firefly.removed_amount', {amount: formatAmountBySymbol(event.amount,event.piggyBank.transactionCurrency.symbol, false)})|raw }}</span>
{% else %} {% else %}
<span class="text-success money-positive">{{ trans('firefly.added_amount', {amount: formatAmountByAccount(event.piggyBank.account, event.amount, false)})|raw }}</span> <span class="text-success money-positive">{{ trans('firefly.added_amount', {amount: formatAmountBySymbol(event.amount, event.piggyBank.transactionCurrency.symbol, false)})|raw }}</span>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>

View File

@@ -14,16 +14,16 @@
<h3 class="box-title">{{ trans('firefly.add_money_to_piggy', {name: piggyBank.name}) }}</h3> <h3 class="box-title">{{ trans('firefly.add_money_to_piggy', {name: piggyBank.name}) }}</h3>
</div> </div>
<div class="box-body"> <div class="box-body">
{% if maxAmount > 0 %} {% if total > 0 %}
<p>
{{ 'max_amount_add'|_ }}: {{ formatAmountByCurrency(currency,maxAmount) }}.
</p>
{% for account in accounts %}
<strong>{{ account.account.name }} ({{ 'max_amount_add'|_ }}: {{ formatAmountByCurrency(piggyBank.transactionCurrency, account.max_amount) }})</strong>
<div class="input-group"> <div class="input-group">
<div class="input-group-addon">{{ currency.symbol|raw }}</div> <div class="input-group-addon">{{ piggyBank.transactionCurrency.symbol|raw }}</div>
<input step="any" class="form-control" id="amount" autocomplete="off" name="amount" max="{{ maxAmount|round(2) }}" <input step="any" min="0" class="form-control" id="amount_{{ account.account.id }}" autocomplete="off" name="amount[{{ account.account.id }}]" max="{{ account.max_amount|round(piggyBank.transactionCurrency.decimal_places) }}" type="number"/>
type="number"/>
</div> </div>
{% endfor %}
<p> <p>
&nbsp; &nbsp;
</p> </p>

View File

@@ -5,19 +5,17 @@
</button> </button>
<h4 class="modal-title">{{ trans('firefly.add_money_to_piggy_title', {name: piggyBank.name}) }}</h4> <h4 class="modal-title">{{ trans('firefly.add_money_to_piggy_title', {name: piggyBank.name}) }}</h4>
</div> </div>
{% if maxAmount > 0 %} {% if total > 0 %}
<form style="display: inline;" id="add" action="{{ route('piggy-banks.add', piggyBank.id) }}" method="POST"> <form style="display: inline;" id="add" action="{{ route('piggy-banks.add', piggyBank.id) }}" method="POST">
<div class="modal-body"> <div class="modal-body">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/> <input type="hidden" name="_token" value="{{ csrf_token() }}"/>
{% for account in accounts %}
<p> <strong>{{ account.account.name }} ({{ 'max_amount_add'|_ }}: {{ formatAmountByCurrency(piggyBank.transactionCurrency, account.max_amount) }})</strong>
{{ 'max_amount_add'|_ }}: {{ formatAmountByCurrency(currency,maxAmount) }}.
</p>
<div class="input-group"> <div class="input-group">
<div class="input-group-addon">{{ currency.symbol|raw }}</div> <div class="input-group-addon">{{ piggyBank.transactionCurrency.symbol|raw }}</div>
<input step="any" class="form-control" id="amount" autocomplete="off" name="amount" max="{{ maxAmount|round(currency.decimal_places) }}" type="number"/> <input step="any" min="0" class="form-control" id="amount_{{ account.account.id }}" autocomplete="off" name="amount[{{ account.account.id }}]" max="{{ account.max_amount|round(piggyBank.transactionCurrency.decimal_places) }}" type="number"/>
</div> </div>
{% endfor %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'close'|_ }}</button> <button type="button" class="btn btn-default" data-dismiss="modal">{{ 'close'|_ }}</button>

View File

@@ -22,8 +22,9 @@
<div class="box-body"> <div class="box-body">
{{ ExpandedForm.text('name') }} {{ ExpandedForm.text('name') }}
{{ AccountForm.assetAccountList('account_id', null, {label: 'saveOnAccount'|_ }) }} {{ ExpandedForm.amountNoCurrency('target_amount') }}
{{ ExpandedForm.amountNoCurrency('targetamount') }} {{ CurrencyForm.currencyList('transaction_currency_id', null, {helpText:'piggy_default_currency'|_}) }}
{{ AccountForm.assetLiabilityMultiAccountList('accounts', preFilled.accounts, {label: 'saveOnAccounts'|_, helpText: 'piggy_account_currency_match'|_ }) }}
</div> </div>
</div> </div>

View File

@@ -14,15 +14,17 @@
</div> </div>
<div class="box-body"> <div class="box-body">
{% for account in accounts %}
<p> <p>
{{ 'max_amount_remove'|_ }}: {{ formatAmountByCurrency(currency, repetition.currentamount) }}. {{ account.account.name }}: {{ 'max_amount_remove'|_ }}: {{ formatAmountByCurrency(piggyBank.transactionCurrency, account.saved_so_far) }}.
</p> </p>
<div class="input-group"> <div class="input-group">
<div class="input-group-addon">{{ currency.symbol|raw }}</div> <div class="input-group-addon">{{ piggyBank.transactionCurrency.symbol|raw }}</div>
<input step="any" class="form-control" id="amount" autocomplete="off" name="amount" max="{{ repetition.currentamount }}" <input step="any" class="form-control" id="amount_{{ account.account.id }}" autocomplete="off" name="amount[{{ account.account.id }}]" max="{{ account.saved_so_far }}"
type="number"/> type="number">
</div> </div>
{% endfor %}
<p> <p>
&nbsp; &nbsp;
</p> </p>

View File

@@ -10,15 +10,16 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
{% for account in accounts %}
<p> <p>
{{ 'max_amount_remove'|_ }}: {{ formatAmountByCurrency(currency, repetition.currentamount) }}. {{ account.account.name }}: {{ 'max_amount_remove'|_ }}: {{ formatAmountByCurrency(piggyBank.transactionCurrency, account.saved_so_far) }}.
</p> </p>
<div class="input-group"> <div class="input-group">
<div class="input-group-addon">{{ currency.symbol|raw }}</div> <div class="input-group-addon">{{ piggyBank.transactionCurrency.symbol|raw }}</div>
<input step="any" class="form-control" id="amount" autocomplete="off" name="amount" max="{{ repetition.currentamount }}" <input step="any" class="form-control" id="amount_{{ account.account.id }}" autocomplete="off" name="amount[{{ account.account.id }}]" max="{{ account.saved_so_far }}"
type="number"> type="number">
</div> </div>
{% endfor %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'close'|_ }}</button> <button type="button" class="btn btn-default" data-dismiss="modal">{{ 'close'|_ }}</button>

View File

@@ -33,8 +33,12 @@
<div class="box-body no-padding"> <div class="box-body no-padding">
<table class="table table-responsive table-hover" id="piggyDetails"> <table class="table table-responsive table-hover" id="piggyDetails">
<tr> <tr>
<td style="width:40%;">{{ 'account'|_ }}</td> <td style="width:40%;">{{ 'saveOnAccounts'|_ }}</td>
<td><a href="{{ route('accounts.show', piggyBank.account_id) }}">{{ piggyBank.account.name }}</a></td> <td>
{% for account in piggy.accounts %}
<a href="{{ route('accounts.show', account.id) }}">{{ account.name }}</a><br>
{% endfor %}
</td>
</tr> </tr>
{% if piggy.object_group_title %} {% if piggy.object_group_title %}
<tr> <tr>