/** * @param TransactionJournal $journal * * @return bool */ public function act(TransactionJournal $journal) : bool { Log::debug(sprintf('RuleAction PrependDescription prepended "%s" to "%s".', $this->action->action_value, $journal->description)); $journal->description = $this->action->action_value . $journal->description; $journal->save(); return true; }
/** * @param TransactionJournal $journal * * @return bool */ public function act(TransactionJournal $journal) : bool { $name = $this->action->action_value; $category = Category::firstOrCreateEncrypted(['name' => $name, 'user_id' => $journal->user->id]); $journal->categories()->sync([$category->id]); Log::debug(sprintf('RuleAction SetCategory set the category of journal #%d to budget #%d ("%s").', $journal->id, $category->id, $category->name)); return true; }
/** * @param TransactionJournal $journal * * @return bool */ public function act(TransactionJournal $journal) : bool { $oldDescription = $journal->description; $journal->description = $this->action->action_value; $journal->save(); Log::debug(sprintf('RuleAction SetDescription changed the description of journal #%d from "%s" to "%s".', $journal->id, $oldDescription, $this->action->action_value)); return true; }
/** * @param TransactionJournal $journal * * @return bool */ public function act(TransactionJournal $journal) : bool { // journal has this tag maybe? $tag = Tag::firstOrCreateEncrypted(['tag' => $this->action->action_value, 'user_id' => $journal->user->id]); $count = $journal->tags()->where('tag_id', $tag->id)->count(); if ($count === 0) { $journal->tags()->save($tag); Log::debug(sprintf('RuleAction AddTag. Added tag #%d ("%s") to journal %d.', $tag->id, $tag->tag, $journal->id)); return true; } Log::debug(sprintf('RuleAction AddTag fired but tag %d ("%s") was already added to journal %d.', $tag->id, $tag->tag, $journal->id)); return true; }
/** * * @param TransactionJournal $journal * @param bool $coloured * * @return string */ public function formatJournal(TransactionJournal $journal, $coloured = true) { $cache = new CacheProperties(); $cache->addProperty($journal->id); $cache->addProperty('formatJournal'); if ($cache->has()) { return $cache->get(); // @codeCoverageIgnore } if (is_null($journal->symbol)) { $symbol = $journal->transactionCurrency->symbol; } else { $symbol = $journal->symbol; } if ($journal->isTransfer() && $coloured) { $txt = '<span class="text-info">' . $this->formatWithSymbol($symbol, $journal->amount_positive, false) . '</span>'; $cache->store($txt); return $txt; } if ($journal->isTransfer() && !$coloured) { $txt = $this->formatWithSymbol($symbol, $journal->amount_positive, false); $cache->store($txt); return $txt; } $txt = $this->formatWithSymbol($symbol, $journal->amount, $coloured); $cache->store($txt); return $txt; }
/** * Connect a new transaction journal to any related piggy banks. * * @param TransactionJournalStored $event * * @return bool */ public function handle(TransactionJournalStored $event) : bool { /** @var TransactionJournal $journal */ $journal = $event->journal; $piggyBankId = $event->piggyBankId; /** @var PiggyBank $piggyBank */ $piggyBank = auth()->user()->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); if (is_null($piggyBank)) { return true; } // update piggy bank rep for date of transaction journal. $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); if (is_null($repetition)) { return true; } $amount = TransactionJournal::amountPositive($journal); // if piggy account matches source account, the amount is positive $sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray(); if (in_array($piggyBank->account_id, $sources)) { $amount = bcmul($amount, '-1'); } $repetition->currentamount = bcadd($repetition->currentamount, $amount); $repetition->save(); PiggyBankEvent::create(['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount]); return true; }
/** * @param TransactionJournal $journal * * @return bool */ public function act(TransactionJournal $journal) : bool { // if tag does not exist, no need to continue: $name = $this->action->action_value; /** @var Tag $tag */ $tag = $journal->user->tags()->get()->filter(function (Tag $tag) use($name) { return $tag->tag == $name; })->first(); if (!is_null($tag)) { Log::debug(sprintf('RuleAction RemoveTag removed tag #%d ("%s") from journal #%d.', $tag->id, $tag->tag, $journal->id)); $journal->tags()->detach([$tag->id]); return true; } Log::debug(sprintf('RuleAction RemoveTag tried to remove tag "%s" from journal #%d but no such tag exists.', $name, $journal->id)); return true; }
/** * Handle the event. * * @param TransactionJournalUpdated $event * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5. * * @return bool */ public function handle(TransactionJournalUpdated $event) : bool { $journal = $event->journal; if (!$journal->isTransfer()) { return true; } // get the event connected to this journal: /** @var PiggyBankEvent $event */ $event = PiggyBankEvent::where('transaction_journal_id', $journal->id)->first(); if (is_null($event)) { return false; } $piggyBank = $event->piggyBank()->first(); $repetition = null; if (!is_null($piggyBank)) { /** @var PiggyBankRepetition $repetition */ $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); } if (is_null($repetition)) { return false; } $amount = TransactionJournal::amount($journal); $diff = bcsub($amount, $event->amount); // update current repetition $repetition->currentamount = bcadd($repetition->currentamount, $diff); $repetition->save(); $event->amount = $amount; $event->save(); return true; }
/** * @covers FireflyIII\Repositories\Journal\JournalRepository::delete * @covers FireflyIII\Providers\EventServiceProvider::boot * @covers FireflyIII\Providers\EventServiceProvider::registerDeleteEvents */ public function testDelete() { $journal = FactoryMuffin::create('FireflyIII\\Models\\TransactionJournal'); $transaction = $journal->transactions[0]; $this->object->delete($journal); $this->assertEquals(0, TransactionJournal::where('id', $journal->id)->whereNull('deleted_at')->count()); $this->assertEquals(0, Transaction::where('id', $transaction->id)->whereNull('deleted_at')->count()); }
/** * @param $value * @param $route * * @return mixed */ public static function routeBinder($value, $route) : TransactionJournal { if (auth()->check()) { $object = TransactionJournal::where('transaction_journals.id', $value)->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')->where('completed', 0)->where('user_id', auth()->user()->id)->first(['transaction_journals.*']); if ($object) { return $object; } } throw new NotFoundHttpException(); }
/** * @covers FireflyIII\Models\Tag::save */ public function testSave() { $tag = FactoryMuffin::create('FireflyIII\\Models\\Tag'); // connect some transaction journals to the tag: $journal = FactoryMuffin::create('FireflyIII\\Models\\TransactionJournal'); $journal->tags()->save($tag); $tag->save(); $journal = TransactionJournal::find($journal->id); $this->assertEquals(1, $journal->tag_count); }
/** * @param $value * * @return mixed * @throws NotFoundHttpException */ public static function routeBinder($value) { if (Auth::check()) { $validTypes = [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]; $object = TransactionJournal::where('transaction_journals.id', $value)->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')->whereIn('transaction_types.type', $validTypes)->where('user_id', Auth::user()->id)->first(['transaction_journals.*']); if ($object) { return $object; } } throw new NotFoundHttpException(); }
/** * @param TransactionJournal $journal * * @return bool */ public function act(TransactionJournal $journal) : bool { /** @var BudgetRepositoryInterface $repository */ $repository = app(BudgetRepositoryInterface::class, [$journal->user]); $search = $this->action->action_value; $budgets = $repository->getActiveBudgets(); $budget = $budgets->filter(function (Budget $current) use($search) { return $current->name == $search; })->first(); if (is_null($budget)) { Log::debug(sprintf('RuleAction SetBudget could not set budget of journal #%d to "%s" because no such budget exists.', $journal->id, $search)); return true; } if ($journal->transactionType->type == TransactionType::TRANSFER) { Log::debug(sprintf('RuleAction SetBudget could not set budget of journal #%d to "%s" because journal is a transfer.', $journal->id, $search)); return true; } Log::debug(sprintf('RuleAction SetBudget set the budget of journal #%d to budget #%d ("%s").', $journal->id, $budget->id, $budget->name)); $journal->budgets()->sync([$budget->id]); return true; }
/** * @param TransactionJournal $journal * * @return bool */ public function triggered(TransactionJournal $journal) : bool { $amount = $journal->destination_amount ?? TransactionJournal::amountPositive($journal); $compare = $this->triggerValue; $result = bccomp($amount, $compare, 4); if ($result === 1) { Log::debug(sprintf('RuleTrigger AmountMore for journal #%d: %d is more than %d, so return true', $journal->id, $amount, $compare)); return true; } Log::debug(sprintf('RuleTrigger AmountMore for journal #%d: %d is NOT more than %d, so return false', $journal->id, $amount, $compare)); return false; }
/** * @param $value * @param $route * * @return mixed */ public static function routeBinder($value, $route) : Collection { if (auth()->check()) { $ids = explode(',', $value); /** @var \Illuminate\Support\Collection $object */ $object = TransactionJournal::whereIn('transaction_journals.id', $ids)->expanded()->where('transaction_journals.user_id', auth()->user()->id)->get(TransactionJournal::queryFields()); if ($object->count() > 0) { return $object; } } throw new NotFoundHttpException(); }
/** * @param Carbon $start * @param Carbon $end * * @return Builder */ protected function queryJournalsWithTransactions(Carbon $start, Carbon $end) { $query = TransactionJournal::leftJoin('transactions as t_from', function (JoinClause $join) { $join->on('t_from.transaction_journal_id', '=', 'transaction_journals.id')->where('t_from.amount', '<', 0); })->leftJoin('accounts as ac_from', 't_from.account_id', '=', 'ac_from.id')->leftJoin('account_meta as acm_from', function (JoinClause $join) { $join->on('ac_from.id', '=', 'acm_from.account_id')->where('acm_from.name', '=', 'accountRole'); })->leftJoin('transactions as t_to', function (JoinClause $join) { $join->on('t_to.transaction_journal_id', '=', 'transaction_journals.id')->where('t_to.amount', '>', 0); })->leftJoin('accounts as ac_to', 't_to.account_id', '=', 'ac_to.id')->leftJoin('account_meta as acm_to', function (JoinClause $join) { $join->on('ac_to.id', '=', 'acm_to.account_id')->where('acm_to.name', '=', 'accountRole'); })->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'); $query->before($end)->after($start)->where('transaction_journals.user_id', Auth::user()->id); return $query; }
/** * @param TransactionJournal $journal * * @return bool */ public function triggered(TransactionJournal $journal) : bool { $name = ''; /** @var Account $account */ foreach (TransactionJournal::sourceAccountList($journal) as $account) { $name .= strtolower($account->name); } $search = strtolower($this->triggerValue); if ($name === $search) { Log::debug(sprintf('RuleTrigger FromAccountIs for journal #%d: "%s" is "%s", return true.', $journal->id, $name, $search)); return true; } Log::debug(sprintf('RuleTrigger FromAccountIs for journal #%d: "%s" is NOT "%s", return false.', $journal->id, $name, $search)); return false; }
/** * @param TransactionJournal $journal * * @return bool */ public function triggered(TransactionJournal $journal) : bool { $name = ''; /** @var Account $account */ foreach (TransactionJournal::sourceAccountList($journal) as $account) { $name .= strtolower($account->name); } $search = strtolower($this->triggerValue); $part = substr($name, 0, strlen($search)); if ($part === $search) { Log::debug(sprintf('RuleTrigger FromAccountStarts for journal #%d: "%s" starts with "%s", return true.', $journal->id, $name, $search)); return true; } Log::debug(sprintf('RuleTrigger FromAccountStarts for journal #%d: "%s" does not start with "%s", return false.', $journal->id, $name, $search)); return false; }
/** * */ protected function registerDeleteEvents() { TransactionJournal::deleted(function (TransactionJournal $journal) { /** @var Transaction $transaction */ foreach ($journal->transactions()->get() as $transaction) { $transaction->delete(); } }); Account::deleted(function (Account $account) { /** @var Transaction $transaction */ foreach ($account->transactions()->get() as $transaction) { $journal = $transaction->transactionJournal()->first(); $journal->delete(); } }); }
/** * @param TransactionJournal $journal * * @return bool */ public function triggered(TransactionJournal $journal) : bool { $fromAccountName = ''; /** @var Account $account */ foreach (TransactionJournal::sourceAccountList($journal) as $account) { $fromAccountName .= strtolower($account->name); } $search = strtolower($this->triggerValue); $strpos = strpos($fromAccountName, $search); if (!($strpos === false)) { Log::debug(sprintf('RuleTrigger FromAccountContains for journal #%d: "%s" contains "%s", return true.', $journal->id, $fromAccountName, $search)); return true; } Log::debug(sprintf('RuleTrigger FromAccountContains for journal #%d: "%s" does not contain "%s", return false.', $journal->id, $fromAccountName, $search)); return false; }
/** * @param Bill $bill * @param Collection $entries * * @return array */ public function single(Bill $bill, Collection $entries) : array { $format = (string) trans('config.month'); $data = ['count' => 3, 'labels' => [], 'datasets' => []]; $minAmount = []; $maxAmount = []; $actualAmount = []; /** @var TransactionJournal $entry */ foreach ($entries as $entry) { $data['labels'][] = $entry->date->formatLocalized($format); $minAmount[] = round($bill->amount_min, 2); $maxAmount[] = round($bill->amount_max, 2); // journalAmount has been collected in BillRepository::getJournals $actualAmount[] = round(TransactionJournal::amountPositive($entry), 2); } $data['datasets'][] = ['type' => 'bar', 'label' => trans('firefly.minAmount'), 'data' => $minAmount]; $data['datasets'][] = ['type' => 'line', 'label' => trans('firefly.billEntry'), 'data' => $actualAmount]; $data['datasets'][] = ['type' => 'bar', 'label' => trans('firefly.maxAmount'), 'data' => $maxAmount]; $data['count'] = count($data['datasets']); return $data; }
/** * @param TransactionJournal $journal * * @return Entry */ public static function fromJournal(TransactionJournal $journal) { $entry = new self(); $entry->description = $journal->description; $entry->date = $journal->date->format('Y-m-d'); $entry->amount = TransactionJournal::amount($journal); $entry->budget = new EntryBudget($journal->budgets->first()); $entry->category = new EntryCategory($journal->categories->first()); $entry->bill = new EntryBill($journal->bill); $sources = TransactionJournal::sourceAccountList($journal); $destinations = TransactionJournal::destinationAccountList($journal); $entry->sourceAccount = new EntryAccount($sources->first()); $entry->destinationAccount = new EntryAccount($destinations->first()); foreach ($sources as $source) { $entry->sourceAccounts->push(new EntryAccount($source)); } foreach ($destinations as $destination) { $entry->destinationAccounts->push(new EntryAccount($destination)); } return $entry; }
/** * @param TransactionJournal $entry */ public function addOrCreateIncome(TransactionJournal $entry) { // add each account individually: $sources = TransactionJournal::sourceTransactionList($entry); foreach ($sources as $transaction) { $amount = strval($transaction->amount); $account = $transaction->account; $amount = bcmul($amount, '-1'); $object = new stdClass(); $object->amount = $amount; $object->name = $account->name; $object->count = 1; $object->id = $account->id; // overrule some properties: if ($this->incomes->has($account->id)) { $object = $this->incomes->get($account->id); $object->amount = bcadd($object->amount, $amount); $object->count++; } $this->incomes->put($account->id, $object); } }
/** * */ protected function registerDeleteEvents() { Account::deleted(function (Account $account) { Log::debug('Now trigger account delete response #' . $account->id); /** @var Transaction $transaction */ foreach ($account->transactions()->get() as $transaction) { Log::debug('Now at transaction #' . $transaction->id); $journal = $transaction->transactionJournal()->first(); if (!is_null($journal)) { Log::debug('Call for deletion of journal #' . $journal->id); $journal->delete(); } } }); TransactionJournal::deleted(function (TransactionJournal $journal) { Log::debug('Now triggered journal delete response #' . $journal->id); /** @var Transaction $transaction */ foreach ($journal->transactions()->get() as $transaction) { Log::debug('Will now delete transaction #' . $transaction->id); $transaction->delete(); } }); }
/** * * @param \FireflyIII\Models\TransactionJournal $journal * @param bool $coloured * * @return string */ public function formatJournal(TransactionJournal $journal, bool $coloured = true) : string { $locale = setlocale(LC_MONETARY, 0); $float = round(TransactionJournal::amount($journal), 2); $formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); $currencyCode = $journal->transaction_currency_code ?? $journal->transactionCurrency->code; $result = $formatter->formatCurrency($float, $currencyCode); if ($coloured === true && $float === 0.0) { return '<span style="color:#999">' . $result . '</span>'; // always grey. } if (!$coloured) { return $result; } if (!$journal->isTransfer()) { if ($float > 0) { return '<span class="text-success">' . $result . '</span>'; } return '<span class="text-danger">' . $result . '</span>'; } else { return '<span class="text-info">' . $result . '</span>'; } }
/** * @param TransactionJournal $entry */ public function addOrCreateExpense(TransactionJournal $entry) { // add each account individually: $destinations = TransactionJournal::destinationTransactionList($entry); foreach ($destinations as $transaction) { $amount = strval($transaction->amount); $account = $transaction->account; if (bccomp('0', $amount) === -1) { $amount = bcmul($amount, '-1'); } $object = new stdClass(); $object->amount = $amount; $object->name = $account->name; $object->count = 1; $object->id = $account->id; // overrule some properties: if ($this->expenses->has($account->id)) { $object = $this->expenses->get($account->id); $object->amount = bcadd($object->amount, $amount); $object->count++; } $this->expenses->put($account->id, $object); } }
/** * @param TransactionJournal $journal * * @return bool */ public function triggered(TransactionJournal $journal) : bool { $toAccountName = ''; /** @var Account $account */ foreach (TransactionJournal::destinationAccountList($journal) as $account) { $toAccountName .= strtolower($account->name); } $toAccountNameLength = strlen($toAccountName); $search = strtolower($this->triggerValue); $searchLength = strlen($search); // if the string to search for is longer than the account name, // shorten the search string. if ($searchLength > $toAccountNameLength) { $search = substr($search, $toAccountNameLength * -1); $searchLength = strlen($search); } $part = substr($toAccountName, $searchLength * -1); if ($part === $search) { Log::debug(sprintf('RuleTrigger ToAccountEnds for journal #%d: "%s" ends with "%s", return true.', $journal->id, $toAccountName, $search)); return true; } Log::debug(sprintf('RuleTrigger ToAccountEnds for journal #%d: "%s" does not end with "%s", return false.', $journal->id, $toAccountName, $search)); return false; }
/** * @param array $words * * @return Collection */ public function searchTransactions(array $words) : Collection { // decrypted transaction journals: $decrypted = auth()->user()->transactionJournals()->expanded()->where('transaction_journals.encrypted', 0)->where(function (EloquentBuilder $q) use($words) { foreach ($words as $word) { $q->orWhere('transaction_journals.description', 'LIKE', '%' . e($word) . '%'); } })->get(TransactionJournal::queryFields()); // encrypted $all = auth()->user()->transactionJournals()->expanded()->where('transaction_journals.encrypted', 1)->get(TransactionJournal::queryFields()); $set = $all->filter(function (TransactionJournal $journal) use($words) { foreach ($words as $word) { $haystack = strtolower($journal->description); $word = strtolower($word); if (!(strpos($haystack, $word) === false)) { return $journal; } } return null; }); $filtered = $set->merge($decrypted); $filtered = $filtered->sortBy(function (TransactionJournal $journal) { return intval($journal->date->format('U')); }); $filtered = $filtered->reverse(); return $filtered; }
/** * @param Bill $bill * @param TransactionJournal $journal * * @return boolean|null */ public function scan(Bill $bill, TransactionJournal $journal) { $matches = explode(',', $bill->match); $description = strtolower($journal->description) . ' ' . strtolower($journal->destination_account->name); $wordMatch = $this->doWordMatch($matches, $description); $amountMatch = $this->doAmountMatch($journal->amount_positive, $bill->amount_min, $bill->amount_max); Log::debug('Journal #' . $journal->id . ' has description "' . $description . '"'); /* * If both, update! */ if ($wordMatch && $amountMatch) { $journal->bill()->associate($bill); $journal->save(); return true; } else { Log::debug('Wordmatch: ' . ($wordMatch ? 'true' : 'false') . ' AmountMatch: ' . ($amountMatch ? 'true' : 'false')); } if ($bill->id == $journal->bill_id) { // if no match, but bill used to match, remove it: $journal->bill_id = null; $journal->save(); return true; } return false; }
/** * @param Carbon $date * * @return TransactionJournal */ protected function createSavings(Carbon $date) { $date = new Carbon($date->format('Y-m') . '-24'); // paid on 24th. $toAccount = $this->findAccount('Savings'); $fromAccount = $this->findAccount('MyBank Checking Account'); $category = Category::firstOrCreateEncrypted(['name' => 'Money management', 'user_id' => $this->user->id]); // create journal: $journal = TransactionJournal::create(['user_id' => $this->user->id, 'transaction_type_id' => 3, 'transaction_currency_id' => 1, 'description' => 'Save money', 'completed' => 1, 'date' => $date]); Transaction::create(['account_id' => $fromAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => -150]); Transaction::create(['account_id' => $toAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => 150]); $journal->categories()->save($category); return $journal; }