First attempt at #142. Needs a lot of work still.

This commit is contained in:
James Cole 2016-04-29 20:59:28 +02:00
parent 4af8272faa
commit 0e3ccebd0b
19 changed files with 714 additions and 155 deletions

203
app/Crud/Split/Journal.php Normal file
View File

@ -0,0 +1,203 @@
<?php
/**
* Journal.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Crud\Split;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\User;
use Illuminate\Support\Collection;
/**
* Class Journal
*
* @package FireflyIII\Crud\Split
*/
class Journal implements JournalInterface
{
/** @var User */
private $user;
/**
* AttachmentRepository constructor.
*
* @param User $user
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* @param array $data
*
* @return TransactionJournal
*/
public function storeJournal(array $data) : TransactionJournal
{
// find transaction type.
$transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
$journal = new TransactionJournal(
[
'user_id' => $this->user->id,
'transaction_type_id' => $transactionType->id,
'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();
return $journal;
}
/**
* @param TransactionJournal $journal
* @param array $transaction
*
* @return Collection
*/
public function storeTransaction(TransactionJournal $journal, array $transaction): Collection
{
// store accounts (depends on type)
list($sourceAccount, $destinationAccount) = $this->storeAccounts($journal->transactionType->type, $transaction);
// store transaction one way:
/** @var Transaction $one */
$one = Transaction::create( // first transaction.
[
'account_id' => $sourceAccount->id,
'transaction_journal_id' => $journal->id,
'amount' => $transaction['amount'] * -1,
]
);
// store transaction the other way:
$two = Transaction::create( // first transaction.
[
'account_id' => $destinationAccount->id,
'transaction_journal_id' => $journal->id,
'amount' => $transaction['amount'],
]
);
// store or get category and connect:
if (strlen($transaction['category']) > 0) {
$category = Category::firstOrCreateEncrypted(['name' => $transaction['category'], 'user_id' => $journal->user_id]);
$one->categories()->save($category);
$two->categories()->save($category);
}
// store or get budget
if (intval($transaction['budget_id']) > 0) {
$budget = Budget::find($transaction['budget_id']);
$one->budgets()->save($budget);
$two->budgets()->save($budget);
}
return new Collection([$one, $two]);
}
/**
* @param string $type
* @param array $transaction
*
* @return array
* @throws FireflyException
*/
private function storeAccounts(string $type, array $transaction): array
{
$sourceAccount = null;
$destinationAccount = null;
switch ($type) {
case TransactionType::WITHDRAWAL:
list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($transaction);
break;
case TransactionType::DEPOSIT:
list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($transaction);
break;
case TransactionType::TRANSFER:
$sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_from_id'])->first();
$destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_to_id'])->first();
break;
default:
throw new FireflyException('Cannot handle ' . e($type));
}
return [$sourceAccount, $destinationAccount];
}
/**
* @param array $data
*
* @return array
*/
private function storeDepositAccounts(array $data): array
{
$destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_destination_id'])->first(['accounts.*']);
if (strlen($data['source_account_name']) > 0) {
$fromType = AccountType::where('type', 'Revenue account')->first();
$fromAccount = Account::firstOrCreateEncrypted(
['user_id' => $this->user->id, 'account_type_id' => $fromType->id, 'name' => $data['source_account_name'], 'active' => 1]
);
return [$fromAccount, $destinationAccount];
} else {
$fromType = AccountType::where('type', 'Cash account')->first();
$fromAccount = Account::firstOrCreateEncrypted(
['user_id' => $this->user->id, 'account_type_id' => $fromType->id, 'name' => 'Cash account', 'active' => 1]
);
}
return [$fromAccount, $destinationAccount];
}
/**
* @param array $data
*
* @return array
*/
private function storeWithdrawalAccounts(array $data): array
{
$sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']);
if (strlen($data['destination_account_name']) > 0) {
$destinationType = AccountType::where('type', 'Expense account')->first();
$destinationAccount = Account::firstOrCreateEncrypted(
[
'user_id' => $this->user->id,
'account_type_id' => $destinationType->id,
'name' => $data['destination_account_name'],
'active' => 1,
]
);
return [$sourceAccount, $destinationAccount];
}
$destinationType = AccountType::where('type', 'Cash account')->first();
$destinationAccount = Account::firstOrCreateEncrypted(
['user_id' => $this->user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
);
return [$sourceAccount, $destinationAccount];
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* JournalInterface.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Crud\Split;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Support\Collection;
/**
* Interface JournalInterface
*
* @package FireflyIII\Crud\Split
*/
interface JournalInterface
{
/**
* @param array $data
*
* @return TransactionJournal
*/
public function storeJournal(array $data) : TransactionJournal;
/**
* @param TransactionJournal $journal
* @param array $transaction
*
* @return Collection
*/
public function storeTransaction(TransactionJournal $journal, array $transaction): Collection;
}

View File

@ -11,11 +11,14 @@ namespace FireflyIII\Http\Controllers\Transaction;
use ExpandedForm;
use FireflyIII\Crud\Split\JournalInterface;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\SplitJournalFormRequest;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Session;
/**
* Class SplitController
@ -39,26 +42,45 @@ class SplitController extends Controller
// expect data to be in session or in post?
$journalData = session('temporary_split_data');
$currency = $currencyRepository->find(intval($journalData['amount_currency_id_amount']));
$currencies = ExpandedForm::makeSelectList($currencyRepository->get());
$assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccounts(['Default account', 'Asset account']));
$budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets());
if (!is_array($journalData)) {
throw new FireflyException('Could not find transaction data in your session. Please go back and try again.'); // translate me.
}
// echo '<pre>';
// var_dump($journalData);
// echo '</pre>';
// exit;
return view('split.journals.from-store', compact('currency', 'assetAccounts', 'budgets'))->with('data', $journalData);
return view('split.journals.from-store', compact('currencies', 'assetAccounts', 'budgets'))->with('data', $journalData);
}
public function postJournalFromStore()
/**
* @param SplitJournalFormRequest $request
* @param JournalInterface $repository
*
* @return mixed
*/
public function postJournalFromStore(SplitJournalFormRequest $request, JournalInterface $repository)
{
$data = $request->getSplitData();
// store an empty journal first. This will be the place holder.
$journal = $repository->storeJournal($data);
// Then, store each transaction individually.
foreach ($data['transactions'] as $transaction) {
$transactions = $repository->storeTransaction($journal, $transaction);
}
// TODO move to repository.
$journal->completed = true;
$journal->save();
// forget temp journal data
// Session::forget('temporary_split_data');
Session::forget('temporary_split_data');
// this is where we originally came from.
return redirect(session('transactions.create.url'));
}
}

View File

@ -0,0 +1,85 @@
<?php
/**
* SplitJournalFormRequest.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Http\Requests;
use Auth;
use Carbon\Carbon;
/**
* Class SplitJournalFormRequest
*
* @package FireflyIII\Http\Requests
*/
class SplitJournalFormRequest extends Request
{
/**
* @return bool
*/
public function authorize(): bool
{
// Only allow logged in users
return Auth::check();
}
/**
* @return array
*/
public function getSplitData(): array
{
$data = [
'description' => $this->get('journal_description'),
'currency_id' => intval($this->get('currency')),
'source_account_id' => intval($this->get('source_account_id')),
'date' => new Carbon($this->get('date')),
'what' => $this->get('what'),
'interest_date' => $this->get('interest_date') ? new Carbon($this->get('interest_date')) : null,
'book_date' => $this->get('book_date') ? new Carbon($this->get('book_date')) : null,
'process_date' => $this->get('process_date') ? new Carbon($this->get('process_date')) : null,
'transactions' => [],
];
// description is leading because it is one of the mandatory fields.
foreach ($this->get('description') as $index => $description) {
$transaction = [
'description' => $description,
'amount' => round($this->get('amount')[$index], 2),
'budget_id' => $this->get('budget')[$index] ? $this->get('budget')[$index] : 0,
'category' => $this->get('category')[$index] ?? '',
'source_account_id' => intval($this->get('source_account_id')),
'destination_account_name' => $this->get('destination_account_name')[$index] ?? ''
];
$data['transactions'][] = $transaction;
}
return $data;
}
/**
* @return array
*/
public function rules(): array
{
return [
'journal_description' => 'required|between:1,255',
'currency' => 'required|exists:transaction_currencies,id',
'source_account_id' => 'numeric|belongsToUser:accounts,id',
'what' => 'required|in:withdrawal,deposit,transfer',
'date' => 'required|date',
'interest_date' => 'date',
'book_date' => 'date',
'process_date' => 'date',
'description.*' => 'required|between:1,255',
'destination_account_name.*' => 'between:1,255',
'amount.*' => 'required|numeric',
'budget.*' => 'belongsToUser:budgets,id',
'category.*' => 'between:1,255',
];
}
}

View File

@ -346,6 +346,7 @@ Route::group(
* Split controller
*/
Route::get('/transaction/split', ['uses' => 'Transaction\SplitController@journalFromStore', 'as' => 'split.journal.from-store']);
Route::post('/transaction/split', ['uses' => 'Transaction\SplitController@postJournalFromStore', 'as' => 'split.journal.from-store.post']);
/**
* Tag Controller

View File

@ -34,6 +34,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereActive($value)
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget whereEncrypted($value)
* @mixin \Eloquent
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions
*/
class Budget extends Model
{
@ -136,6 +137,14 @@ class Budget extends Model
return $this->belongsToMany('FireflyIII\Models\TransactionJournal', 'budget_transaction_journal', 'budget_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function transactions()
{
return $this->belongsToMany('FireflyIII\Models\Transaction', 'budget_transaction', 'budget_id');
}
/**
* @return BelongsTo
*/

View File

@ -31,6 +31,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category whereEncrypted($value)
* @mixin \Eloquent
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions
*/
class Category extends Model
{
@ -116,6 +117,13 @@ class Category extends Model
{
return $this->belongsToMany('FireflyIII\Models\TransactionJournal', 'category_transaction_journal', 'category_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function transactions()
{
return $this->belongsToMany('FireflyIII\Models\Transaction', 'category_transaction', 'category_id');
}
/**
* @return BelongsTo

View File

@ -32,6 +32,8 @@ use Watson\Validating\ValidatingTrait;
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Transaction whereDescription($value)
* @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Transaction whereAmount($value)
* @mixin \Eloquent
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Budget[] $budgets
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Category[] $categories
*/
class Transaction extends Model
{
@ -104,4 +106,20 @@ class Transaction extends Model
{
return $this->belongsTo('FireflyIII\Models\TransactionJournal');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function budgets()
{
return $this->belongsToMany('FireflyIII\Models\Budget');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function categories()
{
return $this->belongsToMany('FireflyIII\Models\Category');
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* CrudServiceProvider.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace FireflyIII\Providers;
use FireflyIII\Exceptions\FireflyException;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;
/**
* Class CrudServiceProvider
*
* @package FireflyIII\Providers
*/
class CrudServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
$this->app->bind(
'FireflyIII\Crud\Split\JournalInterface',
function (Application $app, array $arguments) {
if (!isset($arguments[0]) && $app->auth->check()) {
return app('FireflyIII\Crud\Split\Journal', [$app->auth->user()]);
}
if (!isset($arguments[0]) && !$app->auth->check()) {
throw new FireflyException('There is no user present.');
}
return app('FireflyIII\Crud\Split\Journal', $arguments);
}
);
}
}

View File

@ -233,7 +233,6 @@ class JournalRepository implements JournalRepositoryInterface
);
$journal->save();
// store or get category
if (strlen($data['category']) > 0) {
$category = Category::firstOrCreateEncrypted(['name' => $data['category'], 'user_id' => $data['user']]);
@ -403,8 +402,8 @@ class JournalRepository implements JournalRepositoryInterface
break;
case TransactionType::TRANSFER:
$sourceAccount = Account::find($data['account_from_id']);
$destinationAccount = Account::find($data['account_to_id']);
$sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_from_id'])->first();
$destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['account_to_id'])->first();
break;
default:
throw new FireflyException('Did not recognise transaction type.');
@ -439,9 +438,10 @@ class JournalRepository implements JournalRepositoryInterface
$fromAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['source_account_name'], 'active' => 1]
);
return [$fromAccount, $destinationAccount];
} else {
$fromType = AccountType::where('type', 'Cash account')->first();
$fromType = AccountType::where('type', 'Cash account')->first();
$fromAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => 'Cash account', 'active' => 1]
);

View File

@ -6,6 +6,11 @@ namespace FireflyIII\Support\Models;
use FireflyIII\Models\Tag;
use Illuminate\Database\Eloquent\Model;
/**
* FireflyIII\Support\Models\TagSupport
*
* @mixin \Eloquent
*/
class TagSupport extends Model
{
/**

View File

@ -32,6 +32,13 @@ class ChangesForV385 extends Migration
// restore backup. Change unknowns to "monthly".
$this->restoreRepeatFreqsToEnum($backup);
// drop budget <> transaction table:
Schema::dropIfExists('budget_transaction');
// drop category <> transaction table:
Schema::dropIfExists('category_transaction');
}
/**
@ -84,6 +91,30 @@ class ChangesForV385 extends Migration
$table->unique(['budget_id', 'startdate', 'repeat_freq'], 'unique_limit');
}
);
// create NEW table for transactions <> budgets
Schema::create(
'budget_transaction', function (Blueprint $table) {
$table->increments('id');
$table->integer('budget_id')->unsigned();
$table->integer('transaction_id')->unsigned();
$table->foreign('budget_id')->references('id')->on('budgets')->onDelete('cascade');
$table->foreign('transaction_id')->references('id')->on('transactions')->onDelete('cascade');
$table->unique(['budget_id', 'transaction_id'], 'budid_tid_unique');
}
);
// create NEW table for transactions <> categories
Schema::create(
'category_transaction', function (Blueprint $table) {
$table->increments('id');
$table->integer('category_id')->unsigned();
$table->integer('transaction_id')->unsigned();
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
$table->foreign('transaction_id')->references('id')->on('transactions')->onDelete('cascade');
$table->unique(['category_id', 'transaction_id'], 'catid_tid_unique');
}
);
}
/**

View File

@ -32,23 +32,6 @@ var colourSet = [
];
// Settings object that controls default parameters for library methods:
accounting.settings = {
currency: {
symbol: currencySymbol, // default currency symbol is '$'
format: "%s %v", // controls output: %s = symbol, %v = value/number (can be object: see below)
decimal: mon_decimal_point, // decimal point separator
thousand: mon_thousands_sep, // thousands separator
precision: frac_digits // decimal places
},
number: {
precision: 0, // default precision on numbers is 0
thousand: ",",
decimal: "."
}
};
var fillColors = [];
var strokePointHighColors = [];

View File

@ -98,3 +98,18 @@ function currencySelect(e) {
return false;
}
// Settings object that controls default parameters for library methods:
accounting.settings = {
currency: {
symbol: currencySymbol, // default currency symbol is '$'
format: "%s %v", // controls output: %s = symbol, %v = value/number (can be object: see below)
decimal: mon_decimal_point, // decimal point separator
thousand: mon_thousands_sep, // thousands separator
precision: frac_digits // decimal places
},
number: {
precision: 0, // default precision on numbers is 0
thousand: ",",
decimal: "."
}
};

View File

@ -0,0 +1,49 @@
/*
* from-store.js
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
/* globals globalSum */
$(function () {
"use strict";
$('.btn-do-split').click(cloneRow);
$('input[name="amount[]"]').on('input', calculateSum)
});
function cloneRow() {
"use strict";
var source = $('.initial-row').clone();
var count = $('.split-table tbody tr').length + 1;
source.removeClass('initial-row');
source.find('.count').text('#' + count);
source.find('input[name="amount[]"]').val("").on('input', calculateSum);
$('.split-table tbody').append(source);
calculateSum();
return false;
}
function calculateSum() {
"use strict";
var sum = 0;
var set = $('input[name="amount[]"]');
for (var i = 0; i < set.length; i++) {
var current = $(set[i]);
sum += (current.val() == "" ? 0 : parseFloat(current.val()));
}
console.log("Sum is now " + sum);
$('.amount-warning').remove();
if (sum != originalSum) {
console.log(sum + ' does not match ' + originalSum);
var holder = $('#amount_holder');
var par = holder.find('p.form-control-static');
var amount = $('<span>').text(' (' + accounting.formatMoney(sum) + ')').addClass('text-danger amount-warning').appendTo(par);
}
}

View File

@ -800,4 +800,16 @@ return [
'user_administration' => 'User administration',
'list_all_users' => 'All users',
'all_users' => 'All users',
// split a transaction:
'transaction_meta_data' => 'Transaction meta-data',
'transaction_dates' => 'Transaction dates',
'splits' => 'Splits',
'split_title_withdrawal' => 'Split your new withdrawal',
'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.',
'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.',
'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.',
'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.',
'add_another_split' => 'Add another split',
'store_splitted_withdrawal' => 'Store splitted withdrawal',
];

View File

@ -26,6 +26,8 @@ return [
'account_to_id' => 'To account',
'asset_destination_account' => 'Asset account (destination)',
'asset_source_account' => 'Asset account (source)',
'journal_description' => 'Description',
'currency' => 'Currency',
'account_id' => 'Asset account',
'budget_id' => 'Budget',
'openingBalance' => 'Opening balance',

View File

@ -24,6 +24,8 @@ return [
'matchesOn' => 'Matched on',
'matchingAmount' => 'Amount',
'lastMatch' => 'Last match',
'split_number' => 'Split #',
'destination' => 'Destination',
'expectedMatch' => 'Expected match',
'automatch' => 'Auto match?',
'repeat_freq' => 'Repeats',

View File

@ -4,163 +4,184 @@
{{ Breadcrumbs.renderIfExists }}
{% endblock %}
{% block content %}
<form method="POST" action="bla" accept-charset="UTF-8" class="form-horizontal" id="store" enctype="multipart/form-data">
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Split your new [type]</h3>
{{ Form.open({'class' : 'form-horizontal','id' : 'store','url' : route('split.journal.from-store.post')}) }}
<input type="hidden" name="what" value="{{ data.what }}" />
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ ('split_title_'~data.what)|_ }}</h3>
<div class="box-tools pull-right">
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
</div>
<div class="box-tools pull-right">
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
</div>
<div class="box-body">
<p>
Firefly supports the "splitting" of an [expense / withdrawal / transfer].
</div>
<div class="box-body">
<p>
{{ ('split_intro_one_'~data.what)|_ }}
</p>
<p>
{{ ('split_intro_two_'~data.what)|_ }}
</p>
<p>
{{ trans(('firefly.split_intro_three_'~data.what), {total: 20|formatAmount, split_one: 15|formatAmount, split_two: 5|formatAmount})|raw }}
</p>
<!--
<p>
Firefly supports the "splitting" of an [expense / withdrawal / transfer].
It means that the amount of money you've spent/earned/transfered is divided between
several source accounts, destination accounts or budgets.
It means that the amount of money you've spent/earned/transfered is divided between
several source accounts, destination accounts or budgets.
Example for withdrawals: split your 20,- groceries so you pay 15,- from your "daily groceries" budget
and 5,- from your "cigarettes" budget.
Example for withdrawals: split your 20,- groceries so you pay 15,- from your "daily groceries" budget
and 5,- from your "cigarettes" budget.
Example for deposits: split your 500,- salary so that 450,- is categorized "salary" and 50,- is categorized "reimbursed expenses".
Example for deposits: split your 500,- salary so that 450,- is categorized "salary" and 50,- is categorized "reimbursed expenses".
Example for transfers: split your 100,- transfer so that 50,- is put in the piggy bank "new couch" and 50,- is not.
</p>
</div>
Example for transfers: split your 100,- transfer so that 50,- is put in the piggy bank "new couch" and 50,- is not.
</p>
-->
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Transaction meta-data</h3>
</div>
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'transaction_meta_data'|_ }}</h3>
<div class="box-tools pull-right">
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
</div>
</div>
<div class="box-body">
{{ ExpandedForm.text('journal_description', data.description) }}
{{ ExpandedForm.staticText('amount', data.amount|formatAmount) }}
<!-- show static source if withdrawal or transfer -->
{% if data.what == 'withdrawal' or data.what == 'transfer' %}
{{ ExpandedForm.staticText('asset_source_account', assetAccounts[data.source_account_id]) }}
{% endif %}
<!-- show static destination if deposit or transfer -->
{% if data.what == 'deposit' or data.what == 'transfer' %}
{{ ExpandedForm.staticText('asset_destination_account', assetAccounts[data.destination_account_id]) }}
{% endif %}
<div class="box-tools pull-right">
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
</div>
</div>
</div>
<div class="col-lg-6 col-md-6 col-sm-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Transaction dates</h3>
<div class="box-tools pull-right">
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
</div>
</div>
<div class="box-body">
{{ ExpandedForm.date('date', data.date) }}
{{ ExpandedForm.date('interest_date', data.interest_date) }}
{{ ExpandedForm.date('book_date', data.book_date) }}
{{ ExpandedForm.date('process_date', data.process_date) }}
</div>
<div class="box-body">
{{ ExpandedForm.text('journal_description', data.description) }}
{{ ExpandedForm.select('currency', currencies, data.amount_currency_id_amount) }}
{{ ExpandedForm.staticText('amount', data.amount|formatAmount) }}
<!-- show static source if withdrawal or transfer -->
{% if data.what == 'withdrawal' or data.what == 'transfer' %}
{{ ExpandedForm.staticText('asset_source_account', assetAccounts[data.source_account_id]) }}
<input type="hidden" name="source_account_id" value="{{ data.source_account_id }}"/>
{% endif %}
<!-- show static destination if deposit or transfer -->
{% if data.what == 'deposit' or data.what == 'transfer' %}
{{ ExpandedForm.staticText('asset_destination_account', assetAccounts[data.destination_account_id]) }}
<input type="hidden" name="destination_account_id" value="{{ data.destination_account_id }}"/>
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Splits</h3>
<div class="col-lg-6 col-md-6 col-sm-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'transaction_dates'|_ }}</h3>
<div class="box-tools pull-right">
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
</div>
<div class="box-tools pull-right">
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
</div>
<div class="box-body">
<p>
Split your [x] in as many things as you want. By default the transaction will not split, there is just one entry.
Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you
do, Firefly will warn you but not correct you.
</p>
<table class="table">
<thead>
<tr>
<th>Split #</th>
<th>Description</th>
<!-- split the source of a deposit -->
{% if data.what == 'deposit' %}
<th>Source</th>
{% endif %}
<!-- split where a withdrawal is going -->
{% if data.what == 'withdrawal' %}
<th>Destination</th>
{% endif %}
<th>Amount</th>
{% if data.what == 'withdrawal' %}
<th>Budget</th>
{% endif %}
<th>Category</th>
{% if data.what == 'transfer' %}
<th>Piggy bank</th>
{% endif %}
</tr>
</thead>
<tbody>
<tr class="initial-row">
<td>#1</td>
<td>{{ Form.input('text', 'description[]', data.description, {class: 'form-control'}) }}</td>
{% if data.what == 'deposit' %}
<td>
{{ Form.input('text', 'source_account_name[]', data.source_account_name, {class: 'form-control'}) }}
</td>
{% endif %}
{% if data.what == 'withdrawal' %}
<td>
{{ Form.input('text', 'destination_account_name[]', data.destination_account_name, {class: 'form-control'}) }}
</td>
{% endif %}
<td style="width:10%;">
{{ ExpandedForm.amountSmall('amount[]', data.amount) }}
</td>
{% if data.what == 'withdrawal' %}
<td>
{{ Form.select('budget[]', budgets, data.budget_id, {class: 'form-control'}) }}
</td>
{% endif %}
</div>
<div class="box-body">
{{ ExpandedForm.date('date', data.date) }}
{{ ExpandedForm.date('interest_date', data.interest_date) }}
{{ ExpandedForm.date('book_date', data.book_date) }}
{{ ExpandedForm.date('process_date', data.process_date) }}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'splits'|_ }}</h3>
<div class="box-tools pull-right">
<button class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
</div>
</div>
<div class="box-body">
<p>
{{ ('split_table_intro_'~data.what)|_ }}
</p>
<table class="table table-bordered table-condensed table-striped split-table">
<thead>
<tr>
<th>{{ trans('list.split_number') }}</th>
<th>{{ trans('list.description') }}</th>
<!-- split the source of a deposit -->
{% if data.what == 'deposit' %}
<th>{{ trans('list.source') }}</th>
{% endif %}
<!-- split where a withdrawal is going -->
{% if data.what == 'withdrawal' %}
<th>{{ trans('list.destination') }}</th>
{% endif %}
<th>{{ trans('list.amount') }}</th>
{% if data.what == 'withdrawal' %}
<th>{{ trans('list.budget') }}</th>
{% endif %}
<th>{{ trans('list.category') }}</th>
{% if data.what == 'transfer' %}
<th>{{ trans('list.piggy_bank') }}</th>
{% endif %}
</tr>
</thead>
<tbody>
<tr class="initial-row">
<td class="count">#1</td>
<td>{{ Form.input('text', 'description[]', data.description, {class: 'form-control'}) }}</td>
{% if data.what == 'deposit' %}
<td>
{{ Form.input('text', 'category[]', data.category, {class: 'form-control'}) }}
{{ Form.input('text', 'source_account_name[]', data.source_account_name, {class: 'form-control'}) }}
</td>
</tr>
</tbody>
</table>
<p>
<a href="#" class="btn btn-success btn-do-split"><i class="fa fa-plus-circle"></i> Add another split</a>
</p>
</div>
{% endif %}
{% if data.what == 'withdrawal' %}
<td>
{{ Form.input('text', 'destination_account_name[]', data.destination_account_name, {class: 'form-control'}) }}
</td>
{% endif %}
<td style="width:10%;">
{{ Form.input('number', 'amount[]', data.amount, {class: 'form-control', autocomplete: 'off', step: 'any', min:'0.01'}) }}
</td>
{% if data.what == 'withdrawal' %}
<td>
{{ Form.select('budget[]', budgets, data.budget_id, {class: 'form-control'}) }}
</td>
{% endif %}
<td>
{{ Form.input('text', 'category[]', data.category, {class: 'form-control'}) }}
</td>
</tr>
</tbody>
</table>
<p>
<br/>
<a href="#" class="btn btn-default btn-do-split"><i class="fa fa-plus-circle"></i> {{ 'add_another_split'|_ }}</a>
</p>
<p class="pull-right">
<button type="submit" id="transaction-btn" class="btn btn-success pull-right">
{{ ('store_splitted_'~data.what)|_ }}
</button>
</p>
</div>
</div>
</div>
</div>
</form>
{% endblock %}
{% block scripts %}
<script type="text/javascript">
var originalSum = {{ data.amount }};
var what = "{{ data.what }}";
</script>
<script type="text/javascript" src="js/lib/bootstrap3-typeahead.min.js"></script>
<script type="text/javascript" src="js/ff/transactions/create-edit.js"></script>
<script type="text/javascript" src="js/ff/split/journal/from-store.js"></script>
{% endblock %}
{% block styles %}
{% endblock %}