Many updates to get split transactions and normal transactions working side by side.

This commit is contained in:
James Cole 2016-10-21 19:06:22 +02:00
parent 801c7c0ab6
commit 9a3cd27700
25 changed files with 960 additions and 435 deletions

View File

@ -20,6 +20,7 @@ use FireflyIII\Export\Entry\Entry;
use FireflyIII\Models\ExportJob;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Collection;
use Storage;
@ -95,9 +96,9 @@ class Processor
*/
public function collectJournals(): bool
{
/** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class);
$this->journals = $repository->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']);
/** @var JournalTaskerInterface $tasker */
$tasker = app(JournalTaskerInterface::class);
$this->journals = $tasker->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']);
return true;
}

View File

@ -20,7 +20,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Input;
@ -270,17 +270,17 @@ class JsonController extends Controller
}
/**
* @param JournalRepositoryInterface $repository
* @param JournalTaskerInterface $tasker
* @param $what
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function transactionJournals(JournalRepositoryInterface $repository, $what)
public function transactionJournals(JournalTaskerInterface $tasker, $what)
{
$descriptions = [];
$type = config('firefly.transactionTypesByWhat.' . $what);
$types = [$type];
$journals = $repository->getJournals($types, 1, 50);
$journals = $tasker->getJournals($types, 1, 50);
foreach ($journals as $j) {
$descriptions[] = $j->description;
}

View File

@ -23,9 +23,12 @@ use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use Illuminate\Http\Request;
use Log;
use Preferences;
use Response;
use Session;
@ -40,6 +43,14 @@ use View;
*/
class TransactionController extends Controller
{
/** @var AccountRepositoryInterface */
private $accounts;
private $attachments;
/** @var BudgetRepositoryInterface */
private $budgets;
/** @var PiggyBankRepositoryInterface */
private $piggyBanks;
/**
*
*/
@ -52,8 +63,21 @@ class TransactionController extends Controller
$maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize'));
$maxPostSize = Steam::phpBytes(ini_get('post_max_size'));
$uploadSize = min($maxFileSize, $maxPostSize);
View::share('uploadSize', $uploadSize);
// some useful repositories:
$this->middleware(
function ($request, $next) {
$this->accounts = app(AccountRepositoryInterface::class);
$this->budgets = app(BudgetRepositoryInterface::class);
$this->piggyBanks = app(PiggyBankRepositoryInterface::class);
$this->attachments = app(AttachmentHelperInterface::class);
return $next($request);
}
);
}
/**
@ -63,20 +87,16 @@ class TransactionController extends Controller
*/
public function create(string $what = TransactionType::DEPOSIT)
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
$piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface');
$what = strtolower($what);
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
$assetAccounts = ExpandedForm::makeSelectList($accountRepository->getActiveAccountsByType(['Default account', 'Asset account']));
$budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
$piggyBanks = $piggyRepository->getPiggyBanksWithAmount();
$piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks);
$preFilled = Session::has('preFilled') ? session('preFilled') : [];
$subTitle = trans('form.add_new_' . $what);
$subTitleIcon = 'fa-plus';
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$what = strtolower($what);
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getActiveAccountsByType(['Default account', 'Asset account']));
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
$piggyBanks = $this->piggyBanks->getPiggyBanksWithAmount();
$piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks);
$preFilled = Session::has('preFilled') ? session('preFilled') : [];
$subTitle = trans('form.add_new_' . $what);
$subTitleIcon = 'fa-plus';
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
Session::put('preFilled', $preFilled);
@ -91,7 +111,6 @@ class TransactionController extends Controller
asort($piggies);
return view('transactions.create', compact('assetAccounts', 'subTitleIcon', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle', 'optionalFields'));
}
@ -145,19 +164,12 @@ class TransactionController extends Controller
{
$count = $journal->transactions()->count();
if ($count > 2) {
return redirect(route('split.journal.edit', [$journal->id]));
return redirect(route('journal.edit-split', [$journal->id]));
}
// code to get list data:
$budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface');
$piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface');
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$assetAccounts = ExpandedForm::makeSelectList($repository->getAccountsByType(['Default account', 'Asset account']));
$budgetList = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
$piggyBankList = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanks());
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType(['Default account', 'Asset account']));
$budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
$piggyBankList = ExpandedForm::makeSelectListWithEmpty($this->piggyBanks->getPiggyBanks());
// view related code
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
@ -216,20 +228,20 @@ class TransactionController extends Controller
}
/**
* @param Request $request
* @param JournalRepositoryInterface $repository
* @param string $what
* @param Request $request
* @param JournalTaskerInterface $tasker
* @param string $what
*
* @return View
*/
public function index(Request $request, JournalRepositoryInterface $repository, string $what)
public function index(Request $request, JournalTaskerInterface $tasker, string $what)
{
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$subTitleIcon = config('firefly.transactionIconsByWhat.' . $what);
$types = config('firefly.transactionTypesByWhat.' . $what);
$subTitle = trans('firefly.title_' . $what);
$page = intval($request->get('page'));
$journals = $repository->getJournals($types, $page, $pageSize);
$journals = $tasker->getJournals($types, $page, $pageSize);
$journals->setPath('transactions/' . $what);
@ -266,14 +278,14 @@ class TransactionController extends Controller
}
/**
* @param TransactionJournal $journal
* @param JournalRepositoryInterface $repository
* @param TransactionJournal $journal
* @param JournalTaskerInterface $tasker
*
* @return View
*/
public function show(TransactionJournal $journal, JournalRepositoryInterface $repository, JournalTaskerInterface $tasker)
public function show(TransactionJournal $journal, JournalTaskerInterface $tasker)
{
$events = $repository->getPiggyBankEvents($journal);
$events = $tasker->getPiggyBankEvents($journal);
$transactions = $tasker->getTransactionsOverview($journal);
$what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
$subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"';
@ -291,63 +303,47 @@ class TransactionController extends Controller
*/
public function store(JournalFormRequest $request, JournalRepositoryInterface $repository)
{
$att = app('FireflyIII\Helpers\Attachments\AttachmentHelperInterface');
$doSplit = intval($request->get('split_journal')) === 1;
$journalData = $request->getJournalData();
$doSplit = intval($request->get('split_journal')) === 1;
$createAnother = intval($request->get('create_another')) === 1;
$data = $request->getJournalData();
$journal = $repository->store($data);
if (is_null($journal->id)) {
// error!
Log::error('Could not store transaction journal: ', $journal->getErrors()->toArray());
Session::flash('error', $journal->getErrors()->first());
return redirect(route('transactions.create', [$request->input('what')]))->withInput();
}
$this->attachments->saveAttachmentsForModel($journal);
// store the journal only, flash the rest.
if ($doSplit) {
$journal = $repository->storeJournal($journalData);
$journal->completed = false;
$journal->save();
// store attachments:
$att->saveAttachmentsForModel($journal);
// flash errors
if (count($att->getErrors()->get('attachments')) > 0) {
Session::flash('error', $att->getErrors()->get('attachments'));
}
// flash messages
if (count($att->getMessages()->get('attachments')) > 0) {
Session::flash('info', $att->getMessages()->get('attachments'));
}
Session::put('journal-data', $journalData);
return redirect(route('split.journal.create', [$journal->id]));
}
// if not withdrawal, unset budgetid.
if ($journalData['what'] != strtolower(TransactionType::WITHDRAWAL)) {
$journalData['budget_id'] = 0;
}
$journal = $repository->store($journalData);
$att->saveAttachmentsForModel($journal);
// flash errors
if (count($att->getErrors()->get('attachments')) > 0) {
Session::flash('error', $att->getErrors()->get('attachments'));
if (count($this->attachments->getErrors()->get('attachments')) > 0) {
Session::flash('error', $this->attachments->getErrors()->get('attachments'));
}
// flash messages
if (count($att->getMessages()->get('attachments')) > 0) {
Session::flash('info', $att->getMessages()->get('attachments'));
if (count($this->attachments->getMessages()->get('attachments')) > 0) {
Session::flash('info', $this->attachments->getMessages()->get('attachments'));
}
event(new TransactionJournalStored($journal, intval($journalData['piggy_bank_id'])));
event(new TransactionJournalStored($journal, $data['piggy_bank_id']));
Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)])));
Preferences::mark();
if (intval($request->get('create_another')) === 1) {
if ($createAnother === true) {
// set value so create routine will not overwrite URL:
Session::put('transactions.create.fromStore', true);
return redirect(route('transactions.create', [$request->input('what')]))->withInput();
}
if ($doSplit === true) {
// redirect to edit screen:
return redirect(route('transactions.edit', [$journal->id]));
}
// redirect to previous URL.
return redirect(session('transactions.create.url'));
@ -355,35 +351,31 @@ class TransactionController extends Controller
/**
* @param JournalFormRequest $request
* @param JournalRepositoryInterface $repository
* @param AttachmentHelperInterface $att
* @param TransactionJournal $journal
* @param JournalFormRequest $request
* @param TransactionJournal $journal
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, AttachmentHelperInterface $att, TransactionJournal $journal)
public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
{
$journalData = $request->getJournalData();
$repository->update($journal, $journalData);
// save attachments:
$att->saveAttachmentsForModel($journal);
$data = $request->getJournalData();
$journal = $repository->update($journal, $data);
$this->attachments->saveAttachmentsForModel($journal);
// flash errors
if (count($att->getErrors()->get('attachments')) > 0) {
Session::flash('error', $att->getErrors()->get('attachments'));
if (count($this->attachments->getErrors()->get('attachments')) > 0) {
Session::flash('error', $this->attachments->getErrors()->get('attachments'));
}
// flash messages
if (count($att->getMessages()->get('attachments')) > 0) {
Session::flash('info', $att->getMessages()->get('attachments'));
if (count($this->attachments->getMessages()->get('attachments')) > 0) {
Session::flash('info', $this->attachments->getMessages()->get('attachments'));
}
event(new TransactionJournalUpdated($journal));
// update, get events by date and sort DESC
$type = strtolower($journal->transaction_type_type ?? TransactionJournal::transactionTypeStr($journal));
Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($journalData['description'])])));
$type = strtolower(TransactionJournal::transactionTypeStr($journal));
Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['description'])])));
Preferences::mark();
if (intval($request->get('return_to_edit')) === 1) {

View File

@ -14,7 +14,6 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Requests;
use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionType;
use Input;
@ -37,86 +36,110 @@ class JournalFormRequest extends Request
}
/**
* Returns and validates the data required to store a new journal. Can handle both single transaction journals and split journals.
*
* @return array
*/
public function getJournalData()
{
$tags = $this->getFieldOrEmptyString('tags');
$data = [
'what' => $this->get('what'), // type. can be 'deposit', 'withdrawal' or 'transfer'
'user' => auth()->user()->id,
'date' => new Carbon($this->get('date')),
'tags' => explode(',', $this->getFieldOrEmptyString('tags')),
'currency_id' => intval($this->get('amount_currency_id_amount')),
return [
'what' => $this->get('what'),
'description' => trim($this->get('description')),
'source_account_id' => intval($this->get('source_account_id')),
'source_account_name' => trim($this->getFieldOrEmptyString('source_account_name')),
'destination_account_id' => intval($this->get('destination_account_id')),
'destination_account_name' => trim($this->getFieldOrEmptyString('destination_account_name')),
'amount' => round($this->get('amount'), 2),
'user' => auth()->user()->id,
'amount_currency_id_amount' => intval($this->get('amount_currency_id_amount')),
'date' => new Carbon($this->get('date')),
'interest_date' => $this->getDateOrNull('interest_date'),
'book_date' => $this->getDateOrNull('book_date'),
'process_date' => $this->getDateOrNull('process_date'),
'budget_id' => intval($this->get('budget_id')),
'category' => trim($this->getFieldOrEmptyString('category')),
'tags' => explode(',', $tags),
'piggy_bank_id' => intval($this->get('piggy_bank_id')),
// all custom fields:
'interest_date' => $this->getDateOrNull('interest_date'),
'book_date' => $this->getDateOrNull('book_date'),
'process_date' => $this->getDateOrNull('process_date'),
'due_date' => $this->getDateOrNull('due_date'),
'payment_date' => $this->getDateOrNull('payment_date'),
'invoice_date' => $this->getDateOrNull('invoice_date'),
'internal_reference' => trim(strval($this->get('internal_reference'))),
'notes' => trim(strval($this->get('notes'))),
// new custom fields here:
'due_date' => $this->getDateOrNull('due_date'),
'payment_date' => $this->getDateOrNull('payment_date'),
'invoice_date' => $this->getDateOrNull('invoice_date'),
'internal_reference' => trim(strval($this->get('internal_reference'))),
'notes' => trim(strval($this->get('notes'))),
// transaction / journal data:
'description' => $this->getFieldOrEmptyString('description'),
'amount' => round($this->get('amount'), 2),
'budget_id' => intval($this->get('budget_id')),
'category' => $this->getFieldOrEmptyString('category'),
'source_account_id' => intval($this->get('source_account_id')),
'source_account_name' => $this->getFieldOrEmptyString('source_account_name'),
'destination_account_id' => $this->getFieldOrEmptyString('destination_account_id'),
'destination_account_name' => $this->getFieldOrEmptyString('destination_account_name'),
'piggy_bank_id' => intval($this->get('piggy_bank_id')),
];
return $data;
}
/**
* @return array
* @throws Exception
*/
public function rules()
{
$what = Input::get('what');
$rules = [
'description' => 'required|min:1,max:255',
'what' => 'required|in:withdrawal,deposit,transfer',
'amount' => 'numeric|required|min:0.01',
'date' => 'required|date',
'process_date' => 'date',
'book_date' => 'date',
'interest_date' => 'date',
'category' => 'between:1,255',
'amount_currency_id_amount' => 'required|exists:transaction_currencies,id',
'piggy_bank_id' => 'numeric',
'what' => 'required|in:withdrawal,deposit,transfer',
'date' => 'required|date',
// new custom fields here:
'due_date' => 'date',
'payment_date' => 'date',
'internal_reference' => 'min:1,max:255',
'notes' => 'min:1,max:65536',
// then, custom fields:
'interest_date' => 'date',
'book_date' => 'date',
'process_date' => 'date',
'due_date' => 'date',
'payment_date' => 'date',
'invoice_date' => 'date',
'internal_reference' => 'min:1,max:255',
'notes' => 'min:1,max:50000',
// and then transaction rules:
'description' => 'required|between:1,255',
'amount' => 'numeric|required|min:0.01',
'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id',
'category' => 'between:1,255',
'source_account_id' => 'numeric|belongsToUser:accounts,id',
'source_account_name' => 'between:1,255',
'destination_account_id' => 'numeric|belongsToUser:accounts,id',
'destination_account_name' => 'between:1,255',
'piggy_bank_id' => 'between:1,255',
];
// some rules get an upgrade depending on the type of data:
$rules = $this->enhanceRules($what, $rules);
return $rules;
}
/**
* Inspired by https://www.youtube.com/watch?v=WwnI0RS6J5A
*
* @param string $what
* @param array $rules
*
* @return array
* @throws FireflyException
*/
private function enhanceRules(string $what, array $rules): array
{
switch ($what) {
case strtolower(TransactionType::WITHDRAWAL):
$rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
$rules['destination_account_name'] = 'between:1,255';
if (intval(Input::get('budget_id')) != 0) {
$rules['budget_id'] = 'exists:budgets,id|belongsToUser:budgets';
}
break;
case strtolower(TransactionType::DEPOSIT):
$rules['source_account_name'] = 'between:1,255';
$rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
break;
case strtolower(TransactionType::TRANSFER):
// this may not work:
$rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_account_id';
$rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_account_id';
break;
default:
throw new FireflyException('Cannot handle transaction type of type ' . e($what) . '.');
throw new FireflyException('Cannot handle transaction type of type ' . e($what) . ' . ');
}
return $rules;
@ -141,4 +164,63 @@ class JournalFormRequest extends Request
{
return $this->get($field) ?? '';
}
//
// /**
// * @param int $index
// * @param string $field
// *
// * @return int
// */
// private function getIntFromArray(int $index, string $field): int
// {
// $array = $this->get($field);
// if (isset($array[$index])) {
// return intval($array[$index]);
// }
//
// return 0;
// }
//
// /**
// * @param int $index
// * @param string $field
// *
// * @return string
// */
// private function getStringFromArray(int $index, string $field): string
// {
// $array = $this->get($field);
// if (isset($array[$index])) {
// return trim($array[$index]);
// }
//
// return '';
// }
//
// /**
// * @return array
// */
// private function getTransactionData(): array
// {
// $transactions = [];
// $array = $this->get('amount');
// if (is_array($array) && count($array) > 0) {
// foreach ($array as $index => $amount) {
// $transaction = [
// 'description' => $this->getStringFromArray($index, 'description'),
// 'amount' => round($amount, 2),
// 'budget_id' => $this->getIntFromArray($index, 'budget_id'),
// 'category' => $this->getStringFromArray($index, 'category'),
// 'source_account_id' => $this->getIntFromArray($index, 'source_account_id'),
// 'source_account_name' => $this->getStringFromArray($index, 'source_account_name'),
// 'destination_account_id' => $this->getIntFromArray($index, 'destination_account_id'),
// 'destination_account_name' => $this->getStringFromArray($index, 'destination_account_name'),
// 'piggy_bank_id' => $this->getIntFromArray($index, 'piggy_bank_id'),
// ];
// $transactions[] = $transaction;
// }
// }
//
// return $transactions;
// }
}

View File

@ -15,7 +15,7 @@ namespace FireflyIII\Jobs;
use Carbon\Carbon;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use FireflyIII\Rules\Processor;
use FireflyIII\User;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -155,10 +155,10 @@ class ExecuteRuleGroupOnExistingTransactions extends Job implements ShouldQueue
*/
protected function collectJournals()
{
/** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class);
/** @var JournalTaskerInterface $tasker */
$tasker = app(JournalTaskerInterface::class);
return $repository->getJournalsInRange($this->accounts, $this->startDate, $this->endDate);
return $tasker->getJournalsInRange($this->accounts, $this->startDate, $this->endDate);
}
/**

View File

@ -434,6 +434,9 @@ class TransactionJournal extends TransactionJournalSupport
return new TransactionJournalMeta();
}
if (is_string($value) && strlen($value) === 0) {
return new TransactionJournalMeta();
}
if ($value instanceof Carbon) {
$value = $value->toW3cString();

View File

@ -15,6 +15,7 @@ namespace FireflyIII\Providers;
use FireflyIII\Support\Amount;
use FireflyIII\Support\ExpandedForm;
use FireflyIII\Support\ExpandedMultiForm;
use FireflyIII\Support\FireflyConfig;
use FireflyIII\Support\Navigation;
use FireflyIII\Support\Preferences;
@ -92,6 +93,11 @@ class FireflyServiceProvider extends ServiceProvider
return new ExpandedForm;
}
);
$this->app->bind(
'expandedmultiform', function () {
return new ExpandedMultiForm;
}
);
$this->app->bind('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface', 'FireflyIII\Repositories\Currency\CurrencyRepository');
$this->app->bind('FireflyIII\Support\Search\SearchInterface', 'FireflyIII\Support\Search\Search');

View File

@ -13,24 +13,18 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Journal;
use Carbon\Carbon;
use DB;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Log;
/**
@ -100,88 +94,6 @@ class JournalRepository implements JournalRepositoryInterface
return $entry;
}
/**
* @param array $types
* @param int $page
* @param int $pageSize
*
* @return LengthAwarePaginator
*/
public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator
{
$offset = ($page - 1) * $pageSize;
$query = $this->user->transactionJournals()->expanded()->sortCorrectly();
$query->where('transaction_journals.completed', 1);
if (count($types) > 0) {
$query->transactionTypes($types);
}
$count = $this->user->transactionJournals()->transactionTypes($types)->count();
$set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields());
$journals = new LengthAwarePaginator($set, $count, $pageSize, $page);
return $journals;
}
/**
* Returns a collection of ALL journals, given a specific account and a date range.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection
{
$query = $this->user->transactionJournals()->expanded()->sortCorrectly();
$query->where('transaction_journals.completed', 1);
$query->before($end);
$query->after($start);
if ($accounts->count() > 0) {
$ids = $accounts->pluck('id')->toArray();
// join source and destination:
$query->leftJoin(
'transactions as source', function (JoinClause $join) {
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
}
);
$query->leftJoin(
'transactions as destination', function (JoinClause $join) {
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
}
);
$query->where(
function (Builder $q) use ($ids) {
$q->whereIn('destination.account_id', $ids);
$q->orWhereIn('source.account_id', $ids);
}
);
}
$set = $query->get(TransactionJournal::queryFields());
return $set;
}
/**
* @param TransactionJournal $journal
*
* @return Collection
*/
public function getPiggyBankEvents(TransactionJournal $journal): Collection
{
/** @var Collection $set */
$events = $journal->piggyBankEvents()->get();
$events->each(
function (PiggyBankEvent $event) {
$event->piggyBank = $event->piggyBank()->withTrashed()->first();
}
);
return $events;
}
/**
* @param array $data
@ -192,56 +104,47 @@ class JournalRepository implements JournalRepositoryInterface
{
// find transaction type.
$transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
// store actual journal.
$journal = new TransactionJournal(
$journal = new TransactionJournal(
[
'user_id' => $data['user'],
'transaction_type_id' => $transactionType->id,
'transaction_currency_id' => $data['amount_currency_id_amount'],
'transaction_currency_id' => $data['currency_id'],
'description' => $data['description'],
'completed' => 0,
'date' => $data['date'],
'interest_date' => $data['interest_date'],
'book_date' => $data['book_date'],
'process_date' => $data['process_date'],
]
);
$journal->save();
// store or get category
if (strlen($data['category']) > 0) {
$category = Category::firstOrCreateEncrypted(['name' => $data['category'], 'user_id' => $data['user']]);
$journal->categories()->save($category);
}
// store stuff:
$this->storeCategoryWithJournal($journal, $data['category']);
$this->storeBudgetWithJournal($journal, $data['budget_id']);
$accounts = $this->storeAccounts($transactionType, $data);
// store or get budget
if (intval($data['budget_id']) > 0 && $transactionType->type !== TransactionType::TRANSFER) {
/** @var \FireflyIII\Models\Budget $budget */
$budget = Budget::find($data['budget_id']);
$journal->budgets()->save($budget);
}
// store two transactions:
$one = [
'journal' => $journal,
'account' => $accounts['source'],
'amount' => bcmul(strval($data['amount']), '-1'),
'description' => null,
'category' => null,
'budget' => null,
'identifier' => 0,
];
$this->storeTransaction($one);
// store accounts (depends on type)
list($sourceAccount, $destinationAccount) = $this->storeAccounts($transactionType, $data);
$two = [
'journal' => $journal,
'account' => $accounts['destination'],
'amount' => $data['amount'],
'description' => null,
'category' => null,
'budget' => null,
'identifier' => 0,
];
$this->storeTransaction($two);
// store accompanying transactions.
Transaction::create( // first transaction.
[
'account_id' => $sourceAccount->id,
'transaction_journal_id' => $journal->id,
'amount' => $data['amount'] * -1,
]
);
Transaction::create( // second transaction.
[
'account_id' => $destinationAccount->id,
'transaction_journal_id' => $journal->id,
'amount' => $data['amount'],
]
);
$journal->completed = 1;
$journal->save();
// store tags
if (isset($data['tags']) && is_array($data['tags'])) {
@ -256,6 +159,9 @@ class JournalRepository implements JournalRepositoryInterface
Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id));
}
$journal->completed = 1;
$journal->save();
return $journal;
}
@ -302,45 +208,24 @@ class JournalRepository implements JournalRepositoryInterface
*/
public function update(TransactionJournal $journal, array $data): TransactionJournal
{
// update actual journal.
$journal->transaction_currency_id = $data['amount_currency_id_amount'];
// update actual journal:
$journal->transaction_currency_id = $data['currency_id'];
$journal->description = $data['description'];
$journal->date = $data['date'];
// unlink all categories, recreate them:
$journal->categories()->detach();
if (strlen($data['category']) > 0) {
$category = Category::firstOrCreateEncrypted(['name' => $data['category'], 'user_id' => $data['user']]);
$journal->categories()->save($category);
}
// unlink all budgets and recreate them:
$journal->budgets()->detach();
if (intval($data['budget_id']) > 0 && $journal->transactionType->type !== TransactionType::TRANSFER) {
/** @var \FireflyIII\Models\Budget $budget */
$budget = Budget::where('user_id', $this->user->id)->where('id', $data['budget_id'])->first();
$journal->budgets()->save($budget);
}
// store accounts (depends on type)
list($fromAccount, $toAccount) = $this->storeAccounts($journal->transactionType, $data);
$this->storeCategoryWithJournal($journal, $data['category']);
$this->storeBudgetWithJournal($journal, $data['budget_id']);
$accounts = $this->storeAccounts($journal->transactionType, $data);
// update the from and to transaction.
/** @var Transaction $transaction */
foreach ($journal->transactions()->get() as $transaction) {
if ($transaction->amount < 0) {
// this is the from transaction, negative amount:
$transaction->amount = $data['amount'] * -1;
$transaction->account_id = $fromAccount->id;
$transaction->save();
}
if ($transaction->amount > 0) {
$transaction->amount = $data['amount'];
$transaction->account_id = $toAccount->id;
$transaction->save();
}
}
$sourceAmount = bcmul(strval($data['amount']), '-1');
$this->updateSourceTransaction($journal, $accounts['source'], $sourceAmount); // negative because source loses money.
$amount = strval($data['amount']);
$this->updateDestinationTransaction($journal, $accounts['destination'], $amount); // positive because destination gets money.
$journal->save();
@ -402,38 +287,66 @@ class JournalRepository implements JournalRepositoryInterface
*/
private function storeAccounts(TransactionType $type, array $data): array
{
$sourceAccount = null;
$destinationAccount = null;
$accounts = [
'source' => null,
'destination' => null,
];
switch ($type->type) {
case TransactionType::WITHDRAWAL:
list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($data);
$accounts = $this->storeWithdrawalAccounts($data);
break;
case TransactionType::DEPOSIT:
list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($data);
$accounts = $this->storeDepositAccounts($data);
break;
case TransactionType::TRANSFER:
$sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first();
$destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first();
$accounts['source'] = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first();
$accounts['destination'] = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first();
break;
default:
throw new FireflyException('Did not recognise transaction type.');
throw new FireflyException(sprintf('Did not recognise transaction type "%s".', $type->type));
}
if (is_null($destinationAccount)) {
if (is_null($accounts['source'])) {
Log::error('"destination"-account is null, so we cannot continue!', ['data' => $data]);
throw new FireflyException('"destination"-account is null, so we cannot continue!');
}
if (is_null($sourceAccount)) {
if (is_null($accounts['destination'])) {
Log::error('"source"-account is null, so we cannot continue!', ['data' => $data]);
throw new FireflyException('"source"-account is null, so we cannot continue!');
}
return [$sourceAccount, $destinationAccount];
return $accounts;
}
/**
* @param TransactionJournal $journal
* @param int $budgetId
*/
private function storeBudgetWithJournal(TransactionJournal $journal, int $budgetId)
{
if (intval($budgetId) > 0 && $journal->transactionType->type !== TransactionType::TRANSFER) {
/** @var \FireflyIII\Models\Budget $budget */
$budget = Budget::find($budgetId);
$journal->budgets()->save($budget);
}
}
/**
* @param TransactionJournal $journal
* @param string $category
*/
private function storeCategoryWithJournal(TransactionJournal $journal, string $category)
{
if (strlen($category) > 0) {
$category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $journal->user_id]);
$journal->categories()->save($category);
}
}
/**
@ -446,19 +359,50 @@ class JournalRepository implements JournalRepositoryInterface
$destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']);
if (strlen($data['source_account_name']) > 0) {
$fromType = AccountType::where('type', 'Revenue account')->first();
$fromAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['source_account_name'], 'active' => 1]
$sourceType = AccountType::where('type', 'Revenue account')->first();
$sourceAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name'], 'active' => 1]
);
return [$fromAccount, $destinationAccount];
return [
'source' => $sourceAccount,
'destination' => $destinationAccount,
];
}
$fromType = AccountType::where('type', 'Cash account')->first();
$fromAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => 'Cash account', 'active' => 1]
$sourceType = AccountType::where('type', 'Cash account')->first();
$sourceAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1]
);
return [$fromAccount, $destinationAccount];
return [
'source' => $sourceAccount,
'destination' => $destinationAccount,
];
}
private function storeTransaction(array $data): Transaction
{
/** @var Transaction $transaction */
$transaction = Transaction::create(
[
'transaction_journal_id' => $data['journal']->id,
'account_id' => $data['account']->id,
'amount' => $data['amount'],
'description' => $data['description'],
'identifier' => $data['identifier'],
]
);
if (!is_null($data['category'])) {
$transaction->categories()->save($data['category']);
}
if (!is_null($data['budget'])) {
$transaction->categories()->save($data['budget']);
}
return $transaction;
}
/**
@ -481,14 +425,69 @@ class JournalRepository implements JournalRepositoryInterface
]
);
return [$sourceAccount, $destinationAccount];
return [
'source' => $sourceAccount,
'destination' => $destinationAccount,
];
}
$destinationType = AccountType::where('type', 'Cash account')->first();
$destinationAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
);
return [$sourceAccount, $destinationAccount];
return [
'source' => $sourceAccount,
'destination' => $destinationAccount,
];
}
/**
* @param TransactionJournal $journal
* @param Account $account
* @param string $amount
*
* @throws FireflyException
*/
private function updateDestinationTransaction(TransactionJournal $journal, Account $account, string $amount)
{
// should be one:
$set = $journal->transactions()->where('amount', '>', 0)->get();
if ($set->count() != 1) {
throw new FireflyException(
sprintf('Journal #%d has an unexpected (%d) amount of transactions with an amount more than zero.', $journal->id, $set->count())
);
}
/** @var Transaction $transaction */
$transaction = $set->first();
$transaction->amount = $amount;
$transaction->account_id = $account->id;
$transaction->save();
}
/**
* @param TransactionJournal $journal
* @param Account $account
* @param string $amount
*
* @throws FireflyException
*/
private function updateSourceTransaction(TransactionJournal $journal, Account $account, string $amount)
{
// should be one:
$set = $journal->transactions()->where('amount', '<', 0)->get();
if ($set->count() != 1) {
throw new FireflyException(
sprintf('Journal #%d has an unexpected (%d) amount of transactions with an amount less than zero.', $journal->id, $set->count())
);
}
/** @var Transaction $transaction */
$transaction = $set->first();
$transaction->amount = $amount;
$transaction->account_id = $account->id;
$transaction->save();
}

