/**
  * 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'));
 }