diff --git a/app/Events/StoredTransactionJournal.php b/app/Events/StoredTransactionJournal.php index c76bc7c006..d1d6805cd2 100644 --- a/app/Events/StoredTransactionJournal.php +++ b/app/Events/StoredTransactionJournal.php @@ -26,7 +26,9 @@ class StoredTransactionJournal extends Event use SerializesModels; + /** @var TransactionJournal */ public $journal; + /** @var int */ public $piggyBankId; /** diff --git a/app/Events/UpdatedTransactionJournal.php b/app/Events/UpdatedTransactionJournal.php index aab43fca25..ce21810bc2 100644 --- a/app/Events/UpdatedTransactionJournal.php +++ b/app/Events/UpdatedTransactionJournal.php @@ -26,6 +26,7 @@ class UpdatedTransactionJournal extends Event use SerializesModels; + /** @var TransactionJournal */ public $journal; /** diff --git a/app/Handlers/Events/StoredJournalEventHandler.php b/app/Handlers/Events/StoredJournalEventHandler.php index 33df20ebe9..0cf669705c 100644 --- a/app/Handlers/Events/StoredJournalEventHandler.php +++ b/app/Handlers/Events/StoredJournalEventHandler.php @@ -14,13 +14,11 @@ declare(strict_types=1); namespace FireflyIII\Handlers\Events; use FireflyIII\Events\StoredTransactionJournal; -use FireflyIII\Models\PiggyBank; -use FireflyIII\Models\PiggyBankEvent; -use FireflyIII\Models\PiggyBankRepetition; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleGroup; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; use FireflyIII\Rules\Processor; use FireflyIII\Support\Events\BillScanner; use Log; @@ -32,58 +30,74 @@ use Log; */ class StoredJournalEventHandler { + /** @var JournalRepositoryInterface */ + public $journalRepository; + /** @var PiggyBankRepositoryInterface */ + public $repository; + + public $ruleGroupRepos; + + /** + * StoredJournalEventHandler constructor. + */ + public function __construct( + PiggyBankRepositoryInterface $repository, JournalRepositoryInterface $journalRepository, RuleGroupRepositoryInterface $ruleGroupRepository + ) { + $this->repository = $repository; + $this->journalRepository = $journalRepository; + $this->ruleGroupRepos = $ruleGroupRepository; + + } + /** * This method connects a new transfer to a piggy bank. * + * @codeCoverageIgnore + * * @param StoredTransactionJournal $event * * @return bool */ public function connectToPiggyBank(StoredTransactionJournal $event): bool { - /** @var TransactionJournal $journal */ $journal = $event->journal; $piggyBankId = $event->piggyBankId; - Log::debug(sprintf('Trying to connect journal %d to piggy bank %d.', $journal->id, $piggyBankId)); + $piggyBank = $this->repository->find($piggyBankId); - /* - * Will only continue when journal is a transfer. - */ - Log::debug(sprintf('Journal transaction type is %s', $journal->transactionType->type)); - if ($journal->transactionType->type !== TransactionType::TRANSFER) { + // is a transfer? + if (!$this->journalRepository->isTransfer($journal)) { Log::info(sprintf('Will not connect %s #%d to a piggy bank.', $journal->transactionType->type, $journal->id)); return true; } - /* - * Verify existence of piggy bank: - */ - if (!$this->verifyExistence($event)) { - Log::error(sprintf('No such piggy bank or no repetition on %s', $journal->date->format('Y-m-d'))); + // piggy exists? + if (is_null($piggyBank->id)) { + Log::error(sprintf('There is no piggy bank with ID #%d', $piggyBankId)); return true; } - /* - * Get relevant data: - */ - $piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); - $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); - $amount = $this->getExactAmount($journal, $piggyBank, $repetition); + // repetition exists? + $repetition = $this->repository->getRepetition($piggyBank, $journal->date); + if (is_null($repetition->id)) { + Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d'))); + + return true; + } + + // get the amount + $amount = $this->repository->getExactAmount($piggyBank, $repetition, $journal); if (bccomp($amount, '0') === 0) { Log::debug('Amount is zero, will not create event.'); return true; } - $repetition->currentamount = bcadd($repetition->currentamount, $amount); - $repetition->save(); + // update amount + $this->repository->addAmountToRepetition($repetition, $amount); + $event = $this->repository->createEventWithJournal($piggyBank, $amount, $journal); - /** @var PiggyBankEvent $event */ - $event = PiggyBankEvent::create( - ['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount] - ); Log::debug(sprintf('Created piggy bank event #%d', $event->id)); return true; @@ -140,81 +154,4 @@ class StoredJournalEventHandler return true; } - - /** - * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but I can live with it. - * @param TransactionJournal $journal - * @param PiggyBank $piggyBank - * @param PiggyBankRepetition $repetition - * - * @return string - */ - private function getExactAmount(TransactionJournal $journal, PiggyBank $piggyBank, PiggyBankRepetition $repetition): string - { - $amount = $journal->amountPositive(); - $sources = $journal->sourceAccountList()->pluck('id')->toArray(); - $room = bcsub(strval($piggyBank->targetamount), strval($repetition->currentamount)); - $compare = bcmul($repetition->currentamount, '-1'); - - Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); - - // if piggy account matches source account, the amount is positive - if (in_array($piggyBank->account_id, $sources)) { - $amount = bcmul($amount, '-1'); - Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id)); - } - - - // if the amount is positive, make sure it fits in piggy bank: - if (bccomp($amount, '0') === 1 && bccomp($room, $amount) === -1) { - // amount is positive and $room is smaller than $amount - Log::debug(sprintf('Room in piggy bank for extra money is %f', $room)); - Log::debug(sprintf('There is NO room to add %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); - Log::debug(sprintf('New amount is %f', $room)); - - return $room; - } - - // amount is negative and $currentamount is smaller than $amount - if (bccomp($amount, '0') === -1 && bccomp($compare, $amount) === 1) { - Log::debug(sprintf('Max amount to remove is %f', $repetition->currentamount)); - Log::debug(sprintf('Cannot remove %f from piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); - Log::debug(sprintf('New amount is %f', $compare)); - - return $compare; - } - - return $amount; - } - - /** - * @param StoredTransactionJournal $event - * - * @return bool - */ - private function verifyExistence(StoredTransactionJournal $event): bool - { - /** @var TransactionJournal $journal */ - $journal = $event->journal; - $piggyBankId = $event->piggyBankId; - - /** @var PiggyBank $piggyBank */ - $piggyBank = $journal->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); - - if (is_null($piggyBank)) { - Log::error('No such piggy bank!'); - - return false; - } - Log::debug(sprintf('Found piggy bank #%d: "%s"', $piggyBank->id, $piggyBank->name)); - // update piggy bank rep for date of transaction journal. - $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); - if (is_null($repetition)) { - Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d'))); - - return false; - } - - return true; - } } diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 2b44dbda42..98ab2dafb5 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -138,6 +138,16 @@ class JournalRepository implements JournalRepositoryInterface return TransactionType::orderBy('type', 'ASC')->get(); } + /** + * @param TransactionJournal $journal + * + * @return bool + */ + public function isTransfer(TransactionJournal $journal): bool + { + return $journal->transactionType->type === TransactionType::TRANSFER; + } + /** * @param TransactionJournal $journal * @param int $order diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index c33d47367b..028f2d52d7 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -27,6 +27,7 @@ use Illuminate\Support\MessageBag; */ interface JournalRepositoryInterface { + /** * @param TransactionJournal $journal * @param TransactionType $type @@ -67,6 +68,13 @@ interface JournalRepositoryInterface */ public function getTransactionTypes(): Collection; + /** + * @param TransactionJournal $journal + * + * @return bool + */ + public function isTransfer(TransactionJournal $journal): bool; + /** * @param TransactionJournal $journal * @param int $order diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 85fa634279..e721c71b63 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -18,8 +18,11 @@ use Carbon\Carbon; use FireflyIII\Models\Note; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankEvent; +use FireflyIII\Models\PiggyBankRepetition; +use FireflyIII\Models\TransactionJournal; use FireflyIII\User; use Illuminate\Support\Collection; +use Log; /** * Class PiggyBankRepository @@ -51,6 +54,21 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return true; } + /** + * @param PiggyBankRepetition $repetition + * @param string $amount + * + * @return string + */ + public function addAmountToRepetition(PiggyBankRepetition $repetition, string $amount): string + { + $newAmount = bcadd($repetition->currentamount, $amount); + $repetition->currentamount = $newAmount; + $repetition->save(); + + return $newAmount; + } + /** * @param PiggyBank $piggyBank * @param string $amount @@ -94,6 +112,23 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return $event; } + /** + * @param PiggyBank $piggyBank + * @param string $amount + * @param TransactionJournal $journal + * + * @return PiggyBankEvent + */ + public function createEventWithJournal(PiggyBank $piggyBank, string $amount, TransactionJournal $journal): PiggyBankEvent + { + /** @var PiggyBankEvent $event */ + $event = PiggyBankEvent::create( + ['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount] + ); + + return $event; + } + /** * @param PiggyBank $piggyBank * @@ -132,6 +167,53 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return $piggyBank->piggyBankEvents()->orderBy('date', 'DESC')->orderBy('id', 'DESC')->get(); } + /** + * Used for connecting to a piggy bank. + * + * @param PiggyBank $piggyBank + * @param PiggyBankRepetition $repetition + * @param TransactionJournal $journal + * + * @return string + */ + public function getExactAmount(PiggyBank $piggyBank, PiggyBankRepetition $repetition, TransactionJournal $journal): string + { + $amount = $journal->amountPositive(); + $sources = $journal->sourceAccountList()->pluck('id')->toArray(); + $room = bcsub(strval($piggyBank->targetamount), strval($repetition->currentamount)); + $compare = bcmul($repetition->currentamount, '-1'); + + Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); + + // if piggy account matches source account, the amount is positive + if (in_array($piggyBank->account_id, $sources)) { + $amount = bcmul($amount, '-1'); + Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id)); + } + + + // if the amount is positive, make sure it fits in piggy bank: + if (bccomp($amount, '0') === 1 && bccomp($room, $amount) === -1) { + // amount is positive and $room is smaller than $amount + Log::debug(sprintf('Room in piggy bank for extra money is %f', $room)); + Log::debug(sprintf('There is NO room to add %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); + Log::debug(sprintf('New amount is %f', $room)); + + return $room; + } + + // amount is negative and $currentamount is smaller than $amount + if (bccomp($amount, '0') === -1 && bccomp($compare, $amount) === 1) { + Log::debug(sprintf('Max amount to remove is %f', $repetition->currentamount)); + Log::debug(sprintf('Cannot remove %f from piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); + Log::debug(sprintf('New amount is %f', $compare)); + + return $compare; + } + + return $amount; + } + /** * @return int */ @@ -167,6 +249,22 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return $set; } + /** + * @param PiggyBank $piggyBank + * @param Carbon $date + * + * @return PiggyBankRepetition + */ + public function getRepetition(PiggyBank $piggyBank, Carbon $date): PiggyBankRepetition + { + $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($date)->first(); + if (is_null($repetition)) { + return new PiggyBankRepetition; + } + + return $repetition; + } + /** * @param PiggyBank $piggyBank * @param string $amount diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php index 4c063931fc..0592d75e3b 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php @@ -13,8 +13,11 @@ declare(strict_types=1); namespace FireflyIII\Repositories\PiggyBank; +use Carbon\Carbon; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankEvent; +use FireflyIII\Models\PiggyBankRepetition; +use FireflyIII\Models\TransactionJournal; use FireflyIII\User; use Illuminate\Support\Collection; @@ -25,7 +28,6 @@ use Illuminate\Support\Collection; */ interface PiggyBankRepositoryInterface { - /** * @param PiggyBank $piggyBank * @param string $amount @@ -34,6 +36,14 @@ interface PiggyBankRepositoryInterface */ public function addAmount(PiggyBank $piggyBank, string $amount): bool; + /** + * @param PiggyBankRepetition $repetition + * @param string $amount + * + * @return string + */ + public function addAmountToRepetition(PiggyBankRepetition $repetition, string $amount): string; + /** * @param PiggyBank $piggyBank * @param string $amount @@ -60,6 +70,15 @@ interface PiggyBankRepositoryInterface */ public function createEvent(PiggyBank $piggyBank, string $amount): PiggyBankEvent; + /** + * @param PiggyBank $piggyBank + * @param string $amount + * @param TransactionJournal $journal + * + * @return PiggyBankEvent + */ + public function createEventWithJournal(PiggyBank $piggyBank, string $amount, TransactionJournal $journal): PiggyBankEvent; + /** * Destroy piggy bank. * @@ -85,6 +104,17 @@ interface PiggyBankRepositoryInterface */ public function getEvents(PiggyBank $piggyBank): Collection; + /** + * Used for connecting to a piggy bank. + * + * @param PiggyBank $piggyBank + * @param PiggyBankRepetition $repetition + * @param TransactionJournal $journal + * + * @return string + */ + public function getExactAmount(PiggyBank $piggyBank, PiggyBankRepetition $repetition, TransactionJournal $journal): string; + /** * Highest order of all piggy banks. * @@ -106,6 +136,14 @@ interface PiggyBankRepositoryInterface */ public function getPiggyBanksWithAmount(): Collection; + /** + * @param PiggyBank $piggyBank + * @param Carbon $date + * + * @return PiggyBankRepetition + */ + public function getRepetition(PiggyBank $piggyBank, Carbon $date): PiggyBankRepetition; + /** * @param PiggyBank $piggyBank * @param string $amount diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index df79a8b3c6..283a342b20 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -85,7 +85,7 @@ $factory->define( $factory->define( FireflyIII\Models\TransactionJournal::class, function (Faker\Generator $faker) { return [ - 'id' => $faker->numberBetween(1, 10), + 'id' => $faker->unique()->numberBetween(1000, 10000), 'user_id' => 1, 'transaction_type_id' => 1, 'bill_id' => null, @@ -122,6 +122,18 @@ $factory->define( } ); +$factory->define( + FireflyIII\Models\PiggyBankRepetition::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->numberBetween(100, 10000), + 'piggy_bank_id' => $faker->numberBetween(1, 10), + 'startdate' => '2017-01-01', + 'targetdate' => '2020-01-01', + 'currentamount' => 10, + ]; +} +); + $factory->define( FireflyIII\Models\PiggyBank::class, function (Faker\Generator $faker) { return [ @@ -194,8 +206,9 @@ $factory->define( $factory->define( FireflyIII\Models\Account::class, function (Faker\Generator $faker) { return [ - 'id' => $faker->numberBetween(1, 10), - 'name' => $faker->words(3, true), + 'id' => $faker->unique()->numberBetween(1000, 10000), + 'name' => $faker->words(3, true), + 'account_type_id' => 1, ]; } );