/** * 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 { $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 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 Carbon $start * @param Carbon $end * @param Collection $accounts * * @return View */ private function auditReport(Carbon $start, Carbon $end, Collection $accounts) { /** @var ARI $repos */ $repos = app(ARI::class); $auditData = []; $dayBefore = clone $start; $dayBefore->subDay(); /** @var Account $account */ foreach ($accounts as $account) { // balance the day before: $id = $account->id; $first = $repos->oldestJournalDate($account); $last = $repos->newestJournalDate($account); $exists = false; $journals = new Collection(); $dayBeforeBalance = Steam::balance($account, $dayBefore); /* * Is there even activity on this account between the requested dates? */ if ($start->between($first, $last) || $end->between($first, $last)) { $exists = true; $journals = $repos->journalsInPeriod(new Collection([$account]), [], $start, $end); } /* * Reverse set, get balances. */ $journals = $journals->reverse(); $startBalance = $dayBeforeBalance; /** @var TransactionJournal $journal */ foreach ($journals as $journal) { $journal->before = $startBalance; $transactionAmount = $journal->source_amount; // get currently relevant transaction: $destinations = TransactionJournal::destinationAccountList($journal)->pluck('id')->toArray(); if (in_array($account->id, $destinations)) { $transactionAmount = TransactionJournal::amountPositive($journal); } $newBalance = bcadd($startBalance, $transactionAmount); $journal->after = $newBalance; $startBalance = $newBalance; } /* * Reverse set again. */ $auditData[$id]['journals'] = $journals->reverse(); $auditData[$id]['exists'] = $exists; $auditData[$id]['end'] = $end->formatLocalized(strval(trans('config.month_and_day'))); $auditData[$id]['endBalance'] = Steam::balance($account, $end); $auditData[$id]['dayBefore'] = $dayBefore->formatLocalized(strval(trans('config.month_and_day'))); $auditData[$id]['dayBeforeBalance'] = $dayBeforeBalance; } $reportType = 'audit'; $accountIds = join(',', $accounts->pluck('id')->toArray()); $hideable = ['buttons', 'icon', 'description', 'balance_before', 'amount', 'balance_after', 'date', 'interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date', 'from', 'to', 'budget', 'category', 'bill', 'internal_reference', 'notes', 'create_date', 'update_date']; $defaultShow = ['icon', 'description', 'balance_before', 'amount', 'balance_after', 'date', 'to']; return view('reports.audit.report', compact('start', 'end', 'reportType', 'accountIds', 'accounts', 'auditData', 'hideable', 'defaultShow')); }
/** * @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; }
/** * @param Request $request * @param TransactionJournal $journal * * @return array */ private function arrayFromJournal(Request $request, TransactionJournal $journal) : array { $sourceAccounts = TransactionJournal::sourceAccountList($journal); $destinationAccounts = TransactionJournal::destinationAccountList($journal); $array = ['journal_description' => $request->old('journal_description', $journal->description), 'journal_amount' => TransactionJournal::amountPositive($journal), 'sourceAccounts' => $sourceAccounts, 'journal_source_account_id' => $sourceAccounts->first()->id, 'journal_source_account_name' => $sourceAccounts->first()->name, 'journal_destination_account_id' => $destinationAccounts->first()->id, 'transaction_currency_id' => $request->old('transaction_currency_id', $journal->transaction_currency_id), 'destinationAccounts' => $destinationAccounts, 'what' => strtolower(TransactionJournal::transactionTypeStr($journal)), 'date' => $request->old('date', $journal->date), 'interest_date' => $request->old('interest_date', $journal->interest_date), 'book_date' => $request->old('book_date', $journal->book_date), 'process_date' => $request->old('process_date', $journal->process_date), 'description' => [], 'source_account_id' => [], 'source_account_name' => [], 'destination_account_id' => [], 'destination_account_name' => [], 'amount' => [], 'budget_id' => [], 'category' => []]; // number of transactions present in old input: $previousCount = count($request->old('description')); if ($previousCount === 0) { // build from scratch $transactions = $this->transactionsFromJournal($request, $journal); $array['description'] = $transactions['description']; $array['source_account_id'] = $transactions['source_account_id']; $array['source_account_name'] = $transactions['source_account_name']; $array['destination_account_id'] = $transactions['destination_account_id']; $array['destination_account_name'] = $transactions['destination_account_name']; $array['amount'] = $transactions['amount']; $array['budget_id'] = $transactions['budget_id']; $array['category'] = $transactions['category']; return $array; } $index = 0; while ($index < $previousCount) { $description = $request->old('description')[$index] ?? ''; $destinationId = $request->old('destination_account_id')[$index] ?? 0; $destinationName = $request->old('destination_account_name')[$index] ?? ''; $sourceId = $request->old('source_account_id')[$index] ?? 0; $sourceName = $request->old('source_account_name')[$index] ?? ''; $amount = $request->old('amount')[$index] ?? ''; $budgetId = $request->old('budget_id')[$index] ?? 0; $categoryName = $request->old('category')[$index] ?? ''; // any transfer not from the source: $array['description'][] = $description; $array['source_account_id'][] = $sourceId; $array['source_account_name'][] = $sourceName; $array['destination_account_id'][] = $destinationId; $array['destination_account_name'][] = $destinationName; $array['amount'][] = $amount; $array['budget_id'][] = intval($budgetId); $array['category'][] = $categoryName; $index++; } return $array; }
/** * @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 BillRepositoryInterface $repository * * @return View */ public function index(BillRepositoryInterface $repository) { /** @var Carbon $start */ $start = session('start'); /** @var Carbon $end */ $end = session('end'); $bills = $repository->getBills(); $bills->each(function (Bill $bill) use($repository, $start, $end) { $bill->nextExpectedMatch = $repository->nextExpectedMatch($bill); $bill->lastFoundMatch = $repository->lastFoundMatch($bill); $journals = $repository->getJournalsInRange($bill, $start, $end); // loop journals, find average: $average = '0'; $count = $journals->count(); if ($count > 0) { $sum = '0'; foreach ($journals as $journal) { $sum = bcadd($sum, TransactionJournal::amountPositive($journal)); } $average = bcdiv($sum, strval($count)); } $bill->lastPaidAmount = $average; $bill->paidInPeriod = $start <= $bill->lastFoundMatch && $end >= $bill->lastFoundMatch; }); return view('bills.index', compact('bills')); }
/** * @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')); }