/** * 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 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 { $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 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; }
/** * @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 $journal * * @return bool */ public function triggered(TransactionJournal $journal) : bool { $name = ''; /** @var Account $account */ foreach (TransactionJournal::sourceAccountList($journal) as $account) { $name .= strtolower($account->name); } $nameLength = strlen($name); $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 > $nameLength) { $search = substr($search, $nameLength * -1); $searchLength = strlen($search); } $part = substr($name, $searchLength * -1); if ($part === $search) { Log::debug(sprintf('RuleTrigger FromAccountEnds for journal #%d: "%s" ends with "%s", return true.', $journal->id, $name, $search)); return true; } Log::debug(sprintf('RuleTrigger FromAccountEnds for journal #%d: "%s" does not end with "%s", return false.', $journal->id, $name, $search)); return false; }
/** * Returns all the incomes that went to the given asset account. * * @param $attributes * * @return string * @throws FireflyException */ private function incomeEntry(array $attributes) : string { /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $crud = app('FireflyIII\\Crud\\Account\\AccountCrudInterface'); $account = $crud->find(intval($attributes['accountId'])); $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; $journals = $repository->journalsInPeriod(new Collection([$account]), $types, $attributes['startDate'], $attributes['endDate']); $destinations = $attributes['accounts']->pluck('id')->toArray(); // filter for transfers and withdrawals FROM the given $account $journals = $journals->filter(function (TransactionJournal $journal) use($account, $destinations) { $currentSources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray(); $currentDest = TransactionJournal::destinationAccountList($journal)->pluck('id')->toArray(); if (!empty(array_intersect([$account->id], $currentSources)) && !empty(array_intersect($destinations, $currentDest))) { return true; } return false; }); $view = view('popup.report.income-entry', compact('journals', 'account'))->render(); return $view; }
/** * @param Bill $bill * @param TransactionJournal $journal * * @return bool */ public function scan(Bill $bill, TransactionJournal $journal) : bool { /* * Can only support withdrawals. */ if (false === $journal->isWithdrawal()) { return false; } $destinationAccounts = TransactionJournal::destinationAccountList($journal); $sourceAccounts = TransactionJournal::sourceAccountList($journal); $matches = explode(',', $bill->match); $description = strtolower($journal->description) . ' '; $description .= strtolower(join(' ', $destinationAccounts->pluck('name')->toArray())); $description .= strtolower(join(' ', $sourceAccounts->pluck('name')->toArray())); $wordMatch = $this->doWordMatch($matches, $description); $amountMatch = $this->doAmountMatch(TransactionJournal::amountPositive($journal), $bill->amount_min, $bill->amount_max); /* * If both, update! */ if ($wordMatch && $amountMatch) { $journal->bill()->associate($bill); $journal->save(); return true; } 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; }
/** * The incoming journal ($journal)'s accounts (source accounts for a withdrawal, destination accounts for a deposit) * must match the already existing transaction's accounts exactly. * * @param TransactionJournal $journal * @param Tag $tag * * * @return bool */ protected function matchAll(TransactionJournal $journal, Tag $tag) : bool { $checkSources = join(',', TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray()); $checkDestinations = join(',', TransactionJournal::destinationAccountList($journal)->pluck('id')->toArray()); $match = true; /** @var TransactionJournal $check */ foreach ($tag->transactionjournals as $check) { // $checkAccount is the source_account for a withdrawal // $checkAccount is the destination_account for a deposit $thisSources = join(',', TransactionJournal::sourceAccountList($check)->pluck('id')->toArray()); $thisDestinations = join(',', TransactionJournal::destinationAccountList($check)->pluck('id')->toArray()); if ($check->isWithdrawal() && $thisSources !== $checkSources) { $match = false; } if ($check->isDeposit() && $thisDestinations !== $checkDestinations) { $match = false; } } if ($match) { $journal->tags()->save($tag); $journal->save(); return true; } return false; }
/** * @param Request $request * @param TransactionJournal $journal * * @return array */ private function transactionsFromJournal(Request $request, TransactionJournal $journal) : array { /** @var Collection $transactions */ $transactions = $journal->transactions()->get(); /* * Splitted journals always have ONE source OR ONE destination. * Withdrawals have ONE source (asset account) * Deposits have ONE destination (asset account) * Transfers have ONE of both (asset account) */ /** @var Account $singular */ $singular = TransactionJournal::sourceAccountList($journal)->first(); if ($journal->transactionType->type == TransactionType::DEPOSIT) { /** @var Account $singular */ $singular = TransactionJournal::destinationAccountList($journal)->first(); } /* * Loop all transactions. Collect info ONLY from the transaction that is NOT related to * the singular account. */ $index = 0; $return = ['description' => [], 'source_account_id' => [], 'source_account_name' => [], 'destination_account_id' => [], 'destination_account_name' => [], 'amount' => [], 'budget_id' => [], 'category' => []]; Log::debug('now at transactionsFromJournal'); /** * @var int $current * @var Transaction $transaction */ foreach ($transactions as $current => $transaction) { $budget = $transaction->budgets()->first(); $category = $transaction->categories()->first(); $budgetId = 0; $categoryName = ''; if (!is_null($budget)) { $budgetId = $budget->id; } if (!is_null($category)) { $categoryName = $category->name; } $budgetId = $request->old('budget_id')[$index] ?? $budgetId; $categoryName = $request->old('category')[$index] ?? $categoryName; $amount = $request->old('amount')[$index] ?? $transaction->amount; $description = $request->old('description')[$index] ?? $transaction->description; $destinationName = $request->old('destination_account_name')[$index] ?? $transaction->account->name; $sourceName = $request->old('source_account_name')[$index] ?? $transaction->account->name; $amount = bccomp($amount, '0') === -1 ? bcmul($amount, '-1') : $amount; if ($transaction->account_id !== $singular->id) { $return['description'][] = $description; $return['destination_account_id'][] = $transaction->account_id; $return['destination_account_name'][] = $destinationName; $return['source_account_name'][] = $sourceName; $return['amount'][] = $amount; $return['budget_id'][] = intval($budgetId); $return['category'][] = $categoryName; // only add one when "valid" transaction $index++; } } return $return; }
/** * @return Twig_SimpleFunction */ public function getSourceAccount() : Twig_SimpleFunction { return new Twig_SimpleFunction('sourceAccount', function (TransactionJournal $journal) : string { $cache = new CacheProperties(); $cache->addProperty($journal->id); $cache->addProperty('transaction-journal'); $cache->addProperty('source-account-string'); if ($cache->has()) { return $cache->get(); } $list = TransactionJournal::sourceAccountList($journal); $array = []; /** @var Account $entry */ foreach ($list as $entry) { if ($entry->accountType->type == 'Cash account') { $array[] = '<span class="text-success">(cash)</span>'; continue; } $array[] = '<a title="' . e($entry->name) . '" href="' . route('accounts.show', $entry->id) . '">' . e($entry->name) . '</a>'; } $array = array_unique($array); $result = join(', ', $array); $cache->store($result); return $result; }); }
/** * @param TransactionJournal $journal * * @return mixed */ public function edit(TransactionJournal $journal) { $count = $journal->transactions()->count(); if ($count > 2) { return redirect(route('split.journal.edit', [$journal->id])); } // code to get list data: $budgetRepository = app('FireflyIII\\Repositories\\Budget\\BudgetRepositoryInterface'); $piggyRepository = app('FireflyIII\\Repositories\\PiggyBank\\PiggyBankRepositoryInterface'); $crud = app('FireflyIII\\Crud\\Account\\AccountCrudInterface'); $assetAccounts = ExpandedForm::makeSelectList($crud->getAccountsByType(['Default account', 'Asset account'])); $budgetList = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); $piggyBankList = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanks()); // view related code $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); $what = strtolower(TransactionJournal::transactionTypeStr($journal)); // journal related code $sourceAccounts = TransactionJournal::sourceAccountList($journal); $destinationAccounts = TransactionJournal::destinationAccountList($journal); $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; $preFilled = ['date' => TransactionJournal::dateAsString($journal), 'interest_date' => TransactionJournal::dateAsString($journal, 'interest_date'), 'book_date' => TransactionJournal::dateAsString($journal, 'book_date'), 'process_date' => TransactionJournal::dateAsString($journal, 'process_date'), 'category' => TransactionJournal::categoryAsString($journal), 'budget_id' => TransactionJournal::budgetId($journal), 'piggy_bank_id' => TransactionJournal::piggyBankId($journal), 'tags' => join(',', $journal->tags->pluck('tag')->toArray()), 'source_account_id' => $sourceAccounts->first()->id, 'source_account_name' => $sourceAccounts->first()->name, 'destination_account_id' => $destinationAccounts->first()->id, 'destination_account_name' => $destinationAccounts->first()->name, 'amount' => TransactionJournal::amountPositive($journal), 'due_date' => TransactionJournal::dateAsString($journal, 'due_date'), 'payment_date' => TransactionJournal::dateAsString($journal, 'payment_date'), 'invoice_date' => TransactionJournal::dateAsString($journal, 'invoice_date'), 'interal_reference' => $journal->getMeta('internal_reference'), 'notes' => $journal->getMeta('notes')]; if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) { $preFilled['destination_account_name'] = ''; } if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type == AccountType::CASH) { $preFilled['source_account_name'] = ''; } Session::flash('preFilled', $preFilled); Session::flash('gaEventCategory', 'transactions'); Session::flash('gaEventAction', 'edit-' . $what); // put previous url in session if not redirect from store (not "return_to_edit"). if (session('transactions.edit.fromUpdate') !== true) { Session::put('transactions.edit.url', URL::previous()); } Session::forget('transactions.edit.fromUpdate'); return view('transactions.edit', compact('journal', 'optionalFields', 'assetAccounts', 'what', 'budgetList', 'piggyBankList', 'subTitle'))->with('data', $preFilled); }
/** * @param Collection $journals * * @return View */ public function massEdit(Collection $journals) { $subTitle = trans('firefly.mass_edit_journals'); $crud = app('FireflyIII\\Crud\\Account\\AccountCrudInterface'); $accountList = ExpandedForm::makeSelectList($crud->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])); // skip transactions that have multiple destinations // or multiple sources: $filtered = new Collection(); $messages = []; /** * @var int $index * @var TransactionJournal $journal */ foreach ($journals as $index => $journal) { $sources = TransactionJournal::sourceAccountList($journal); $destinations = TransactionJournal::destinationAccountList($journal); if ($sources->count() > 1) { $messages[] = trans('firefly.cannot_edit_multiple_source', ['description' => $journal->description, 'id' => $journal->id]); continue; } if ($destinations->count() > 1) { $messages[] = trans('firefly.cannot_edit_multiple_dest', ['description' => $journal->description, 'id' => $journal->id]); continue; } $filtered->push($journal); } if (count($messages)) { Session::flash('info', $messages); } // put previous url in session Session::put('transactions.mass-edit.url', URL::previous()); Session::flash('gaEventCategory', 'transactions'); Session::flash('gaEventAction', 'mass-edit'); // set some values to be used in the edit routine: $filtered->each(function (TransactionJournal $journal) { $journal->amount = TransactionJournal::amountPositive($journal); $sources = TransactionJournal::sourceAccountList($journal); $destinations = TransactionJournal::destinationAccountList($journal); $journal->transaction_count = $journal->transactions()->count(); if (!is_null($sources->first())) { $journal->source_account_id = $sources->first()->id; $journal->source_account_name = $sources->first()->name; } if (!is_null($destinations->first())) { $journal->destination_account_id = $destinations->first()->id; $journal->destination_account_name = $destinations->first()->name; } }); if ($filtered->count() === 0) { Session::flash('error', trans('firefly.no_edit_multiple_left')); } $journals = $filtered; return view('transactions.mass-edit', compact('journals', 'subTitle', 'accountList')); }