Expanded the 'save transaction' routine and cleaned it up. Still some work to do though.

This commit is contained in:
James Cole 2014-09-20 08:39:24 +02:00
parent c39c59fff5
commit 6a26408552
12 changed files with 592 additions and 556 deletions

View File

@ -2,6 +2,8 @@
use Carbon\Carbon;
use Firefly\Exception\FireflyException;
use Firefly\Helper\Controllers\TransactionInterface as TI;
use Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface as TJRI;
/**
@ -14,13 +16,16 @@ class TransactionController extends BaseController
{
protected $_repository;
protected $_helper;
/**
* @param TJRI $repository
* @param TI $helper
*/
public function __construct(TJRI $repository)
public function __construct(TJRI $repository, TI $helper)
{
$this->_repository = $repository;
$this->_helper = $helper;
View::share('title', 'Transactions');
View::share('mainTitleIcon', 'fa-repeat');
}
@ -55,7 +60,7 @@ class TransactionController extends BaseController
return View::make('transactions.create')->with('accounts', $assetAccounts)->with('budgets', $budgets)->with(
'what', $what
)->with('piggies', $piggies)->with('subTitle', 'Add a new ' . $what)->with('title', 'Transactions');
)->with('piggies', $piggies)->with('subTitle', 'Add a new ' . $what);
}
/**
@ -272,34 +277,37 @@ class TransactionController extends BaseController
/**
* @param $what
*
* @return \Illuminate\Http\RedirectResponse
* @return $this|\Illuminate\Http\RedirectResponse
* @throws FireflyException
*/
public function store($what)
{
/*
* Collect data to process:
*/
$data = Input::except(['_token']);
$data['what'] = $what;
$journal = $this->_repository->store($what, Input::all());
if ($journal->errors()->count() > 0) {
Session::flash('error', 'Could not save transaction: ' . $journal->errors()->first() . '!');
return Redirect::route('transactions.create', [$what])->withInput()->withErrors($journal->errors());
}
switch (Input::get('post_submit_action')) {
case 'store':
/*
* Try to store:
*/
$messageBag = $this->_helper->store($data);
if ($journal->validate() && !is_null($journal->id)) {
Session::flash('success', 'Transaction "' . $journal->description . '" saved!');
/*
* Failure!
*/
if($messageBag->count() > 0) {
Session::flash('error', 'Could not save transaction: ' . $messageBag->first());
return Redirect::route('transactions.create', [$what])->withInput()->withErrors($messageBag);
}
// if reminder present, deactivate it:
if (Input::get('reminder')) {
/** @var \Firefly\Storage\Reminder\ReminderRepositoryInterface $reminders */
$reminders = App::make('Firefly\Storage\Reminder\ReminderRepositoryInterface');
$reminder = $reminders->find(Input::get('reminder'));
$reminders->deactivate($reminder);
}
/*
* Success!
*/
Session::flash('success', 'Transaction "' . e(Input::get('description')) . '" saved!');
// trigger the creation for recurring transactions.
Event::fire('journals.store', [$journal]);
if (Input::get('create') == '1') {
return Redirect::route('transactions.create', [$what])->withInput();
} else {
switch ($what) {
case 'withdrawal':
return Redirect::route('transactions.expenses');
@ -312,14 +320,11 @@ class TransactionController extends BaseController
break;
}
}
} else {
Session::flash('error', 'Could not save transaction: ' . $journal->errors()->first() . '!');
return Redirect::route('transactions.create', [$what])->withInput()->withErrors($journal->errors());
break;
default:
throw new FireflyException('Method ' . Input::get('post_submit_action') . ' not implemented yet.');
break;
}
}
/**

View File

@ -0,0 +1,119 @@
<?php
namespace Firefly\Helper\Controllers;
use Illuminate\Support\MessageBag;
/**
* Class Transaction
*
* @package Firefly\Helper\Controllers
*/
class Transaction implements TransactionInterface
{
/**
* Store a full transaction journal and associated stuff
*
* @param array $data
*
* @return MessageBag
*
* @SuppressWarnings(PHPMD.ShortVariable)
*/
public function store(array $data)
{
/*
* save journal using repository
*/
/** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $journals */
$journals = \App::make('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface');
$journal = $journals->store($data);
/*
* If invalid, return the message bag:
*/
if (!$journal->validate()) {
return $journal->errors();
}
/*
* save budget using repository
*/
if (isset($data['budget_id'])) {
/** @var \Firefly\Storage\Budget\BudgetRepositoryInterface $budgets */
$budgets = \App::make('Firefly\Storage\Budget\BudgetRepositoryInterface');
$budget = $budgets->find($data['budget_id']);
}
/*
* save category using repository
*/
/** @var \Firefly\Storage\Category\CategoryRepositoryInterface $categories */
$categories = \App::make('Firefly\Storage\Category\CategoryRepositoryInterface');
$category = $categories->firstOrCreate($data['category']);
/*
* save accounts using repositories
* this depends on the kind of transaction and i've yet to fix this.
*/
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accounts */
$accounts = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
if (isset($data['account_id'])) {
$from = $accounts->findAssetAccountById($data['account_id']);
}
if (isset($data['expense_account'])) {
$to = $accounts->findExpenseAccountByName($data['expense_account']);
}
if (isset($data['revenue_account'])) {
$from = $accounts->findRevenueAccountByName($data['revenue_account']);
$to = $accounts->findAssetAccountById($data['account_id']);
}
if (isset($data['account_from_id'])) {
$from = $accounts->findAssetAccountById($data['account_from_id']);
}
if (isset($data['account_to_id'])) {
$to = $accounts->findAssetAccountById($data['account_to_id']);
}
/*
* Add a custom error when they are the same.
*/
if($to->id == $from->id) {
$bag = new MessageBag;
$bag->add('account_from_id','The account from cannot be the same as the account to.');
return $bag;
}
/*
* save transactions using repository.
*/
$one = $journals->saveTransaction($journal, $from, floatval($data['amount']) * -1);
$two = $journals->saveTransaction($journal, $to, floatval($data['amount']));
/*
* Count for $journal is zero? Then there were errors!
*/
if ($journal->transactions()->count() < 2) {
/*
* Join message bags and return them:
*/
$bag = $one->errors();
$bag->merge($two->errors());
return $bag;
}
/*
* Connect budget and category:
*/
if (isset($budget) && !is_null($budget)) {
$journal->budgets()->save($budget);
}
if (!is_null($category)) {
$journal->categories()->save($category);
}
$journal->completed = true;
$journal->save();
return $journal->errors();
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Firefly\Helper\Controllers;
use Illuminate\Support\MessageBag;
/**
* Interface TransactionInterface
*
* @package Firefly\Helper\Controllers
*/
interface TransactionInterface {
/**
* Store a full transaction journal and associated stuff
*
* @param array $data
*
* @return MessageBag
*/
public function store(array $data);
}

View File

@ -26,6 +26,12 @@ class HelperServiceProvider extends ServiceProvider
'Firefly\Helper\Controllers\ChartInterface',
'Firefly\Helper\Controllers\Chart'
);
$this->app->bind(
'Firefly\Helper\Controllers\TransactionInterface',
'Firefly\Helper\Controllers\Transaction'
);
$this->app->bind(
'Firefly\Helper\Controllers\CategoryInterface',
'Firefly\Helper\Controllers\Category'

View File

@ -96,6 +96,13 @@ interface AccountRepositoryInterface
*/
public function findExpenseAccountByName($name);
/**
* @param $name
*
* @return |Account|null
*/
public function findRevenueAccountByName($name);
/**
* @param \Account $from
* @param \Account $to

View File

@ -118,6 +118,40 @@ class EloquentAccountRepository implements AccountRepositoryInterface
return $account;
}
/**
* @param $name
*
* @return |Account|null
*/
public function findRevenueAccountByName($name)
{
// find account:
$type = $this->findAccountType('Revenue account');
$account = $this->_user->accounts()->where('name', $name)->where('account_type_id', $type->id)->first();
// find cash account as fall back:
if (is_null($account)) {
$cashType = $this->findAccountType('Cash account');
$account = $this->_user->accounts()->where('account_type_id', $cashType->id)->first();
}
// create cash account as ultimate fall back:
if (is_null($account)) {
$set = [
'name' => 'Cash account',
'user_id' => $this->_user->id,
'active' => 1,
'account_type_id' => $cashType->id
];
$account = $this->firstOrCreate($set);
}
if ($account->active == 0) {
return null;
}
return $account;
}
/**
* @param $type

View File

@ -55,7 +55,7 @@ interface CategoryRepositoryInterface
*
* @return mixed
*/
public function createOrFind($name);
public function firstOrCreate($name);
/**
* @param \User $user

View File

@ -29,7 +29,8 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
*
* @return mixed
*/
public function importUpdateTransfer(Job $job, array $payload) {
public function importUpdateTransfer(Job $job, array $payload)
{
/** @var \Firefly\Storage\Import\ImportRepositoryInterface $repository */
$repository = \App::make('Firefly\Storage\Import\ImportRepositoryInterface');
@ -57,13 +58,13 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
/*
* Prep some vars from the payload
*/
$transferId = intval($payload['data']['transfer_id']);
$componentId = intval($payload['data']['component_id']);
$transferId = intval($payload['data']['transfer_id']);
$componentId = intval($payload['data']['component_id']);
/*
* Find the import map for both:
*/
$categoryMap = $repository->findImportEntry($importMap, 'Category', $componentId);
$categoryMap = $repository->findImportEntry($importMap, 'Category', $componentId);
$transferMap = $repository->findImportEntry($importMap, 'Transfer', $transferId);
/*
@ -71,7 +72,7 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
*/
if (is_null($categoryMap) || is_null($transferMap)) {
\Log::notice('No map found in category/transfer mapper. Release.');
if(\Config::get('queue.default') == 'sync') {
if (\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
@ -93,7 +94,7 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
*/
if (is_null($category) || is_null($journal)) {
\Log::notice('Map is incorrect in category/transfer mapper. Release.');
if(\Config::get('queue.default') == 'sync') {
if (\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
@ -119,6 +120,27 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
return;
}
/**
* @param \User $user
*
* @return mixed|void
*/
public function overruleUser(\User $user)
{
$this->_user = $user;
return true;
}
/**
* @param $categoryId
*
* @return mixed
*/
public function find($categoryId)
{
return $this->_user->categories()->find($categoryId);
}
/**
* Takes a transaction/category component and updates the transaction journal to match.
*
@ -170,7 +192,7 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
*/
if (is_null($categoryMap) || is_null($transactionMap)) {
\Log::notice('No map found in category/transaction mapper. Release.');
if(\Config::get('queue.default') == 'sync') {
if (\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
@ -192,7 +214,7 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
*/
if (is_null($category) || is_null($journal)) {
\Log::notice('Map is incorrect in category/transaction mapper. Release.');
if(\Config::get('queue.default') == 'sync') {
if (\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
@ -218,27 +240,6 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
return;
}
/**
* @param \User $user
*
* @return mixed|void
*/
public function overruleUser(\User $user)
{
$this->_user = $user;
return true;
}
/**
* @param $categoryId
*
* @return mixed
*/
public function find($categoryId)
{
return $this->_user->categories()->find($categoryId);
}
/**
* @param Job $job
* @param array $payload
@ -334,18 +335,16 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
*
* @return \Category|mixed
*/
public function createOrFind($name)
public function firstOrCreate($name)
{
if (strlen($name) == 0) {
return null;
}
$category = $this->findByName($name);
if (!$category) {
return $this->store(['name' => $name]);
}
return $category;
$data = [
'name' => $name,
'user_id' => $this->_user->id,
];
return \Category::firstOrCreate($data);
}

View File

@ -52,7 +52,6 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
}
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accounts */
$accounts = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$accounts->overruleUser($user);
@ -102,7 +101,7 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
*/
if (is_null($accountTo) || is_null($accountFrom)) {
\Log::notice('No account to, or account from. Release transfer ' . $description);
if(\Config::get('queue.default') == 'sync') {
if (\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
@ -140,143 +139,58 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
return true;
}
/**
*
* We're building this thinking the money goes from A to B.
* If the amount is negative however, the money still goes
* from A to B but the balances are reversed.
*
* Aka:
*
* Amount = 200
* A loses 200 (-200). * -1
* B gains 200 (200). * 1
*
* Final balance: -200 for A, 200 for B.
*
* When the amount is negative:
*
* Amount = -200
* A gains 200 (200). * -1
* B loses 200 (-200). * 1
*
* @param \Account $from
* @param \Account $toAccount
* @param $description
* @param $amount
* @param \Carbon\Carbon $date
*
* @return \TransactionJournal
* @throws \Firefly\Exception\FireflyException
*/
public function createSimpleJournal(\Account $fromAccount, \Account $toAccount, $description, $amount, Carbon $date)
public function store(array $data)
{
/*
* Create the journal and fill relevant fields.
*/
$journal = new \TransactionJournal;
$journal->description = trim($data['description']);
$journal->date = new Carbon($data['date']);
$journal->user_id = $this->_user->id;
$journal->completed = false;
$journal->description = $description;
$journal->date = $date;
$amountFrom = $amount * -1;
$amountTo = $amount;
/*
* Find the more complex fields and fill those:
*/
$currency = \TransactionCurrency::where('code', 'EUR')->first();
$journal->transaction_currency_id = $currency->id;
$transactionType = \TransactionType::where('type', $data['what'])->first();
$journal->transaction_type_id = $transactionType->id;
if (round(floatval($amount), 2) == 0.00) {
$journal->errors()->add('amount', 'Amount must not be zero.');
return $journal;
}
// account types for both:
$toAT = $toAccount->accountType->type;
$fromAT = $fromAccount->accountType->type;
$journalType = null;
switch (true) {
case ($fromAccount->transactions()->count() == 0 && $toAccount->transactions()->count() == 0):
$journalType = \TransactionType::where('type', 'Opening balance')->first();
break;
case (in_array($fromAT, ['Default account', 'Asset account'])
&& in_array(
$toAT, ['Default account', 'Asset account']
)): // both are yours:
// determin transaction type. If both accounts are new, it's an initial balance transfer.
$journalType = \TransactionType::where('type', 'Transfer')->first();
break;
case ($amount < 0):
$journalType = \TransactionType::where('type', 'Deposit')->first();
break;
// is deposit into one of your own accounts:
case ($toAT == 'Default account' || $toAT == 'Asset account'):
$journalType = \TransactionType::where('type', 'Deposit')->first();
break;
// is withdrawal from one of your own accounts:
case ($fromAT == 'Default account' || $fromAT == 'Asset account'):
$journalType = \TransactionType::where('type', 'Withdrawal')->first();
break;
}
if (is_null($journalType)) {
throw new FireflyException('Could not figure out transaction type.');
}
// always the same currency:
$currency = \TransactionCurrency::where('code', 'EUR')->first();
if (is_null($currency)) {
throw new FireflyException('No currency for journal!');
}
// new journal:
$journal->transactionType()->associate($journalType);
$journal->transactionCurrency()->associate($currency);
$journal->user()->associate($this->_user);
// same account:
if ($fromAccount->id == $toAccount->id) {
$journal->errors()->add('account_to_id', 'Must be different from the "account from".');
$journal->errors()->add('account_from_id', 'Must be different from the "account to".');
return $journal;
}
if (!$journal->validate()) {
return $journal;
}
$journal->save();
// create transactions:
$fromTransaction = new \Transaction;
$fromTransaction->account()->associate($fromAccount);
$fromTransaction->transactionJournal()->associate($journal);
$fromTransaction->description = null;
$fromTransaction->amount = $amountFrom;
if (!$fromTransaction->validate()) {
throw new FireflyException('Cannot create valid transaction (from): ' . $fromTransaction->errors()
->first());
}
$fromTransaction->save();
$toTransaction = new \Transaction;
$toTransaction->account()->associate($toAccount);
$toTransaction->transactionJournal()->associate($journal);
$toTransaction->description = null;
$toTransaction->amount = $amountTo;
if (!$toTransaction->validate()) {
throw new FireflyException('Cannot create valid transaction (to): ' . $toTransaction->errors()->first()
. ': ' . print_r($toAccount->toArray(), true));
}
$toTransaction->save();
$journal->completed = true;
/*
* Validatre & save journal
*/
$journal->validate();
$journal->save();
/*
* Return regardless.
*/
return $journal;
}
/**
* @param \TransactionJournal $journal
* @param \Account $account
* @param $amount
*
* @return mixed
*/
public function saveTransaction(\TransactionJournal $journal, \Account $account, $amount)
{
$transaction = new \Transaction;
$transaction->account_id = $account->id;
$transaction->transaction_journal_id = $journal->id;
$transaction->amount = $amount;
if ($transaction->validate()) {
$transaction->save();
}
return $transaction;
}
/**
* @param Job $job
* @param array $payload
@ -304,7 +218,6 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
}
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accounts */
$accounts = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$accounts->overruleUser($user);
@ -364,7 +277,7 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
*/
if (is_null($assetAccount)) {
\Log::notice('No asset account for "' . $description . '", try again later.');
if(\Config::get('queue.default') == 'sync') {
if (\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
@ -417,20 +330,20 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
{
return $this->_user->transactionjournals()->with(
['transactions' => function ($q) {
return $q->orderBy('amount', 'ASC');
}, 'transactioncurrency', 'transactiontype', 'components', 'transactions.account',
return $q->orderBy('amount', 'ASC');
}, 'transactioncurrency', 'transactiontype', 'components', 'transactions.account',
'transactions.account.accounttype']
)
->where('id', $journalId)->first();
}
/**
*
*/
public function get()
{
}
// /**
// *
// */
// public function get()
// {
//
// }
/**
* @param $type
@ -534,129 +447,6 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito
return $result;
}
/**
* @param $what
* @param $data
*
* @return mixed|\TransactionJournal
*/
public function store($what, $data)
{
// $fromAccount and $toAccount are found
// depending on the $what
$fromAccount = null;
$toAccount = null;
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accountRepository */
$accountRepository = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$accountRepository->overruleUser($this->_user);
/** @var \Firefly\Storage\Category\CategoryRepositoryInterface $catRepository */
$catRepository = \App::make('Firefly\Storage\Category\CategoryRepositoryInterface');
$catRepository->overruleUser($this->_user);
/** @var \Firefly\Storage\Budget\BudgetRepositoryInterface $budRepository */
$budRepository = \App::make('Firefly\Storage\Budget\BudgetRepositoryInterface');
$budRepository->overruleUser($this->_user);
switch ($what) {
case 'withdrawal':
$fromAccount = $accountRepository->find(intval($data['account_id']));
$expenseAccountType = $accountRepository->findAccountType('Expense account');
$set = [
'name' => $data['expense_account'],
'account_type_id' => $expenseAccountType->id,
'user_id' => $this->_user->id,
'active' => 1];
$toAccount = $accountRepository->firstOrCreate($set);
break;
case 'deposit':
$revenueAccountType = $accountRepository->findAccountType('Revenue account');
$set = [
'name' => $data['revenue_account'],
'account_type_id' => $revenueAccountType->id,
'user_id' => $this->_user->id,
'active' => 1];
$fromAccount = $accountRepository->firstOrCreate($set);
$toAccount = $accountRepository->find(intval($data['account_id']));
break;
case 'transfer':
$fromAccount = $accountRepository->find(intval($data['account_from_id']));
$toAccount = $accountRepository->find(intval($data['account_to_id']));
break;
}
// fall back to cash if necessary:
$fromAccount = is_null($fromAccount) ? $fromAccount = $accountRepository->getCashAccount() : $fromAccount;
$toAccount = is_null($toAccount) ? $toAccount = $accountRepository->getCashAccount() : $toAccount;
// create or find category:
$category = isset($data['category']) ? $catRepository->createOrFind($data['category']) : null;
// find budget:
$budget = isset($data['budget_id']) ? $budRepository->find(intval($data['budget_id'])) : null;
// find amount & description:
$description = trim($data['description']);
$amount = floatval($data['amount']);
$date = new Carbon($data['date']);
// try to create a journal:
$transactionJournal = $this->createSimpleJournal($fromAccount, $toAccount, $description, $amount, $date);
if (!$transactionJournal->id || $transactionJournal->completed == 0) {
return $transactionJournal;
}
// here we're done and we have transactions in the journal:
// do something with the piggy bank:
if ($what == 'transfer') {
/** @var \Firefly\Storage\Piggybank\PiggybankRepositoryInterface $piggyRepository */
$piggyRepository = \App::make('Firefly\Storage\Piggybank\PiggybankRepositoryInterface');
$piggyRepository->overruleUser($this->_user);
if (isset($data['piggybank_id'])) {
/** @var \Piggybank $piggyBank */
$piggyBank = $piggyRepository->find(intval($data['piggybank_id']));
if ($piggyBank) {
// one of the two transactions may be connected to this piggy bank.
$connected = false;
foreach ($transactionJournal->transactions()->get() as $transaction) {
if ($transaction->account_id == $piggyBank->account_id) {
$connected = true;
$transaction->piggybank()->associate($piggyBank);
$transaction->save();
\Event::fire(
'piggybanks.createRelatedTransfer', [$piggyBank, $transactionJournal, $transaction]
);
break;
}
}
if ($connected === false) {
\Session::flash(
'warning', 'Piggy bank "' . e($piggyBank->name)
. '" is not set to draw money from any of the accounts in this transfer'
);
}
}
}
}
// attach:
if (!is_null($budget)) {
$transactionJournal->budgets()->save($budget);
}
if (!is_null($category)) {
$transactionJournal->categories()->save($category);
}
return $transactionJournal;
}
/**
* @param \TransactionJournal $journal
* @param $data

View File

@ -28,21 +28,21 @@ interface TransactionJournalRepositoryInterface
*/
public function importTransfer(Job $job, array $payload);
/**
* @param \Account $from
* @param \Account $toAccount
* @param $description
* @param $amount
* @param Carbon $date
*
* @return mixed
*/
public function createSimpleJournal(\Account $from, \Account $toAccount, $description, $amount, Carbon $date);
// /**
// * @param \Account $from
// * @param \Account $toAccount
// * @param $description
// * @param $amount
// * @param Carbon $date
// *
// * @return mixed
// */
// public function createSimpleJournal(\Account $from, \Account $toAccount, $description, $amount, Carbon $date);
/**
* @return mixed
*/
public function get();
// /**
// * @return mixed
// */
// public function get();
/**
@ -53,12 +53,22 @@ interface TransactionJournalRepositoryInterface
public function overruleUser(\User $user);
/**
* @param $what
* Store a new transaction journal.
*
* @param $data
*
* @return \TransactionJournal|null
*/
public function store(array $data);
/**
* @param \TransactionJournal $journal
* @param \Account $account
* @param $amount
*
* @return mixed
*/
public function store($what, $data);
public function saveTransaction(\TransactionJournal $journal, \Account $account, $amount);
/**
* @param \TransactionJournal $journal

View File

@ -27,11 +27,13 @@ class Component extends SingleTableInheritanceEntity
public static $rules
= [
'user_id' => 'exists:users,id|required',
'name' => ['required', 'between:1,100', 'alphabasic'],
'name' => ['required', 'between:1,100','min:1', 'alphabasic'],
'class' => 'required',
];
protected $table = 'components';
protected $subclassField = 'class';
protected $fillable = ['name','user_id'];
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany

View File

@ -1,235 +1,277 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-12">
<p class="text-info">
@if($what == 'withdrawal')
Some text about moving from asset accounts to expense accounts
@endif
@if($what == 'deposit')
A deposit is when you earn money, moving an amount from a beneficiary into your own account.
@endif
@if($what == 'transfer')
TRANSFER
@endif
</p>
</div>
</div>
{{Form::open(['class' => 'form-horizontal','url' => route('transactions.store',$what)])}}
{{Form::hidden('reminder',Input::get('reminder_id'))}}
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-12">
<h4>Mandatory fields</h4>
<!-- ALWAYS AVAILABLE -->
<div class="form-group">
<label for="description" class="col-sm-4 control-label">Description</label>
<div class="col-sm-8">
<input type="text" name="description" value="{{{Input::old('description') ?: Input::get('description')}}}" autocomplete="off" class="form-control" placeholder="Description" />
@if($errors->has('description'))
<p class="text-danger">{{$errors->first('description')}}</p>
@endif
<!-- panel for mandatory fields -->
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-exclamation-circle"></i> Mandatory fields
</div>
<div class="panel-body">
<!-- DESCRIPTION ALWAYS AVAILABLE -->
<div
@if($errors->has('description'))
class="form-group has-error has-feedback"
@else
class="form-group"
@endif
>
<label for="description" class="col-sm-4 control-label">Description</label>
<div class="col-sm-8">
<input
type="text" name="description"
value="{{{Input::old('description') ?: Input::get('description')}}}"
placeholder="Description"
autocomplete="off"
class="form-control" />
@if($errors->has('description'))
<p class="text-danger">{{$errors->first('description')}}</p>
@endif
</div>
</div>
<!-- SHOW ACCOUNT (FROM) ONLY FOR WITHDRAWALS AND DEPOSITS -->
@if($what == 'deposit' || $what == 'withdrawal')
<div class="form-group">
<label for="account_id" class="col-sm-4 control-label">
Asset account
</label>
<div class="col-sm-8">
{{Form::select('account_id',$accounts,Input::old('account_id') ?: Input::get('account_id'),['class' => 'form-control'])}}
@if($errors->has('account_id'))
<p class="text-danger">{{$errors->first('account_id')}}</p>
@endif
</div>
</div>
@endif
<!-- SHOW EXPENSE ACCOUNT ONLY FOR WITHDRAWALS -->
@if($what == 'withdrawal')
<div class="form-group">
<label for="expense_account" class="col-sm-4 control-label">Expense account</label>
<div class="col-sm-8">
<input type="text" name="expense_account" value="{{{Input::old('expense_account')}}}" autocomplete="off" class="form-control" placeholder="Expense account" />
@if($errors->has('expense_account'))
<p class="text-danger">{{$errors->first('expense_account')}}</p>
@else
<span class="help-block">
This field will auto-complete your existing expense accounts (where you spent the
money), but you can type freely to create new ones. If you took the money from
an ATM, you should leave this field empty.</span>
@endif
</div>
</div>
@endif
<!-- SHOW REVENUE ACCOUNT ONLY FOR DEPOSITS -->
@if($what == 'deposit')
<div class="form-group">
<label for="revenue_account" class="col-sm-4 control-label">
Revenue account
</label>
<div class="col-sm-8">
<input type="text" name="revenue_account" value="{{{Input::old('revenue_account')}}}" autocomplete="off" class="form-control" placeholder="Revenue account" />
@if($errors->has('beneficiary'))
<p class="text-danger">{{$errors->first('revenue_account')}}</p>
@else
<span class="help-block">
This field will auto-complete your existing revenue accounts (if any), but you can type freely to create new ones.</span>
@endif
</div>
</div>
@endif
<!-- ONLY SHOW FROM/TO ACCOUNT WHEN CREATING TRANSFER -->
@if($what == 'transfer')
<div class="form-group">
<label for="account_from_id" class="col-sm-4 control-label">Account from</label>
<div class="col-sm-8">
{{Form::select('account_from_id',$accounts,Input::old('account_from_id') ?: Input::get('account_from_id'),['class' => 'form-control'])}}
@if($errors->has('account_from_id'))
<p class="text-danger">{{$errors->first('account_from_id')}}</p>
@endif
</div>
</div>
<div class="form-group">
<label for="account_to_id" class="col-sm-4 control-label">Account to</label>
<div class="col-sm-8">
{{Form::select('account_to_id',$accounts,Input::old('account_to_id') ?: Input::get('account_to_id'),['class' => 'form-control'])}}
@if($errors->has('account_to_id'))
<p class="text-danger">{{$errors->first('account_to_id')}}</p>
@endif
</div>
</div>
@endif
<!-- ALWAYS SHOW AMOUNT -->
<div class="form-group">
<label for="amount" class="col-sm-4 control-label">
@if($what == 'withdrawal')
Amount spent
@endif
@if($what == 'deposit')
Amount received
@endif
@if($what == 'transfer')
Amount transferred
@endif
</label>
<div class="col-sm-8">
<input type="number" name="amount" min="0.01" value="{{Input::old('amount') ?: Input::get('amount')}}" step="any" class="form-control" />
@if($errors->has('amount'))
<p class="text-danger">{{$errors->first('amount')}}</p>
@endif
</div>
</div>
<!-- ALWAYS SHOW DATE -->
<div class="form-group">
<label for="date" class="col-sm-4 control-label">Date</label>
<div class="col-sm-8">
<input type="date" name="date" value="{{Input::old('date') ?: date('Y-m-d')}}" class="form-control" />
@if($errors->has('date'))
<p class="text-danger">{{$errors->first('date')}}</p>
@endif
</div>
</div>
</div>
</div>
<!-- SHOW ACCOUNT (FROM) ONLY FOR WITHDRAWALS AND DEPOSITS -->
@if($what == 'deposit' || $what == 'withdrawal')
<div class="form-group">
<label for="account_id" class="col-sm-4 control-label">
Asset account
</label>
<div class="col-sm-8">
{{Form::select('account_id',$accounts,Input::old('account_id') ?: Input::get('account_id'),['class' => 'form-control'])}}
@if($errors->has('account_id'))
<p class="text-danger">{{$errors->first('account_id')}}</p>
@endif
</div>
</div>
@endif
<!-- SHOW EXPENSE ACCOUNT ONLY FOR WITHDRAWALS -->
@if($what == 'withdrawal')
<div class="form-group">
<label for="expense_account" class="col-sm-4 control-label">Expense account</label>
<div class="col-sm-8">
<input type="text" name="expense_account" value="{{{Input::old('expense_account')}}}" autocomplete="off" class="form-control" placeholder="Expense account" />
@if($errors->has('expense_account'))
<p class="text-danger">{{$errors->first('expense_account')}}</p>
@else
<span class="help-block">This field will auto-complete your existing expense accounts (if any), but you can type freely to create new ones.</span>
@endif
</div>
</div>
@endif
<!-- SHOW REVENUE ACCOUNT ONLY FOR DEPOSITS -->
@if($what == 'deposit')
<div class="form-group">
<label for="revenue_account" class="col-sm-4 control-label">
Revenue account
</label>
<div class="col-sm-8">
<input type="text" name="revenue_account" value="{{{Input::old('revenue_account')}}}" autocomplete="off" class="form-control" placeholder="Revenue account" />
@if($errors->has('beneficiary'))
<p class="text-danger">{{$errors->first('revenue_account')}}</p>
@else
<span class="help-block">This field will auto-complete your existing revenue accounts (if any), but you can type freely to create new ones.</span>
@endif
</div>
</div>
@endif
<!-- ONLY SHOW FROM/TO ACCOUNT WHEN CREATING TRANSFER -->
@if($what == 'transfer')
<div class="form-group">
<label for="account_from_id" class="col-sm-4 control-label">Account from</label>
<div class="col-sm-8">
{{Form::select('account_from_id',$accounts,Input::old('account_from_id') ?: Input::get('account_from_id'),['class' => 'form-control'])}}
@if($errors->has('account_from_id'))
<p class="text-danger">{{$errors->first('account_from_id')}}</p>
@endif
</div>
</div>
<div class="form-group">
<label for="account_to_id" class="col-sm-4 control-label">Account to</label>
<div class="col-sm-8">
{{Form::select('account_to_id',$accounts,Input::old('account_to_id') ?: Input::get('account_to_id'),['class' => 'form-control'])}}
@if($errors->has('account_to_id'))
<p class="text-danger">{{$errors->first('account_to_id')}}</p>
@endif
</div>
</div>
@endif
<!-- ALWAYS SHOW AMOUNT -->
<div class="form-group">
<label for="amount" class="col-sm-4 control-label">
@if($what == 'withdrawal')
Amount spent
@endif
@if($what == 'deposit')
Amount received
@endif
@if($what == 'transfer')
Amount transferred
@endif
</label>
<div class="col-sm-8">
<input type="number" name="amount" min="0.01" value="{{Input::old('amount') ?: Input::get('amount')}}" step="any" class="form-control" />
@if($errors->has('amount'))
<p class="text-danger">{{$errors->first('amount')}}</p>
@endif
</div>
</div>
<!-- ALWAYS SHOW DATE -->
<div class="form-group">
<label for="date" class="col-sm-4 control-label">Date</label>
<div class="col-sm-8">
<input type="date" name="date" value="{{Input::old('date') ?: date('Y-m-d')}}" class="form-control" />
@if($errors->has('date'))
<p class="text-danger">{{$errors->first('date')}}</p>
@endif
</div>
</div>
<p>
<button type="submit" class="btn btn-lg btn-success">
<i class="fa fa-plus-circle"></i> Store new {{{$what}}}
</button>
</p>
</div>
<div class="col-lg-6 col-md-12 col-sm-12">
<h4>Optional fields</h4>
<!-- BUDGET ONLY WHEN CREATING A WITHDRAWAL -->
@if($what == 'withdrawal')
<div class="form-group">
<label for="budget_id" class="col-sm-4 control-label">Budget</label>
<div class="col-sm-8">
{{Form::select('budget_id',$budgets,Input::old('budget_id') ?: 0,['class' => 'form-control'])}}
@if($errors->has('budget_id'))
<p class="text-danger">{{$errors->first('budget_id')}}</p>
@else
<span class="help-block">Select one of your budgets to make this transaction a part of it.</span>
@endif
</div>
</div>
@endif
<!-- CATEGORY ALWAYS -->
<div class="form-group">
<label for="category" class="col-sm-4 control-label">Category</label>
<div class="col-sm-8">
<input type="text" name="category" value="{{Input::old('category')}}" autocomplete="off" class="form-control" placeholder="Category" />
@if($errors->has('category'))
<p class="text-danger">{{$errors->first('category')}}</p>
@else
<span class="help-block">Add more fine-grained information to this transaction by entering a category.
Like the beneficiary-field, this field will auto-complete existing categories but can also be used
to create new ones.
</span>
@endif
</div>
</div>
<!-- RELATE THIS TRANSFER TO A PIGGY BANK -->
@if($what == 'transfer' && count($piggies) > 0)
<div class="form-group">
<label for="piggybank_id" class="col-sm-4 control-label">
Piggy bank
</label>
<div class="col-sm-8">
<select name="piggybank_id" class="form-control">
<option value="0" label="(no piggy bank)">(no piggy bank)</option>
@foreach($piggies as $piggy)
@if($piggy->id == Input::old('piggybank_id') || $piggy->id == Input::get('piggybank_id'))
<option value="{{$piggy->id}}" label="{{{$piggy->name}}}" selected="selected ">{{{$piggy->name}}}</option>
@else
<option value="{{$piggy->id}}" label="{{{$piggy->name}}}">{{{$piggy->name}}}</option>
<!-- panel for mandatory fields -->
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-smile-o"></i> Optional fields
</div>
<div class="panel-body">
<!-- BUDGET ONLY WHEN CREATING A WITHDRAWAL -->
@if($what == 'withdrawal')
<div class="form-group">
<label for="budget_id" class="col-sm-4 control-label">Budget</label>
<div class="col-sm-8">
{{Form::select('budget_id',$budgets,Input::old('budget_id') ?: 0,['class' => 'form-control'])}}
@if($errors->has('budget_id'))
<p class="text-danger">{{$errors->first('budget_id')}}</p>
@else
<span class="help-block">Select one of your budgets to make this transaction a part of it.</span>
@endif
</div>
</div>
@endif
@endforeach
</select>
@if($errors->has('piggybank_id'))
<p class="text-danger">{{$errors->first('piggybank_id')}}</p>
@else
<span class="help-block">
You can directly add the amount you're transferring
to one of your piggy banks, provided they are related to the account your
transferring <em>to</em>.
</span>
@endif
</div>
</div>
@endif
<!-- CATEGORY ALWAYS -->
<div class="form-group">
<label for="category" class="col-sm-4 control-label">Category</label>
<div class="col-sm-8">
<input type="text" name="category" value="{{Input::old('category')}}" autocomplete="off" class="form-control" placeholder="Category" />
@if($errors->has('category'))
<p class="text-danger">{{$errors->first('category')}}</p>
@else
<span class="help-block">Add more fine-grained information to this transaction by entering a category.
This field will auto-complete existing categories but can also be used to create new ones.
</span>
@endif
</div>
</div>
<!-- TAGS -->
</div>
</div>
<!-- RELATE THIS TRANSFER TO A PIGGY BANK -->
@if($what == 'transfer' && count($piggies) > 0)
<div class="form-group">
<label for="piggybank_id" class="col-sm-4 control-label">
Piggy bank
</label>
<div class="col-sm-8">
<select name="piggybank_id" class="form-control">
<option value="0" label="(no piggy bank)">(no piggy bank)</option>
@foreach($piggies as $piggy)
@if($piggy->id == Input::old('piggybank_id') || $piggy->id == Input::get('piggybank_id'))
<option value="{{$piggy->id}}" label="{{{$piggy->name}}}" selected="selected ">{{{$piggy->name}}}</option>
@else
<option value="{{$piggy->id}}" label="{{{$piggy->name}}}">{{{$piggy->name}}}</option>
@endif
@endforeach
</select>
@if($errors->has('piggybank_id'))
<p class="text-danger">{{$errors->first('piggybank_id')}}</p>
@else
<span class="help-block">
You can directly add the amount you're transferring
to one of your piggy banks, provided they are related to the account your
transferring <em>to</em>.
</span>
@endif
</div>
</div>
@endif
</div>
</div>
<div class="row">
<div class="col-lg-6">
<!-- add another after this one? -->
<div class="form-group">
<label for="submit" class="col-sm-4 control-label">&nbsp;</label>
<div class="col-sm-8">
<div class="checkbox">
<label>
<input type="checkbox" value="1" name="create" @if(Input::old('create') == '1') checked @endif>
Create another (return to this form)
</label>
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-bolt"></i> Options
</div>
<div class="panel-body">
<div class="form-group">
<label for="default" class="col-sm-4 control-label">
Store
</label>
<div class="col-sm-8">
<div class="radio">
<label>
{{Form::radio('post_submit_action','store',true)}}
Store the {{{$what}}}
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="validate_only" class="col-sm-4 control-label">
Validate only
</label>
<div class="col-sm-8">
<div class="radio">
<label>
{{Form::radio('post_submit_action','validate_only')}}
Only validate, do not save
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="return_to_form" class="col-sm-4 control-label">
Return here
</label>
<div class="col-sm-8">
<div class="radio">
<label>
{{Form::radio('post_submit_action','create_another')}}
After storing, return here to create another one.
</label>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ALWAYS SHOW SUBMit -->
<div class="form-group">
<label for="submit" class="col-sm-4 control-label">&nbsp;</label>
<div class="col-sm-8">
<input type="submit" name="submit" value="Create {{$what}}" class="btn btn-info" />
</div>
</div>
</div>
</div>
{{Form::close()}}
@stop
@section('scripts')
<!-- a -->
<?php echo javascript_include_tag('transactions'); ?>
<!-- b -->
@stop