/**
  * 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;
 }
예제 #2
0
 /**
  * @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;
 }
예제 #5
0
 /**
  * @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;
 }
예제 #8
0
 /**
  * @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;
 }
예제 #9
0
 /**
  * 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;
 }
예제 #10
0
 /**
  * @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;
 }
예제 #11
0
 /**
  * @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);
 }
예제 #13
0
 /**
  * @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'));
 }