View File

@ -13,11 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Journal;
use Carbon\Carbon;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
/**
* Interface JournalRepositoryInterface
@ -52,34 +48,6 @@ interface JournalRepositoryInterface
*/
public function first(): TransactionJournal;
/**
* Returns a page of a specific type(s) of journal.
*
* @param array $types
* @param int $page
* @param int $pageSize
*
* @return LengthAwarePaginator
*/
public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator;
/**
* Returns a collection of ALL journals, given a specific account and a date range.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection;
/**
* @param TransactionJournal $journal
*
* @return Collection
*/
public function getPiggyBankEvents(TransactionJournal $journal): Collection;
/**
* @param array $data

View File

@ -13,6 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Journal;
use Carbon\Carbon;
use Crypt;
use DB;
use FireflyIII\Models\Transaction;
@ -20,6 +21,8 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
/**
* Class JournalTasker
@ -42,6 +45,91 @@ class JournalTasker implements JournalTaskerInterface
$this->user = $user;
}
/**
* Returns a page of a specific type(s) of journal.
*
* @param array $types
* @param int $page
* @param int $pageSize
*
* @return LengthAwarePaginator
*/
public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator
{
$offset = ($page - 1) * $pageSize;
$query = $this->user->transactionJournals()->expanded()->sortCorrectly();
$query->where('transaction_journals.completed', 1);
if (count($types) > 0) {
$query->transactionTypes($types);
}
$count = $this->user->transactionJournals()->transactionTypes($types)->count();
$set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields());
$journals = new LengthAwarePaginator($set, $count, $pageSize, $page);
return $journals;
}
/**
* Returns a collection of ALL journals, given a specific account and a date range.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection
{
$query = $this->user->transactionJournals()->expanded()->sortCorrectly();
$query->where('transaction_journals.completed', 1);
$query->before($end);
$query->after($start);
if ($accounts->count() > 0) {
$ids = $accounts->pluck('id')->toArray();
// join source and destination:
$query->leftJoin(
'transactions as source', function (JoinClause $join) {
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
}
);
$query->leftJoin(
'transactions as destination', function (JoinClause $join) {
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
}
);
$query->where(
function (Builder $q) use ($ids) {
$q->whereIn('destination.account_id', $ids);
$q->orWhereIn('source.account_id', $ids);
}
);
}
$set = $query->get(TransactionJournal::queryFields());
return $set;
}
/**
* @param TransactionJournal $journal
*
* @return Collection
*/
public function getPiggyBankEvents(TransactionJournal $journal): Collection
{
/** @var Collection $set */
$events = $journal->piggyBankEvents()->get();
$events->each(
function (PiggyBankEvent $event) {
$event->piggyBank = $event->piggyBank()->withTrashed()->first();
}
);
return $events;
}
/**
* Get an overview of the transactions of a journal, tailored to the view
* that shows a transaction (transaction/show/xx).
@ -138,7 +226,6 @@ class JournalTasker implements JournalTaskerInterface
return $transactions;
}
/**
* Collect the balance of an account before the given transaction has hit. This is tricky, because
* the balance does not depend on the transaction itself but the journal it's part of. And of course

View File

@ -14,7 +14,10 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Journal;
use Carbon\Carbon;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
/**
* Interface JournalTaskerInterface
@ -23,6 +26,34 @@ use FireflyIII\Models\TransactionJournal;
*/
interface JournalTaskerInterface
{
/**
* Returns a page of a specific type(s) of journal.
*
* @param array $types
* @param int $page
* @param int $pageSize
*
* @return LengthAwarePaginator
*/
public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator;
/**
* Returns a collection of ALL journals, given a specific account and a date range.
*
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection;
/**
* @param TransactionJournal $journal
*
* @return Collection
*/
public function getPiggyBankEvents(TransactionJournal $journal): Collection;
/**
* Get an overview of the transactions of a journal, tailored to the view

View File

@ -15,7 +15,7 @@ namespace FireflyIII\Rules;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use Illuminate\Support\Collection;
use Log;
@ -31,8 +31,8 @@ class TransactionMatcher
private $limit = 10;
/** @var int Maximum number of transaction to search in (for performance reasons) * */
private $range = 200;
/** @var JournalRepositoryInterface */
private $repository;
/** @var JournalTaskerInterface */
private $tasker;
/** @var array */
private $transactionTypes = [TransactionType::DEPOSIT, TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
/** @var array List of triggers to match */
@ -41,11 +41,11 @@ class TransactionMatcher
/**
* TransactionMatcher constructor. Typehint the repository.
*
* @param JournalRepositoryInterface $repository
* @param JournalTaskerInterface $tasker
*/
public function __construct(JournalRepositoryInterface $repository)
public function __construct(JournalTaskerInterface $tasker)
{
$this->repository = $repository;
$this->tasker = $tasker;
}
@ -76,7 +76,7 @@ class TransactionMatcher
// - the maximum number of transactions to search in have been searched
do {
// Fetch a batch of transactions from the database
$paginator = $this->repository->getJournals($this->transactionTypes, $page, $pagesize);
$paginator = $this->tasker->getJournals($this->transactionTypes, $page, $pagesize);
$set = $paginator->getCollection();

View File

@ -427,6 +427,7 @@ class ExpandedForm
*/
protected function expandOptionArray(string $name, $label, array $options): array
{
$name = str_replace('[]', '', $name);
$options['class'] = 'form-control';
$options['id'] = 'ffInput_' . $name;
$options['autocomplete'] = 'off';
@ -494,6 +495,7 @@ class ExpandedForm
if (isset($options['label'])) {
return $options['label'];
}
$name = str_replace('[]', '', $name);
return strval(trans('form.' . $name));

View File

@ -0,0 +1,188 @@
<?php
/**
* ExpandedMultiForm.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Support;
use Amount as Amt;
use Carbon\Carbon;
use Illuminate\Support\MessageBag;
use Input;
use RuntimeException;
use Session;
/**
* Class ExpandedMultiForm
*
* @package FireflyIII\Support
*/
class ExpandedMultiForm
{
/**
* @param string $name
* @param int $index
* @param null $value
* @param array $options
*
* @return string
*/
public function amount(string $name, int $index, $value = null, array $options = []): string
{
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $index, $label, $options);
$classes = $this->getHolderClasses($name, $index);
$value = $this->fillFieldValue($name, $index, $value);
$options['step'] = 'any';
$options['min'] = '0.01';
$defaultCurrency = isset($options['currency']) ? $options['currency'] : Amt::getDefaultCurrency();
$currencies = Amt::getAllCurrencies();
$options['data-hiddenfield'] = 'amount_currency_id_' . $name . '_' . $index;
unset($options['currency']);
unset($options['placeholder']);
$html = view('form.multi.amount', compact('defaultCurrency', 'index', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render();
return $html;
}
/**
* @param string $name
* @param int $index
* @param array $list
* @param null $selected
* @param array $options
*
* @return string
*/
public function select(string $name, int $index, array $list = [], $selected = null, array $options = []): string
{
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $index, $label, $options);
$classes = $this->getHolderClasses($name, $index);
$selected = $this->fillFieldValue($name, $index, $selected);
unset($options['autocomplete']);
unset($options['placeholder']);
$html = view('form.multi.select', compact('classes', 'index', 'name', 'label', 'selected', 'options', 'list'))->render();
return $html;
}
/**
* @param string $name
* @param int $index
* @param null $value
* @param array $options
*
* @return string
*/
public function text(string $name, int $index, $value = null, array $options = []): string
{
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $index, $label, $options);
$classes = $this->getHolderClasses($name, $index);
$value = $this->fillFieldValue($name, $index, $value);
$html = view('form.multi.text', compact('classes', 'name', 'index', 'label', 'value', 'options'))->render();
return $html;
}
/**
* @param string $name
* @param int $index
* @param string $label
* @param array $options
*
* @return array
*/
protected function expandOptionArray(string $name, int $index, string $label, array $options): array
{
$options['class'] = 'form-control';
$options['id'] = 'ffInput_' . $name . '_' . $index;
$options['autocomplete'] = 'off';
$options['placeholder'] = ucfirst($label);
return $options;
}
/**
* @param string $name
* @param int $index
* @param $value
*
* @return mixed
*/
protected function fillFieldValue(string $name, int $index, $value)
{
if (Session::has('preFilled')) {
$preFilled = session('preFilled');
$value = isset($preFilled[$name][$index]) && is_null($value) ? $preFilled[$name][$index] : $value;
}
try {
if (!is_null(Input::old($name)[$index])) {
$value = Input::old($name)[$index];
}
} catch (RuntimeException $e) {
// don't care about session errors.
}
if ($value instanceof Carbon) {
$value = $value->format('Y-m-d');
}
return $value;
}
/**
* @param string $name
* @param int $index
*
* @return string
*/
protected function getHolderClasses(string $name, int $index): string
{
/*
* Get errors from session:
*/
/** @var MessageBag $errors */
$errors = session('errors');
$classes = 'form-group';
$set = [];
if (!is_null($errors)) {
$set = $errors->get($name . '.' . $index);
}
if (!is_null($errors) && count($set) > 0) {
$classes = 'form-group has-error has-feedback';
}
return $classes;
}
/**
* @param string $name
* @param array $options
*
* @return string
*/
protected function label(string $name, array $options): string
{
if (isset($options['label'])) {
return $options['label'];
}
return strval(trans('form.' . $name));
}
}

View File

@ -0,0 +1,35 @@
<?php
/**
* ExpandedMultiForm.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Support\Facades;
use Illuminate\Support\Facades\Facade;
/**
* Class Amount
*
* @package FireflyIII\Support\Facades
*/
class ExpandedMultiForm extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor(): string
{
return 'expandedmultiform';
}
}

View File

@ -124,6 +124,28 @@ class FireflyValidator extends Validator
return (intval($checksum) === 1);
}
/**
* @param $attribute
* @param $value
* @param $parameters
*
* @return bool
*/
public function validateMustExist($attribute, $value, $parameters): bool
{
$field = $parameters[1] ?? 'id';
if (intval($value) === 0) {
return true;
}
$count = DB::table($parameters[0])->where($field, $value)->count();
if ($count === 1) {
return true;
}
return false;
}
/**
* @param $attribute
*

View File

@ -188,8 +188,8 @@ return [
// own stuff:
//Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
//Barryvdh\Debugbar\ServiceProvider::class,
Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
Barryvdh\Debugbar\ServiceProvider::class,
DaveJamesMiller\Breadcrumbs\ServiceProvider::class,
TwigBridge\ServiceProvider::class,
'PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider',
@ -226,50 +226,51 @@ return [
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'Twig' => 'TwigBridge\Facade\Twig',
'Form' => Collective\Html\FormFacade::class,
'Html' => Collective\Html\HtmlFacade::class,
'Breadcrumbs' => 'DaveJamesMiller\Breadcrumbs\Facade',
'Preferences' => 'FireflyIII\Support\Facades\Preferences',
'FireflyConfig' => 'FireflyIII\Support\Facades\FireflyConfig',
'Navigation' => 'FireflyIII\Support\Facades\Navigation',
'Amount' => 'FireflyIII\Support\Facades\Amount',
'Steam' => 'FireflyIII\Support\Facades\Steam',
'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm',
'Entrust' => 'Zizaco\Entrust\EntrustFacade',
'Input' => 'Illuminate\Support\Facades\Input',
'Google2FA' => 'PragmaRX\Google2FA\Vendor\Laravel\Facade',
'App' => Illuminate\Support\Facades\App::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'Twig' => 'TwigBridge\Facade\Twig',
'Form' => Collective\Html\FormFacade::class,
'Html' => Collective\Html\HtmlFacade::class,
'Breadcrumbs' => 'DaveJamesMiller\Breadcrumbs\Facade',
'Preferences' => 'FireflyIII\Support\Facades\Preferences',
'FireflyConfig' => 'FireflyIII\Support\Facades\FireflyConfig',
'Navigation' => 'FireflyIII\Support\Facades\Navigation',
'Amount' => 'FireflyIII\Support\Facades\Amount',
'Steam' => 'FireflyIII\Support\Facades\Steam',
'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm',
'ExpandedMultiForm' => 'FireflyIII\Support\Facades\ExpandedMultiForm',
'Entrust' => 'Zizaco\Entrust\EntrustFacade',
'Input' => 'Illuminate\Support\Facades\Input',
'Google2FA' => 'PragmaRX\Google2FA\Vendor\Laravel\Facade',
],

View File

@ -162,6 +162,11 @@ return [
'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall',
],
],
'ExpandedMultiForm' => [
'is_safe' => [
'text','select','amount'
],
],
'Form' => [
'is_safe' => [
'input', 'select', 'checkbox', 'model', 'open', 'radio', 'textarea', 'file',

View File

@ -5,6 +5,9 @@ $(function () {
// when you click on a currency, this happens:
$('.currency-option').click(currencySelect);
// when you click on a multi currency, this happens:
$('.multi-currency-option').click(multiCurrencySelect);
var ranges = {};
ranges[dateRangeConfig.currentPeriod] = [moment(dateRangeConfig.ranges.current[0]), moment(dateRangeConfig.ranges.current[1])];
ranges[dateRangeConfig.previousPeriod] = [moment(dateRangeConfig.ranges.previous[0]), moment(dateRangeConfig.ranges.previous[1])];
@ -57,6 +60,47 @@ $(function () {
});
function multiCurrencySelect(e) {
"use strict";
// clicked on
var target = $(e.target); // target is the <A> tag.
// name of the field in question:
var name = target.data('name');
// index of the field in question:
var index = target.data('index');
console.log('name is ' + name + ':' + index);
// id of menu button (used later on):
var menuID = 'currency_dropdown_' + name + '_' + index;
// the hidden input with the actual value of the selected currency:
var hiddenInputName = 'amount_currency_id_' + name + '_' + index;
console.log('Looking for hidden input: ' + hiddenInputName);
// span with the current selection (next to the caret):
var spanId = 'currency_select_symbol_' + name + '_' + index;
// the selected currency symbol:
var symbol = target.data('symbol');
// id of the selected currency.
var id = target.data('id');
// update the hidden input:
$('input[name="' + hiddenInputName + '"]').val(id);
// update the symbol:
$('#' + spanId).text(symbol);
// close the menu (hack hack)
$('#' + menuID).click();
return false;
}
function currencySelect(e) {
"use strict";
// clicked on

View File

@ -2,7 +2,12 @@
<label for="{{ options.id }}" class="col-sm-4 control-label">{{ label }}</label>
<div class="col-sm-8">
{{ Form.input('date', name, value, options) }}
<div class="input-group">
<div class="input-group-addon">
<i class="fa fa-calendar"></i>
</div>
{{ Form.input('date', name, value, options) }}
</div>
{% include 'form/help.twig' %}
{% include 'form/feedback.twig' %}
</div>

View File

@ -0,0 +1,30 @@
<div class="{{ classes }}" id="{{ name }}_{{ index }}_holder">
<label for="{{ options.id }}" class="col-sm-4 control-label">{{ label }}</label>
<div class="col-sm-8">
<div class="input-group">
<div class="input-group-btn">
<button type="button"
class="btn btn-default dropdown-toggle currency-dropdown" id="currency_dropdown_{{ name }}_{{ index }}" data-toggle="dropdown"
aria-expanded="false">
<span id="currency_select_symbol_{{ name }}_{{ index }}">{{ defaultCurrency.symbol|raw }}</span> <span class="caret"></span>
</button>
<ul class="dropdown-menu currency-dropdown-menu" role="menu">
{% for currency in currencies %}
<li>
<a href="#"
class="multi-currency-option"
data-id="{{ currency.id }}"
data-name="{{ name }}"
data-index="{{ index }}"
data-currency="{{ currency.code }}"
data-symbol="{{ currency.symbol|raw }}">{{ currency.name }}</a></li>
{% endfor %}
</ul>
</div>
{{ Form.input('number', name~'['~index~']', value, options) }}
</div>
{% include 'form.multi.feedback.twig' %}
</div>
<input type="hidden" name="amount_currency_id_{{ name }}_{{ index }}" value="{{ defaultCurrency.id }}"/>
</div>

View File

@ -0,0 +1,4 @@
{% if errors.has(name~'.'~index) %}
<span class="form-control-feedback"><i class="fa fa-fw fa-remove"></i></span>
<p class="text-danger">{{ errors.first(name~'.'~index) }}</p>
{% endif %}

View File

@ -0,0 +1,10 @@
<div class="{{ classes }}" id="{{ name }}_{{ index }}_holder">
<label for="{{ options.id }}" class="col-sm-4 control-label">{{ label }}</label>
<div class="col-sm-8">
{{ Form.select(name~'['~index~']', list, selected , options ) }}
{% include 'form.help.twig' %}
{% include 'form.multi.feedback.twig' %}
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="{{ classes }}" id="{{ name }}_{{ index }}_holder">
<label for="{{ options.id }}" class="col-sm-4 control-label">{{ label }}</label>
<div class="col-sm-8">
{{ Form.input('text', name~'['~index~']', value, options) }}
{% include 'form/help.twig' %}
{% include 'form.multi.feedback.twig' %}
</div>
</div>

View File

@ -29,23 +29,22 @@
</div>
<!-- DESCRIPTION ALWAYS AVAILABLE -->
{# DESCRIPTION IS ALWAYS AVAILABLE #}
{{ ExpandedForm.text('description') }}
<!-- SELECTABLE SOURCE ACCOUNT ONLY FOR WITHDRAWALS AND TRANSFERS -->
{{ ExpandedForm.select('source_account_id',assetAccounts, null, {label: trans('form.asset_source_account')}) }}
{# SELECTABLE SOURCE ACCOUNT ONLY FOR WITHDRAWALS AND TRANSFERS #}
{{ ExpandedForm.select('source_account_id', assetAccounts, null, {label: trans('form.asset_source_account')}) }}
<!-- FREE FORMAT SOURCE ACCOUNT ONLY FOR DEPOSITS -->
{{ ExpandedForm.text('source_account_name',null, {label: trans('form.revenue_account')}) }}
{# FREE FORMAT SOURCE ACCOUNT ONLY FOR DEPOSITS #}
{{ ExpandedForm.text('source_account_name', null, {label: trans('form.revenue_account')}) }}
<!-- FREE FORMAT DESTINATION ACCOUNT ONLY FOR EXPENSES -->
{{ ExpandedForm.text('destination_account_name',null, {label: trans('form.expense_account')}) }}
{# FREE FORMAT DESTINATION ACCOUNT ONLY FOR EXPENSES #}
{{ ExpandedForm.text('destination_account_name', null, {label: trans('form.expense_account')}) }}
<!-- SELECTABLE DESTINATION ACCOUNT ONLY FOR TRANSFERS AND DEPOSITS -->
{{ ExpandedForm.select('destination_account_id',assetAccounts, null, {label: trans('form.asset_destination_account')} ) }}
{# SELECTABLE DESTINATION ACCOUNT ONLY FOR TRANSFERS AND DEPOSITS #}
{{ ExpandedForm.select('destination_account_id', assetAccounts, null, {label: trans('form.asset_destination_account')} ) }}
<!-- ALWAYS SHOW AMOUNT -->
{# ALWAYS SHOW AMOUNT #}
{{ ExpandedForm.amount('amount') }}
<!-- ALWAYS SHOW DATE -->
@ -66,9 +65,9 @@
<div class="box-body">
<!-- BUDGET ONLY WHEN CREATING A WITHDRAWAL -->
{% if budgets|length > 1 %}
{{ ExpandedForm.select('budget_id',budgets,0) }}
{{ ExpandedForm.select('budget_id', budgets, 0) }}
{% else %}
{{ ExpandedForm.select('budget_id',budgets,0, {helpText: trans('firefly.no_budget_pointer')}) }}
{{ ExpandedForm.select('budget_id', budgets, 0, {helpText: trans('firefly.no_budget_pointer')}) }}
{% endif %}
<!-- CATEGORY ALWAYS -->
@ -78,7 +77,7 @@
{{ ExpandedForm.text('tags') }}
<!-- RELATE THIS TRANSFER TO A PIGGY BANK -->
{{ ExpandedForm.select('piggy_bank_id',piggies) }}
{{ ExpandedForm.select('piggy_bank_id', piggies) }}
</div>
</div>
@ -108,33 +107,33 @@
</div>
<div class="box-body">
{# INTEREST DATE #}
{% if optionalFields.interest_date %}
<!-- INTEREST DATE -->
{{ ExpandedForm.date('interest_date') }}
{% endif %}
{# BOOK DATE #}
{% if optionalFields.book_date %}
<!-- BOOK DATE -->
{{ ExpandedForm.date('book_date') }}
{% endif %}
{# PROCESSING DATE #}
{% if optionalFields.process_date %}
<!-- PROCESSING DATE -->
{{ ExpandedForm.date('process_date') }}
{% endif %}
{# DUE DATE #}
{% if optionalFields.due_date %}
<!-- DUE DATE -->
{{ ExpandedForm.date('due_date') }}
{% endif %}
{# PAYMENT DATE #}
{% if optionalFields.payment_date %}
<!-- PAYMENT DATE -->
{{ ExpandedForm.date('payment_date') }}
{% endif %}
{# INVOICE DATE #}
{% if optionalFields.invoice_date %}
<!-- INVOICE DATE -->
{{ ExpandedForm.date('invoice_date') }}
{% endif %}
@ -149,13 +148,14 @@
<h3 class="box-title">{{ 'optional_field_meta_business'|_ }}</h3>
</div>
<div class="box-body">
{# REFERENCE #}
{% if optionalFields.internal_reference %}
<!-- REFERENCE -->
{{ ExpandedForm.text('internal_reference') }}
{% endif %}
{# NOTES #}
{% if optionalFields.notes %}
<!-- NOTES -->
{{ ExpandedForm.textarea('notes') }}
{% endif %}
@ -170,8 +170,8 @@
<h3 class="box-title">{{ 'optional_field_attachments'|_ }}</h3>
</div>
<div class="box-body">
{# ATTACHMENTS #}
{% if optionalFields.attachments %}
<!-- ATTACHMENTS -->
{{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }}
{% endif %}
</div>
@ -193,6 +193,7 @@
</div>
</div>
</div>
#}
</div>
</form>