Lots of new code for new transaction screen.

This commit is contained in:
James Cole 2019-05-04 20:58:11 +02:00
parent 912fe99981
commit d5c5fa4fad
33 changed files with 55416 additions and 128 deletions

View File

@ -45,7 +45,7 @@ echo "Discover packages..."
php artisan package:discover
echo "Run various artisan commands..."
. $FIREFLY_PATH/.env
#. $FIREFLY_PATH/.env
if [[ -z "$DB_PORT" ]]; then
if [[ $DB_CONNECTION == "pgsql" ]]; then
DB_PORT=5432

View File

@ -543,6 +543,15 @@ return [
TransactionTypeModel::OPENING_BALANCE => [AccountType::INITIAL_BALANCE, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT,
AccountType::MORTGAGE],
TransactionTypeModel::RECONCILIATION => [AccountType::RECONCILIATION, AccountType::ASSET],
// in case no transaction type is known yet, it could be anything.
'none' => [
AccountType::ASSET,
AccountType::EXPENSE,
AccountType::REVENUE,
AccountType::LOAN,
AccountType::DEBT,
AccountType::MORTGAGE,
],
],
'destination' => [
TransactionTypeModel::WITHDRAWAL => [AccountType::EXPENSE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT,
@ -554,6 +563,127 @@ return [
TransactionTypeModel::RECONCILIATION => [AccountType::RECONCILIATION, AccountType::ASSET],
],
],
'allowed_opposing_types' => [
'source' => [
AccountType::ASSET => [AccountType::ASSET, AccountType::CASH, AccountType::DEBT, AccountType::EXPENSE, AccountType::INITIAL_BALANCE,
AccountType::LOAN, AccountType::RECONCILIATION],
AccountType::CASH => [AccountType::ASSET],
AccountType::DEBT => [AccountType::ASSET, AccountType::DEBT, AccountType::EXPENSE, AccountType::INITIAL_BALANCE, AccountType::LOAN,
AccountType::MORTGAGE],
AccountType::EXPENSE => [], // is not allowed as a source.
AccountType::INITIAL_BALANCE => [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE],
AccountType::LOAN => [AccountType::ASSET, AccountType::DEBT, AccountType::EXPENSE, AccountType::INITIAL_BALANCE, AccountType::LOAN,
AccountType::MORTGAGE],
AccountType::MORTGAGE => [AccountType::ASSET, AccountType::DEBT, AccountType::EXPENSE, AccountType::INITIAL_BALANCE, AccountType::LOAN,
AccountType::MORTGAGE],
AccountType::RECONCILIATION => [AccountType::ASSET],
AccountType::REVENUE => [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE],
],
'destination' => [
AccountType::ASSET => [AccountType::ASSET, AccountType::CASH, AccountType::DEBT, AccountType::INITIAL_BALANCE, AccountType::LOAN,
AccountType::MORTGAGE, AccountType::RECONCILIATION, AccountType::REVENUE],
AccountType::CASH => [AccountType::ASSET],
AccountType::DEBT => [AccountType::ASSET, AccountType::DEBT, AccountType::INITIAL_BALANCE, AccountType::LOAN, AccountType::MORTGAGE,
AccountType::REVENUE],
AccountType::EXPENSE => [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE],
AccountType::INITIAL_BALANCE => [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE],
AccountType::LOAN => [AccountType::ASSET, AccountType::DEBT, AccountType::INITIAL_BALANCE, AccountType::LOAN, AccountType::MORTGAGE,
AccountType::REVENUE],
AccountType::MORTGAGE => [AccountType::ASSET, AccountType::DEBT, AccountType::INITIAL_BALANCE, AccountType::LOAN, AccountType::MORTGAGE,
AccountType::REVENUE],
AccountType::RECONCILIATION => [AccountType::ASSET],
AccountType::REVENUE => [], // is not allowed as a destination
],
],
// depending on the account type, return the allowed transaction types:
'allowed_transaction_types' => [
'source' => [
AccountType::ASSET => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::TRANSFER, TransactionTypeModel::OPENING_BALANCE,
TransactionTypeModel::RECONCILIATION],
AccountType::EXPENSE => [], // is not allowed as a source.
AccountType::REVENUE => [TransactionTypeModel::DEPOSIT],
AccountType::LOAN => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER,
TransactionTypeModel::OPENING_BALANCE],
AccountType::DEBT => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER,
TransactionTypeModel::OPENING_BALANCE],
AccountType::MORTGAGE => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER,
TransactionTypeModel::OPENING_BALANCE],
AccountType::INITIAL_BALANCE => [], // todo fill me in.
AccountType::RECONCILIATION => [], // todo fill me in.
],
'destination' => [
AccountType::ASSET => [TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER, TransactionTypeModel::OPENING_BALANCE,
TransactionTypeModel::RECONCILIATION],
AccountType::EXPENSE => [TransactionTypeModel::WITHDRAWAL],
AccountType::REVENUE => [], // is not allowed as destination.
AccountType::LOAN => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER,
TransactionTypeModel::OPENING_BALANCE],
AccountType::DEBT => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER,
TransactionTypeModel::OPENING_BALANCE],
AccountType::MORTGAGE => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER,
TransactionTypeModel::OPENING_BALANCE],
AccountType::INITIAL_BALANCE => [], // todo fill me in.
AccountType::RECONCILIATION => [], // todo fill me in.
],
],
// having the source + dest will tell you the transaction type.
'account_to_transaction' => [
AccountType::ASSET => [
AccountType::ASSET => TransactionTypeModel::TRANSFER,
AccountType::CASH => TransactionTypeModel::WITHDRAWAL,
AccountType::DEBT => TransactionTypeModel::WITHDRAWAL,
AccountType::EXPENSE => TransactionTypeModel::WITHDRAWAL,
AccountType::INITIAL_BALANCE => TransactionTypeModel::OPENING_BALANCE,
AccountType::LOAN => TransactionTypeModel::WITHDRAWAL,
AccountType::MORTGAGE => TransactionTypeModel::WITHDRAWAL,
AccountType::RECONCILIATION => TransactionTypeModel::RECONCILIATION,
],
AccountType::CASH => [
AccountType::ASSET => TransactionTypeModel::DEPOSIT,
],
AccountType::DEBT => [
AccountType::ASSET => TransactionTypeModel::DEPOSIT,
AccountType::DEBT => TransactionTypeModel::TRANSFER,
AccountType::EXPENSE => TransactionTypeModel::WITHDRAWAL,
AccountType::INITIAL_BALANCE => TransactionTypeModel::OPENING_BALANCE,
AccountType::LOAN => TransactionTypeModel::TRANSFER,
AccountType::MORTGAGE => TransactionTypeModel::TRANSFER,
],
AccountType::INITIAL_BALANCE => [
AccountType::ASSET => TransactionTypeModel::OPENING_BALANCE,
AccountType::DEBT => TransactionTypeModel::OPENING_BALANCE,
AccountType::LOAN => TransactionTypeModel::OPENING_BALANCE,
AccountType::MORTGAGE => TransactionTypeModel::OPENING_BALANCE,
],
AccountType::LOAN => [
AccountType::ASSET => TransactionTypeModel::DEPOSIT,
AccountType::DEBT => TransactionTypeModel::TRANSFER,
AccountType::EXPENSE => TransactionTypeModel::WITHDRAWAL,
AccountType::INITIAL_BALANCE => TransactionTypeModel::OPENING_BALANCE,
AccountType::LOAN => TransactionTypeModel::TRANSFER,
AccountType::MORTGAGE => TransactionTypeModel::TRANSFER,
],
AccountType::MORTGAGE => [
AccountType::ASSET => TransactionTypeModel::DEPOSIT,
AccountType::DEBT => TransactionTypeModel::TRANSFER,
AccountType::EXPENSE => TransactionTypeModel::WITHDRAWAL,
AccountType::INITIAL_BALANCE => TransactionTypeModel::OPENING_BALANCE,
AccountType::LOAN => TransactionTypeModel::TRANSFER,
AccountType::MORTGAGE => TransactionTypeModel::TRANSFER,
],
AccountType::RECONCILIATION => [
AccountType::ASSET => TransactionTypeModel::RECONCILIATION,
],
AccountType::REVENUE => [
AccountType::ASSET => TransactionTypeModel::DEPOSIT,
AccountType::DEBT => TransactionTypeModel::DEPOSIT,
AccountType::LOAN => TransactionTypeModel::DEPOSIT,
AccountType::MORTGAGE => TransactionTypeModel::DEPOSIT,
],
],
// allowed source / destination accounts.
'source_dests' => [

View File

@ -13,12 +13,14 @@
"axios": "^0.17",
"bootstrap-sass": "^3.3.7",
"cross-env": "^5.1",
"jquery": "^3.1.1",
"laravel-mix": "^1.0",
"lodash": "^4.17.4",
"vue": "^2.5.7"
},
"dependencies": {
"font-awesome": "^4.7.0"
"@johmun/vue-tags-input": "^2.0.1",
"font-awesome": "^4.7.0",
"jquery": "^3.1.1",
"uiv": "^0.31.5"
}
}

View File

@ -1,7 +1,3 @@
{
"/v2/js/index.js": "/v2/js/index.js",
"/v2/js/manifest.js": "/v2/js/manifest.js",
"/v2/js/vendor.js": "/v2/js/vendor.js",
"/undefined.js": "/undefined.js",
"/v2/css/app.css": "/v2/css/app.css"
"/v1/js/app.js": "/v1/js/app.js"
}

2
public/v1/css/app.css vendored Normal file
View File

@ -0,0 +1,2 @@
/* TODO REMOVE ME */

View File

@ -18,6 +18,14 @@
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
input.ti-new-tag-input {
font-size: 14px !important;
line-height: 1.42857143;
color: #555;
font-family:"Source Sans Pro", "Helvetica Neue",Helvetica,Arial,sans-serif !important;
}
.split_amount_input {
width: 40%;
border-radius: 0;
@ -36,6 +44,13 @@
}
.autocomplete-suggestions { border: 1px solid #999; background: #FFF; overflow: auto; }
.autocomplete-suggestion { padding: 2px 5px; white-space: nowrap; overflow: hidden; }
.autocomplete-selected { background: #F0F0F0; }
.autocomplete-suggestions strong { font-weight: normal; color: #3399FF; }
.autocomplete-group { padding: 2px 5px; font-weight: bold;}
.autocomplete-group strong { display: block; border-bottom: 1px solid #000; }
.split_amount_input:focus {
border-color: #98cbe8;
outline: 0;

53455
public/v1/js/app.js vendored

File diff suppressed because one or more lines are too long

225
public/v1/js/ff/transactions/create.js vendored Normal file
View File

@ -0,0 +1,225 @@
/*
* create.js
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
/** global: autoCompleteUri */
$(function () {
"use strict";
initPage();
});
function initPage() {
// recreate buttons and auto-complete things
autoComplete();
makeButtons();
runModernizer();
}
/**
* Reset all click triggers.
*/
function makeButtons() {
$('.clearDestination').unbind('click').on('click', clearDestination);
$('.clearSource').unbind('click').on('click', clearSource);
$('#addSplitButton').unbind('click').on('click', addSplit);
}
function addSplit() {
// clone the latest
var latest =$($('#transactions').children()[$('#transactions').children().length - 1]);
latest.clone(true).appendTo('#transactions');
initPage();
return false;
}
/**
* Code to handle clearing the source account.
* @param e
*/
function clearSource(e) {
console.log('Now clearing source.');
var button = $(e.currentTarget);
// empty value.
$(button.parent().parent().find('input').get(0)).val('');
// reset source account
setSourceAccount(null);
}
/**
* Code to handle clearing the destination account.
* @param e
*/
function clearDestination(e) {
console.log('Now clearing destination.');
var button = $(e.currentTarget);
// empty value.
$(button.parent().parent().find('input').get(0)).val('');
// reset destination account
setDestinationAccount(null);
}
/**
* Set the new source account (from a suggestion).
*
* @param newAccount
*/
function setSourceAccount(newAccount) {
if (null === newAccount) {
console.log('New source account is now null.');
sourceAccount = null;
setAllowedDestinationAccounts(newAccount);
return;
}
console.log('The new source account is now ' + newAccount.value + 'of type ' + newAccount.data.type);
setAllowedDestinationAccounts(newAccount);
sourceAccount = newAccount;
setTransactionType();
}
/**
* Set the new destination account (from a suggestion).
*
* @param newAccount
*/
function setDestinationAccount(newAccount) {
if (null === newAccount) {
console.log('New destination account is now null.');
destinationAccount = null;
setAllowedSourceAccounts(newAccount);
return;
}
console.log('The new destination account is now ' + newAccount.value + 'of type ' + newAccount.data.type);
setAllowedSourceAccounts(newAccount);
sourceAccount = newAccount;
setTransactionType();
}
/**
* Set a new limit on the allowed destination account.
*
* @param newAccount
*/
function setAllowedDestinationAccounts(newAccount) {
if (null === newAccount) {
console.log('Allowed type for destination account is anything.');
destAllowedAccountTypes = [];
return;
}
destAllowedAccountTypes = allowedOpposingTypes.source[newAccount.data.type];
console.log('The destination account must be of type: ', destAllowedAccountTypes);
// todo if the current destination account is not of this type, reset it.
}
/**
* Set a new limit on the allowed destination account.
*
* @param newAccount
*/
function setAllowedSourceAccounts(newAccount) {
if (null === newAccount) {
console.log('Allowed type for source account is anything.');
sourceAllowedAccountTypes = [];
return;
}
sourceAllowedAccountTypes = allowedOpposingTypes.source[newAccount.data.type];
console.log('The source account must be of type: ', sourceAllowedAccountTypes);
// todo if the current destination account is not of this type, reset it.
}
/**
* Create auto complete.
*/
function autoComplete() {
var options = {
serviceUrl: getSourceAutoCompleteURI,
groupBy: 'type',
onSelect: function (suggestion) {
setSourceAccount(suggestion);
}
};
$('.sourceAccountAC').autocomplete(options);
// also select destination account.
var destinationOptions = {
serviceUrl: getDestinationAutoCompleteURI,
groupBy: 'type',
onSelect: function (suggestion) {
setDestinationAccount(suggestion);
}
};
$('.destinationAccountAC').autocomplete(destinationOptions);
}
function setTransactionType() {
if (sourceAccount === undefined || destinationAccount === undefined || sourceAccount === null || destinationAccount === null) {
$('.transactionTypeIndicator').text('');
$('.transactionTypeIndicatorBlock').hide();
console.warn('Not both accounts are known yet.');
return;
}
$('.transactionTypeIndicatorBlock').show();
var expectedType = accountToTypes[sourceAccount.data.type][destinationAccount.data.type];
$('.transactionTypeIndicator').html(creatingTypes[expectedType]);
console.log('Expected transaction type is ' + expectedType);
}
/**
* Returns the auto complete URI for source accounts.
* @returns {string}
*/
function getSourceAutoCompleteURI() {
console.log('Will filter source accounts', sourceAllowedAccountTypes);
return accountAutoCompleteURI + '?types=' + encodeURI(sourceAllowedAccountTypes.join(','));
}
/**
* Returns the auto complete URI for destination accounts.
* @returns {string}
*/
function getDestinationAutoCompleteURI() {
console.log('Will filter destination accounts', destAllowedAccountTypes);
return accountAutoCompleteURI + '?types=' + encodeURI(destAllowedAccountTypes.join(','));
}
/**
* Give date a datepicker if not natively supported.
*/
function runModernizer() {
if (!Modernizr.inputtypes.date) {
$('input[type="date"]').datepicker(
{
dateFormat: 'yy-mm-dd'
}
);
}
}

8
public/v1/js/lib/jquery.autocomplete.min.js vendored Executable file

File diff suppressed because one or more lines are too long

View File

@ -3,10 +3,26 @@
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
/* TODO REMOVE ME */
require('./bootstrap');
window.Vue = require('vue');
import * as uiv from 'uiv';
Vue.use(uiv);
// components for create and edit transactions.
Vue.component('budget', require('./components/transactions/Budget.vue'));
Vue.component('custom-transaction-fields', require('./components/transactions/CustomTransactionFields.vue'));
Vue.component('piggy-bank', require('./components/transactions/PiggyBank.vue'));
Vue.component('tags', require('./components/transactions/Tags.vue'));
Vue.component('category', require('./components/transactions/Category.vue'));
Vue.component('amount', require('./components/transactions/Amount.vue'));
Vue.component('foreign-amount', require('./components/transactions/ForeignAmountSelect.vue'));
Vue.component('transaction-type', require('./components/transactions/TransactionType.vue'));
Vue.component('account-select', require('./components/transactions/AccountSelect.vue'));
/**
* Components for OAuth2 tokens.
@ -14,6 +30,7 @@ window.Vue = require('vue');
Vue.component('passport-clients', require('./components/passport/Clients.vue'));
Vue.component('passport-authorized-clients', require('./components/passport/AuthorizedClients.vue'));
Vue.component('passport-personal-access-tokens', require('./components/passport/PersonalAccessTokens.vue'));
Vue.component('create-transaction', require('./components/transactions/CreateTransaction'));
const app = new Vue({

View File

@ -0,0 +1,160 @@
<!--
- AccountSelect.vue
- Copyright (c) 2019 thegrumpydictator@gmail.com
-
- This file is part of Firefly III.
-
- Firefly III is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Firefly III is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group">
<div class="col-sm-12">
<div class="input-group">
<input
ref="input"
type="text"
:placeholder="title"
:data-index="index"
autocomplete="off"
data-role="input"
v-on:keypress="handleEnter"
:disabled="inputDisabled"
class="form-control"
v-on:submit.prevent
:name="inputName"
:title="title">
<span class="input-group-btn">
<button
v-on:click="clearSource"
class="btn btn-default"
type="button"><i class="fa fa-trash-o"></i></button>
</span>
</div>
<typeahead
:open-on-empty=true
:open-on-focus=true
v-on:input="selectedItem"
:async-src="accountAutoCompleteURI"
v-model="name"
:target="target"
item-key="name"
></typeahead>
</div>
</div>
</template>
<script>
export default {
props: {
inputName: String,
title: String,
index: Number,
transactionType: String,
accountName: {
type: String,
default: ''
},
accountTypeFilters: {
type: Array,
default: function () {
return [];
}
}
},
data() {
return {
accountAutoCompleteURI: null,
name: null,
trType: this.transactionType,
target: null,
inputDisabled: false,
allowedTypes: this.accountTypeFilters
}
},
ready() {
this.name = this.accountName;
},
mounted() {
this.target = this.$refs.input;
let types = this.allowedTypes.join(',');
this.name = this.accountName;
this.accountAutoCompleteURI = document.getElementsByTagName('base')[0].href + "json/accounts?types=" + types + "&query=";
this.triggerTransactionType();
},
watch: {
transactionType() {
this.triggerTransactionType();
},
accountTypeFilters() {
let types = this.accountTypeFilters.join(',');
console.log(this.inputName + '[' + this.index + '] is now searching for: ' + types);
this.accountAutoCompleteURI = document.getElementsByTagName('base')[0].href + "json/accounts?types=" + types + "&query=";
}
},
methods:
{
triggerTransactionType: function () {
if (null === this.transactionType) {
return;
}
this.inputDisabled = false;
if (this.transactionType.toString() !== '' && this.index > 0) {
if (this.transactionType.toString() === 'Transfer') {
this.inputDisabled = true;
// todo: needs to copy value from very first input
return;
}
if (this.transactionType.toString() === 'Withdrawal' && this.inputName.substr(0, 6).toLowerCase() === 'source') {
// todo also clear value?
this.inputDisabled = true;
return;
}
if (this.transactionType.toString() === 'Deposit' && this.inputName.substr(0, 11).toLowerCase() === 'destination') {
// todo also clear value?
this.inputDisabled = true;
}
}
},
selectedItem: function (e) {
if (typeof this.name === 'undefined') {
return;
}
// emit the fact that the user selected a type of account
// (influencing the destination)
this.$emit('select:account', this.name);
},
clearSource: function (e) {
//props.value = '';
this.name = '';
// some event?
this.$emit('clear:value')
},
handleEnter: function (e) {
// todo feels sloppy
if (e.keyCode === 13) {
e.preventDefault();
}
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,76 @@
<!--
- Amount.vue
- Copyright (c) 2019 thegrumpydictator@gmail.com
-
- This file is part of Firefly III.
-
- Firefly III is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Firefly III is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group">
<label class="col-sm-4 control-label" ref="cur"></label>
<div class="col-sm-8">
<input type="number" step="any" class="form-control" name="amount[]"
title="amount" autocomplete="off" placeholder="Amount">
</div>
</div>
</template>
<script>
export default {
name: "Amount",
props: ['source', 'destination', 'transactionType'],
data() {
return {
sourceAccount: this.source,
destinationAccount: this.destination,
type: this.transactionType,
}
},
methods: {
changeData: function () {
if ('' === this.transactionType) {
$(this.$refs.cur).text(this.sourceAccount.currency_name);
return;
}
if (this.transactionType === 'Withdrawal' || this.transactionType === 'Transfer') {
$(this.$refs.cur).text(this.sourceAccount.currency_name);
return;
}
if (this.transactionType === 'Deposit') {
$(this.$refs.cur).text(this.destinationAccount.currency_name);
}
}
},
watch: {
source: function () {
this.changeData();
},
destination: function () {
this.changeData();
},
transactionType: function () {
this.changeData();
}
},
mounted() {
this.changeData();
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,66 @@
<!--
- Budget.vue
- Copyright (c) 2019 thegrumpydictator@gmail.com
-
- This file is part of Firefly III.
-
- Firefly III is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Firefly III is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group" v-if="typeof this.transactionType !== 'undefined' && this.transactionType === 'Withdrawal'">
<div class="col-sm-12">
<select name="budget[]" class="form-control" v-if="this.budgets.length > 0">
<option v-for="budget in this.budgets">{{budget.name}}</option>
</select>
</div>
</div>
</template>
<script>
export default {
name: "Budget",
props: ['transactionType'],
mounted() {
this.loadBudgets();
},
data() {
return {
budgets: [],
}
},
methods: {
loadBudgets: function () {
let URI = document.getElementsByTagName('base')[0].href + "json/budgets";
axios.get(URI, {}).then((res) => {
this.budgets = [
{
name: '(no budget)',
id: 0,
}
];
for (const key in res.data) {
if (res.data.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
this.budgets.push(res.data[key]);
}
}
});
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,108 @@
<!--
- Category.vue
- Copyright (c) 2019 thegrumpydictator@gmail.com
-
- This file is part of Firefly III.
-
- Firefly III is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Firefly III is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group">
<div class="col-sm-12">
<div class="input-group">
<input
ref="input"
type="text"
placeholder="Category"
autocomplete="off"
data-role="input"
v-on:keypress="handleEnter"
class="form-control"
v-on:submit.prevent
name="category[]"
title="Category">
<span class="input-group-btn">
<button
v-on:click="clearCategory"
class="btn btn-default"
type="button"><i class="fa fa-trash-o"></i></button>
</span>
</div>
<typeahead
:open-on-empty=true
:open-on-focus=true
v-on:input="selectedItem"
:async-src="categoryAutoCompleteURI"
v-model="name"
:target="target"
item-key="name"
></typeahead>
</div>
</div>
</template>
<script>
export default {
name: "Category",
props: {
inputName: String,
accountName: {
type: String,
default: ''
},
},
data() {
return {
categoryAutoCompleteURI: null,
name: null,
target: null,
}
},
ready() {
this.name = this.accountName;
},
mounted() {
this.target = this.$refs.input;
this.categoryAutoCompleteURI = document.getElementsByTagName('base')[0].href + "json/categories?query=";
//this.triggerTransactionType();
},
methods: {
clearCategory: function () {
//props.value = '';
this.name = '';
// some event?
this.$emit('clear:category')
},
selectedItem: function (e) {
if (typeof this.name === 'undefined') {
return;
}
// emit the fact that the user selected a type of account
// (influencing the destination)
this.$emit('select:category', this.name);
},
handleEnter: function (e) {
// todo feels sloppy
if (e.keyCode === 13) {
e.preventDefault();
}
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,301 @@
<!--
- CreateTransaction.vue
- Copyright (c) 2019 thegrumpydictator@gmail.com
-
- This file is part of Firefly III.
-
- Firefly III is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Firefly III is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<form method="POST" action="xxxx" accept-charset="UTF-8" class="form-horizontal" id="store" enctype="multipart/form-data">
<input name="_token" type="hidden" value="xxx">
<div class="row" v-if="transactions.transactions.length > 1">
<div class="col-lg-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">
Description of the split transaction
</h3>
</div>
<div class="box-body">
<div class="form-group">
<div class="col-sm-12">
<input type="text" class="form-control" name="group_title"
v-model="transactions.group_title"
title="Description of the split transaction" autocomplete="off" placeholder="Description of the split transaction">
<p class="help-block">
If you create a split transaction, there must be a global description for all splits of the transaction.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-for="(transaction, index) in transactions.transactions">
<div class="row">
<div class="col-lg-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title splitTitle">
<span v-if="transactions.transactions.length > 1">Split {{ index+1 }} / {{ transactions.transactions.length }}</span>
<span v-if="transactions.transactions.length === 1">Transaction information</span>
</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-lg-4">
<account-select
inputName="source[]"
title="Source account"
:accountName="transaction.source_account.name"
:accountTypeFilters="transaction.source_account.allowed_types"
:transactionType="transactionType"
:index="index"
v-on:clear:value="clearSource(index)"
v-on:select:account="selectedSourceAccount(index, $event)"
></account-select>
<account-select
inputName="destination[]"
title="Destination account"
:accountName="transaction.destination_account.name"
:accountTypeFilters="transaction.destination_account.allowed_types"
:transactionType="transactionType"
:index="index"
v-on:clear:value="clearDestination(index)"
v-on:select:account="selectedDestinationAccount(index, $event)"
></account-select>
<div class="form-group">
<div class="col-sm-12">
<input type="text" class="form-control" name="description[]"
:value="transaction.description"
title="Description" autocomplete="off" placeholder="Description">
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<input type="date" class="form-control" name="date[]"
title="Date" value="" autocomplete="off"
:value="transaction.date"
:disabled="index > 0"
placeholder="Date">
</div>
</div>
<div v-if="index===0">
<transaction-type
:source="transaction.source_account.type"
:destination="transaction.destination_account.type"
v-on:set:transactionType="setTransactionType($event)"
v-on:act:limitSourceType="limitSourceType($event)"
v-on:act:limitDestinationType="limitDestinationType($event)"
></transaction-type>
</div>
</div>
<div class="col-lg-4">
<amount
:source="transaction.source_account"
:destination="transaction.destination_account"
:transactionType="transactionType"
></amount>
<foreign-amount
:source="transaction.source_account"
:destination="transaction.destination_account"
:transactionType="transactionType"
></foreign-amount>
</div>
<div class="col-lg-4">
<budget :transactionType="transactionType"></budget>
<category :transactionType="transactionType"></category>
<piggy-bank :transactionType="transactionType"></piggy-bank>
<tags></tags>
<!-- custom string fields -->
<custom-transaction-fields></custom-transaction-fields>
<!-- custom date fields -->
<!-- custom other fields -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<p>
<button class="btn btn-primary" v-on:click="addTransaction">Add another split</button>
</p>
</div>
</div>
</form>
</template>
<script>
export default {
name: "CreateTransaction",
components: {
},
mounted() {
// not sure if something needs to happen here.
},
ready() {
},
methods: {
addTransaction: function (e) {
let latest = this.transactions.transactions[this.transactions.transactions.length - 1];
this.transactions.transactions.push(latest);
e.preventDefault();
},
setTransactionType: function (type) {
this.transactionType = type;
},
limitSourceType: function (type) {
let i;
for (i = 0; i < this.transactions.transactions.length; i++) {
this.transactions.transactions[i].source_account.allowed_types = [type];
}
},
limitDestinationType: function (type) {
let i;
for (i = 0; i < this.transactions.transactions.length; i++) {
this.transactions.transactions[i].destination_account.allowed_types = [type];
}
},
selectedSourceAccount: function (index, model) {
if (typeof model === 'string') {
// cant change types, only name.
this.transactions.transactions[index].source_account.name = model;
} else {
// todo maybe replace the entire model?
this.transactions.transactions[index].source_account.id = model.id;
this.transactions.transactions[index].source_account.name = model.name;
this.transactions.transactions[index].source_account.type = model.type;
this.transactions.transactions[index].source_account.currency_id = model.currency_id;
this.transactions.transactions[index].source_account.currency_name = model.currency_name;
this.transactions.transactions[index].source_account.currency_code = model.currency_code;
this.transactions.transactions[index].source_account.currency_decimal_places = model.currency_decimal_places;
// force types on destination selector.
this.transactions.transactions[index].destination_account.allowed_types = window.allowedOpposingTypes.source[model.type];
}
},
selectedDestinationAccount: function (index, model) {
if (typeof model === 'string') {
// cant change types, only name.
this.transactions.transactions[index].destination_account.name = model;
} else {
// todo maybe replace the entire model?
this.transactions.transactions[index].destination_account.id = model.id;
this.transactions.transactions[index].destination_account.name = model.name;
this.transactions.transactions[index].destination_account.type = model.type;
this.transactions.transactions[index].destination_account.currency_id = model.currency_id;
this.transactions.transactions[index].destination_account.currency_name = model.currency_name;
this.transactions.transactions[index].destination_account.currency_code = model.currency_code;
this.transactions.transactions[index].destination_account.currency_decimal_places = model.currency_decimal_places;
// force types on destination selector.
this.transactions.transactions[index].source_account.allowed_types = window.allowedOpposingTypes.destination[model.type];
}
},
clearSource: function (index) {
this.transactions.transactions[index].source_account.id = 0;
this.transactions.transactions[index].source_account.name = "";
this.transactions.transactions[index].source_account.type = "";
this.transactions.transactions[index].destination_account.allowed_types = [];
// if there is a destination model, reset the types of the source
// by pretending we selected it again.
if (this.transactions.transactions[index].destination_account) {
console.log('There is a destination account.');
this.selectedDestinationAccount(index, this.transactions.transactions[index].destination_account);
}
},
clearDestination: function (index) {
this.transactions.transactions[index].destination_account.id = 0;
this.transactions.transactions[index].destination_account.name = "";
this.transactions.transactions[index].destination_account.type = "";
this.transactions.transactions[index].source_account.allowed_types = [];
// if there is a source model, reset the types of the destination
// by pretending we selected it again.
if (this.transactions.transactions[index].source_account) {
console.log('There is a source account.');
this.selectedSourceAccount(index, this.transactions.transactions[index].source_account);
}
}
},
/*
* The component's data.
*/
data() {
return {
transactionType: null,
transactions: {
group_title: "",
transactions: [
{
description: "",
date: "",
amount: "",
foreign_amount: "",
source_account: {
id: 0,
name: "",
type: "",
//currency_id: window.defaultCurrency.id,
//currency_name: window.defaultCurrency.name,
//currency_code: window.defaultCurrency.code,
//currency_decimal_places: window.defaultCurrency.decimal_places,
currency_id: 0,
currency_name: '',
currency_code: '',
currency_decimal_places: 2,
allowed_types: []
},
destination_account: {
id: 0,
name: "",
type: "",
//currency_id: window.defaultCurrency.id,
//currency_name: window.defaultCurrency.name,
//currency_code: window.defaultCurrency.code,
//currency_decimal_places: window.defaultCurrency.decimal_places,
currency_id: 0,
currency_name: '',
currency_code: '',
currency_decimal_places: 2,
allowed_types: []
}
}
]
}
};
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,46 @@
<!--
- CustomTransactionFields.vue
- Copyright (c) 2019 thegrumpydictator@gmail.com
-
- This file is part of Firefly III.
-
- Firefly III is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Firefly III is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
</template>
<script>
export default {
name: "CustomTransactionFields",
mounted() {
this.getPreference();
},
methods: {
getPreference() {
const url = document.getElementsByTagName('base')[0].href + 'api/v1/preferences/transaction_journal_optional_fields';
axios.get(url).then(response => {
console.log(response.data.data.attributes);
}).catch(() => console.warn('Oh. Something went wrong'));
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,127 @@
<!--
- ForeignAmountSelect.vue
- Copyright (c) 2019 thegrumpydictator@gmail.com
-
- This file is part of Firefly III.
-
- Firefly III is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Firefly III is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group">
<div class="col-sm-4">
<select class="form-control" name="foreign_currency[]" v-if="this.enabledCurrencies.length > 0">
<option v-for="currency in this.enabledCurrencies" v-if="currency.enabled">{{ currency.name }}</option>
</select>
</div>
<div class="col-sm-8">
<input type="number" step="any" class="form-control" name="foreign_amount[]" v-if="this.enabledCurrencies.length > 0"
title="Foreign amount" autocomplete="off" placeholder="Foreign amount">
</div>
</div>
</template>
<script>
export default {
name: "ForeignAmountSelect",
props: ['source', 'destination', 'transactionType'],
mounted() {
this.loadCurrencies();
},
data() {
return {
currencies: [],
enabledCurrencies: [],
exclude: null
}
},
watch: {
source: function () {
this.changeData();
},
destination: function () {
this.changeData();
},
transactionType: function () {
this.changeData();
}
},
methods: {
changeData: function () {
this.enabledCurrencies = [];
if (this.transactionType === 'Transfer') {
// lock source on currencyID of destination
for (const key in this.currencies) {
if (this.currencies.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
if (this.currencies[key].id === this.destination.currency_id) {
this.enabledCurrencies.push(this.currencies[key]);
}
}
}
return;
}
// if type is withdrawal, list all but skip the source account ID.
if (this.transactionType === 'Withdrawal' && this.source) {
for (const key in this.currencies) {
if (this.currencies.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
if (this.source.currency_id !== this.currencies[key].id) {
this.enabledCurrencies.push(this.currencies[key]);
}
}
}
return;
}
// if type is deposit, list all but skip the source account ID.
if (this.transactionType === 'Deposit' && this.destination) {
for (const key in this.currencies) {
if (this.currencies.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
if (this.destination.currency_id !== this.currencies[key].id) {
this.enabledCurrencies.push(this.currencies[key]);
}
}
}
return;
}
for (const key in this.currencies) {
if (this.currencies.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
this.enabledCurrencies.push(this.currencies[key]);
}
}
},
loadCurrencies: function () {
let URI = document.getElementsByTagName('base')[0].href + "json/currencies";
axios.get(URI, {}).then((res) => {
this.currencies = [
{
name: '(none)',
id: 0,
enabled: true
}
];
for (const key in res.data) {
if (res.data.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
this.currencies.push(res.data[key]);
this.enabledCurrencies.push(res.data[key]);
}
}
});
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,66 @@
<!--
- PiggyBank.vue
- Copyright (c) 2019 thegrumpydictator@gmail.com
-
- This file is part of Firefly III.
-
- Firefly III is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Firefly III is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group" v-if="typeof this.transactionType !== 'undefined' && this.transactionType === 'Transfer'">
<div class="col-sm-12">
<select name="piggy_bank[]" class="form-control" v-if="this.piggies.length > 0">
<option v-for="piggy in this.piggies">{{piggy.name}}</option>
</select>
</div>
</div>
</template>
<script>
export default {
name: "PiggyBank",
props: ['transactionType'],
mounted() {
this.loadPiggies();
},
data() {
return {
piggies: [],
}
},
methods: {
loadPiggies: function () {
let URI = document.getElementsByTagName('base')[0].href + "json/piggy-banks";
axios.get(URI, {}).then((res) => {
this.piggies = [
{
name: '(no piggy bank)',
id: 0,
}
];
for (const key in res.data) {
if (res.data.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
this.piggies.push(res.data[key]);
}
}
});
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,82 @@
<!--
- Tags.vue
- Copyright (c) 2019 thegrumpydictator@gmail.com
-
- This file is part of Firefly III.
-
- Firefly III is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Firefly III is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group">
<div class="col-sm-12">
<vue-tags-input
v-model="tag"
:tags="tags"
classes="form-input"
:autocomplete-items="autocompleteItems"
:add-only-from-autocomplete="false"
@tags-changed="update"
placeholder="Tags"
/>
</div>
</div>
</template>
<script>
import axios from 'axios';
import VueTagsInput from '@johmun/vue-tags-input';
export default {
name: "Tags",
components: {
VueTagsInput
}, data() {
return {
tag: '',
tags: [],
autocompleteItems: [],
debounce: null,
};
},
watch: {
'tag': 'initItems',
},
methods: {
update(newTags) {
this.autocompleteItems = [];
this.tags = newTags;
},
initItems() {
if (this.tag.length < 2) {
return;
}
const url = document.getElementsByTagName('base')[0].href + `json/tags?query=${this.tag}`;
clearTimeout(this.debounce);
this.debounce = setTimeout(() => {
axios.get(url).then(response => {
this.autocompleteItems = response.data.map(a => {
return {text: a.tag};
});
}).catch(() => console.warn('Oh. Something went wrong'));
}, 600);
},
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,90 @@
<!--
- TransactionType.vue
- Copyright (c) 2019 thegrumpydictator@gmail.com
-
- This file is part of Firefly III.
-
- Firefly III is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Firefly III is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group">
<div class="col-sm-12">
<label v-if="sentence !== ''" class="control-label text-info">
{{ sentence }}
</label>
</div>
</div>
</template>
<script>
export default {
props: {
source: String,
destination: String,
type: String
},
methods: {
changeValue: function () {
if (this.source && this.destination) {
let transactionType = '';
if (window.accountToTypes[this.source]) {
if (window.accountToTypes[this.source][this.destination]) {
transactionType = window.accountToTypes[this.source][this.destination];
} else {
console.warn('User selected an impossible destination.');
}
} else {
console.warn('User selected an impossible source.');
}
if ('' !== transactionType) {
this.transactionType = transactionType;
this.sentence = 'You\'re creating a ' + this.transactionType;
// Must also emit a change to set ALL sources and destinations to this particular type.
this.$emit('act:limitSourceType', this.source);
this.$emit('act:limitDestinationType', this.destination);
}
} else {
this.sentence = '';
this.transactionType = '';
}
// emit event how cool is that.
this.$emit('set:transactionType', this.transactionType);
}
},
data() {
return {
transactionType: this.type,
sentence: ''
}
},
watch: {
source() {
this.changeValue();
},
destination() {
this.changeValue();
}
},
name: "TransactionType"
}
</script>
<style scoped>
</style>

View File

@ -46,6 +46,7 @@ return [
'create_withdrawal' => 'Create new withdrawal',
'create_deposit' => 'Create new deposit',
'create_transfer' => 'Create new transfer',
'create_new_transaction' => 'Create a new transaction',
'edit_journal' => 'Edit transaction ":description"',
'edit_reconciliation' => 'Edit ":description"',
'delete_journal' => 'Delete transaction ":description"',

View File

@ -1177,7 +1177,7 @@ return [
'store_configuration' => 'Store configuration',
'single_user_administration' => 'User administration for :email',
'edit_user' => 'Edit user :email',
'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your <a href=":link">settings</a>.',
'hidden_fields_preferences' => 'You can enable more transaction options in your <a href=":link">settings</a>.',
'user_data_information' => 'User data',
'user_information' => 'User information',
'total_size' => 'total size',
@ -1202,6 +1202,14 @@ return [
'send_message' => 'Send message',
'send_test_triggered' => 'Test was triggered. Check your inbox and the log files.',
'split_transaction_title' => 'Description of the split transaction',
'split_title_help' => 'If you create a split transaction, there must be a global description for all splits of the transaction.',
'transaction_information' => 'Transaction information',
'you_create_transfer' => 'You\'re creating a <strong>transfer</strong>.',
'you_create_withdrawal' => 'You\'re creating a <strong>withdrawal</strong>.',
'you_create_deposit' => 'You\'re creating a <strong>deposit</strong>.',
// links
'journal_link_configuration' => 'Transaction links configuration',
'create_new_link_type' => 'Create new link type',

View File

@ -57,6 +57,7 @@ return [
'asset_source_account' => 'Source account',
'journal_description' => 'Description',
'note' => 'Notes',
'store_new_transaction' => 'Store new transaction',
'split_journal' => 'Split this transaction',
'split_journal_explanation' => 'Split this transaction in multiple parts',
'currency' => 'Currency',

View File

@ -31,18 +31,4 @@
</div>
</div>
{% endif %}
{% if type == 'create' and name == 'transaction' %}
<div class="form-group">
<label for="{{ name }}_split" class="col-sm-4 control-label">
{{ trans('form.split_journal') }}
</label>
<div class="col-sm-8">
<div class="checkbox"><label>
{{ Form.checkbox('split_journal', '1', old('split_journal') == '1', {'id': name ~ 'split'}) }}
{{ trans('form.split_journal_explanation') }}
</label>
</div>
</div>
</div>
{% endif %}

View File

@ -193,8 +193,8 @@
{% if not shownDemo %}
<script type="text/javascript">
var routeForTour = "{{ current_route_name }}";
var routeStepsUri = "{{ route('json.intro', [current_route_name, what|default("")]) }}";
var routeForFinishedTour = "{{ route('json.intro.finished', [current_route_name, what|default("")]) }}";
var routeStepsUri = "{{ route('json.intro', [current_route_name, objectType|default("")]) }}";
var routeForFinishedTour = "{{ route('json.intro.finished', [current_route_name, objectType|default("")]) }}";
</script>
<script type="text/javascript" src="v1/lib/intro/intro.min.js?v={{ FF_VERSION }}"></script>
<script type="text/javascript" src="v1/js/ff/intro/intro.js?v={{ FF_VERSION }}"></script>

View File

@ -1,7 +1,9 @@
<table class="table">
<thead>
<tr>
<th>X</th>
<th>
{{ groups.render }}
</th>
</tr>
</thead>
<tbody>

View File

@ -12,22 +12,22 @@
</span>
</a>
<ul class="treeview-menu">
<li class="{{ activeRoutePartialWhat('accounts', 'asset') }}">
<li class="{{ activeRoutePartialObjectType('accounts', 'asset') }}">
<a href="{{ route('accounts.index','asset') }}">
<i class="fa fa-money fa-fw"></i> {{ 'asset_accounts'|_ }}
</a>
</li>
<li class="{{ activeRoutePartialWhat('accounts', 'expense') }}">
<li class="{{ activeRoutePartialObjectType('accounts', 'expense') }}">
<a href="{{ route('accounts.index','expense') }}">
<i class="fa fa-shopping-cart fa-fw"></i> {{ 'expense_accounts'|_ }}
</a>
</li>
<li class="{{ activeRoutePartialWhat('accounts', 'revenue') }}">
<li class="{{ activeRoutePartialObjectType('accounts', 'revenue') }}">
<a href="{{ route('accounts.index','revenue') }}">
<i class="fa fa-download fa-fw"></i> {{ 'revenue_accounts'|_ }}
</a>
</li>
<li class="{{ activeRoutePartialWhat('accounts', 'liabilities') }}">
<li class="{{ activeRoutePartialObjectType('accounts', 'liabilities') }}">
<a href="{{ route('accounts.index','liabilities') }}">
<i class="fa fa-ticket fa-fw"></i> {{ 'liabilities_accounts'|_ }}
</a>
@ -67,15 +67,15 @@
</span>
</a>
<ul class="treeview-menu">
<li class="{{ activeRoutePartialWhat('transactions','withdrawal') }}">
<li class="{{ activeRoutePartialObjectType('transactions','withdrawal') }}">
<a href="{{ route('transactions.index','withdrawal') }}">
<i class="fa fa-long-arrow-left fa-fw"></i> {{ 'expenses'|_ }}</a>
</li>
<li class="{{ activeRoutePartialWhat('transactions','deposit') }}">
<li class="{{ activeRoutePartialObjectType('transactions','deposit') }}">
<a href="{{ route('transactions.index','deposit') }}"><i
class="fa fa-long-arrow-right fa-fw"></i> {{ 'income'|_ }}</a>
</li>
<li class="{{ activeRoutePartialWhat('transactions','transfers') }}">
<li class="{{ activeRoutePartialObjectType('transactions','transfers') }}">
<a href="{{ route('transactions.index','transfers') }}">
<i class="fa fa-fw fa-exchange"></i> {{ 'transfers'|_ }}</a>
</li>

View File

@ -0,0 +1,288 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
{{ Breadcrumbs.render(Route.getCurrentRoute.getName, objectType) }}
{% endblock %}
{% block content %}
<create-transaction></create-transaction>
{#
<form method="POST" action="{{ route('transactions.store') }}" accept-charset="UTF-8" class="form-horizontal" id="store" enctype="multipart/form-data">
<input name="_token" type="hidden" value="{{ csrf_token() }}">
<div class="row">
<div class="col-lg-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">
{{ 'split_transaction_title'|_ }}
</h3>
</div>
<div class="box-body">
<div class="form-group">
<div class="col-sm-12">
<input type="text" class="form-control" name="group_title"
title="{{ 'split_transaction_title'|_ }}" autocomplete="off" placeholder="{{ 'split_transaction_title'|_ }}">
<p class="help-block">
{{ 'split_title_help'|_ }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="transactions">
<div class="row transactionRow">
<div class="col-lg-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title splitTitle">
{{ 'transaction_information'|_ }}
</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-lg-4">
<div class="form-group transactionTypeIndicatorBlock" style="display:none;">
<div class="col-sm-12">
<label class="control-label transactionTypeIndicator text-info"></label>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<div class="input-group">
<input type="text" data-index="0" class="form-control indexField sourceAccountAC" name="source[]"
title="{{ trans('form.source_account') }}" autocomplete="off"
placeholder="{{ trans('form.source_account') }}">
<span class="input-group-btn">
<button class="btn btn-default clearSource" type="button"><i class="fa fa-trash-o"></i></button>
</span>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<div class="input-group">
<input data-index="0" type="text" class="form-control indexField destinationAccountAC" name="destination[]"
title="{{ trans('form.destination_account') }}" autocomplete="off"
placeholder="{{ trans('form.destination_account') }}">
<span class="input-group-btn">
<button class="btn btn-default clearDestination" type="button"><i class="fa fa-trash-o"></i></button>
</span>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<input type="text" class="form-control" name="description[]"
title="{{ trans('form.description') }}" autocomplete="off" placeholder="{{ trans('form.description') }}">
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<input type="date" class="form-control" name="date[]"
title="{{ trans('form.date') }}" value="{{ phpdate('Y-m-d') }}" autocomplete="off"
placeholder="{{ trans('form.date') }}">
</div>
</div>
</div>
<div class="col-lg-4">
<div class="form-group">
<label class="col-sm-3 control-label">$</label>
<div class="col-sm-9">
<input type="number" step="any" class="form-control" name="amount[]"
title="{{ trans('form.amount') }}" autocomplete="off" placeholder="{{ trans('form.amount') }}">
</div>
</div>
<div class="form-group">
<div class="col-sm-3">
<select name="foreign_amount">
<option>none</option>
<option>USD</option>
</select>
</div>
<div class="col-sm-9">
<input type="number" step="any" class="form-control" name="amount[]"
title="Foreign amount" autocomplete="off" placeholder="Foreign amount">
</div>
</div>
</div>
<div class="col-lg-4">
<div class="form-group">
<div class="col-sm-12">
<select name="budget">
<option>budget</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<input type="text" class="form-control" name="category[]"
title="Category" autocomplete="off" placeholder="Category">
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<input type="text" class="form-control" name="tags[]"
title="Tags" autocomplete="off" placeholder="Tags">
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<select name="piggy">
<option>Piggy</option>
</select>
</div>
</div>
{% if
optionalFields.interest_date or optionalFields.book_date or optionalFields.process_date
or optionalFields.due_date or optionalFields.payment_date
or optionalFields.invoice_date %}
{% for field in ['interest_date','book_date','process_date','due_date','payment_date','invoice_date'] %}
{% if optionalFields[field] %}
<div class="form-group">
<div class="col-sm-12">
<input type="text" class="form-control" name="{{ field }}[]"
title="{{ field }}" autocomplete="off" placeholder="{{ field }}">
</div>
</div>
{% endif %}
{% endfor %}
{% endif %}
{% if optionalFields.internal_reference or optionalFields.notes %}
{% if optionalFields.internal_reference %}
<div class="form-group">
<div class="col-sm-12">
<input type="text" class="form-control" name="internal_reference[]"
title="internal_reference" autocomplete="off" placeholder="internal_reference">
</div>
</div>
{% endif %}
{% if optionalFields.notes %}
<div class="form-group">
<div class="col-sm-12">
<textarea></textarea>
<br>
{{ trans('firefly.field_supports_markdown')|raw }}
</div>
</div>
{% endif %}
{% endif %}
{% if optionalFields.attachments %}
<div class="form-group">
<div class="col-sm-12">
<input multiple="multiple" class="form-control"
autocomplete="off" placeholder="Attachments" name="attachments[]" type="file">
<p class="help-block">
{{ trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }}
</p>
</div>
</div>
{% endif %}
{% if
not optionalFields.interest_date or
not optionalFields.book_date or
not optionalFields.process_date or
not optionalFields.due_date or
not optionalFields.payment_date or
not optionalFields.invoice_date or
not optionalFields.internal_reference or
not optionalFields.notes or
not optionalFields.attachments %}
<div class="form-group">
<div class="col-sm-12">
<p class="text-success"><i class="fa fa-info-circle"></i>
<em>{{ trans('firefly.hidden_fields_preferences', {link: route('preferences.index')})|raw }}</em></p>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<p>
<button id="addSplitButton" class="btn btn-primary">Add another split</button>
</p>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'options'|_ }}</h3>
</div>
<div class="box-body">
{{ ExpandedForm.optionsList('create','transaction') }}
</div>
<div class="box-footer">
<button type="submit" class="transaction-btn btn btn-success pull-right">
{{ trans('form.store_new_transaction') }}
</button>
</div>
</div>
</div>
</div>
</form>
#}
{% endblock %}
{% block scripts %}
<script type="text/javascript">
var allowedOpposingTypes = {{ allowedOpposingTypes|json_encode|raw }};
var accountToTypes = {{ accountToTypes|json_encode|raw }};
var defaultCurrency = {{ defaultCurrency.toArray()|json_encode|raw }};
</script>
<!--
<script type="text/javascript">
var transactionType = 'none';
var accountToTypes = {{ accountToTypes|json_encode|raw }};
var creatingTypes = {
Transfer: "{{ 'you_create_transfer'|_ }}",
Withdrawal: "{{ 'you_create_withdrawal'|_ }}",
Deposit: "{{ 'you_create_deposit'|_ }}",
};
// options for source account selection.
var sourceAccount = null;
var sourceAllowedAccountTypes = [];
// options for destination account selection.
var destinationAccount = null;
var destAllowedAccountTypes = [];
</script>
<script type="text/javascript" src="v1/js/lib/modernizr-custom.js?v={{ FF_VERSION }}"></script>
<script type="text/javascript" src="v1/js/lib/jquery.autocomplete.min.js?v={{ FF_VERSION }}"></script>
<script type="text/javascript" src="v1/js/ff/transactions/create.js?v={{ FF_VERSION }}"></script>
-->
{% endblock %}
{% block styles %}
{% endblock %}

View File

@ -31,7 +31,7 @@
{% endif %}
</div>
{# actual list #}
{% include 'list.transactions' %}
{% include 'list.groups' %}
</div>
<div class="box-footer">
{# links for other views #}

View File

@ -1022,9 +1022,9 @@ try {
Breadcrumbs::register(
'transactions.create',
function (BreadcrumbsGenerator $breadcrumbs, string $what) {
$breadcrumbs->parent('transactions.index', $what);
$breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what]));
function (BreadcrumbsGenerator $breadcrumbs, string $objectType) {
$breadcrumbs->parent('transactions.index', $objectType);
$breadcrumbs->push(trans('breadcrumbs.create_new_transaction'), route('transactions.create', [$objectType]));
}
);

View File

@ -546,14 +546,20 @@ Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'json', 'as' => 'json.'], function () {
// for auto complete
Route::get('accounts', ['uses' => 'Json\AutoCompleteController@accounts', 'as' => 'autocomplete.accounts']);
Route::get('currencies', ['uses' => 'Json\AutoCompleteController@currencies', 'as' => 'autocomplete.currencies']);
Route::get('budgets', ['uses' => 'Json\AutoCompleteController@budgets', 'as' => 'autocomplete.budgets']);
Route::get('categories', ['uses' => 'Json\AutoCompleteController@categories', 'as' => 'autocomplete.categories']);
Route::get('piggy-banks', ['uses' => 'Json\AutoCompleteController@piggyBanks', 'as' => 'autocomplete.piggy-banks']);
Route::get('tags', ['uses' => 'Json\AutoCompleteController@tags', 'as' => 'autocomplete.tags']);
// TODO improve 3 routes:
Route::get('transaction-journals/all', ['uses' => 'Json\AutoCompleteController@allTransactionJournals', 'as' => 'all-transaction-journals']);
Route::get('transaction-journals/with-id/{tj}', ['uses' => 'Json\AutoCompleteController@journalsWithId', 'as' => 'journals-with-id']);
Route::get('transaction-journals/{what}', ['uses' => 'Json\AutoCompleteController@transactionJournals', 'as' => 'transaction-journals']);
//Route::get('transaction-journals/all', ['uses' => 'Json\AutoCompleteController@allTransactionJournals', 'as' => 'all-transaction-journals']);
//Route::get('transaction-journals/with-id/{tj}', ['uses' => 'Json\AutoCompleteController@journalsWithId', 'as' => 'journals-with-id']);
//Route::get('transaction-journals/{what}', ['uses' => 'Json\AutoCompleteController@transactionJournals', 'as' => 'transaction-journals']);
// TODO end of improvement
Route::get('transaction-types', ['uses' => 'Json\AutoCompleteController@transactionTypes', 'as' => 'transaction-types']);
// boxes
@ -577,7 +583,7 @@ Route::group(
Route::post('intro/enable/{route}/{specificPage?}', ['uses' => 'Json\IntroController@postEnable', 'as' => 'intro.enable']);
Route::get('intro/{route}/{specificPage?}', ['uses' => 'Json\IntroController@getIntroSteps', 'as' => 'intro']);
Route::get('/{subject}', ['uses' => 'Json\AutoCompleteController@autoComplete', 'as' => 'autocomplete']);
//Route::get('/{subject}', ['uses' => 'Json\AutoCompleteController@autoComplete', 'as' => 'autocomplete']);
}
);
@ -873,11 +879,13 @@ Route::group(
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'transactions', 'as' => 'transactions.'], function () {
// show groups:
Route::get('{what}/{start_date?}/{end_date?}', ['uses' => 'Transaction\IndexController@index', 'as' => 'index'])->where(
['what' => 'withdrawal|deposit|transfers|transfer']
);
// create group:
Route::get('create/{objectType}', ['uses' => 'Transaction\CreateController@create', 'as' => 'create'])->where(['objectType' => 'withdrawal|deposit|transfer']);
// TODO improve these routes
@ -903,7 +911,7 @@ Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions', 'as' => 'transactions.'],
function () {
// TODO improve these routes
Route::get('create/{what}', ['uses' => 'SingleController@create', 'as' => 'create'])->where(['what' => 'withdrawal|deposit|transfer']);
Route::get('edit/{tj}', ['uses' => 'SingleController@edit', 'as' => 'edit']);
Route::get('delete/{tj}', ['uses' => 'SingleController@delete', 'as' => 'delete']);
Route::post('store', ['uses' => 'SingleController@store', 'as' => 'store'])->where(['what' => 'withdrawal|deposit|transfer']);

View File

@ -2,6 +2,13 @@
# yarn lockfile v1
"@johmun/vue-tags-input@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@johmun/vue-tags-input/-/vue-tags-input-2.0.1.tgz#5ca22a573858c8df6c05788e3ffabe92e1baa1df"
integrity sha512-1LUsINr6iBROfG31C05qf+XpoM4jLstpLzTmfu+57fxErgP2gGnVB41oz1qVxjKe1gdpOaAZSByhoAm2h0cO3w==
dependencies:
vue "^2.5.16"
"@types/q@^1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.1.tgz#48fd98c1561fe718b61733daed46ff115b496e18"
@ -7974,6 +7981,13 @@ uglifyjs-webpack-plugin@^1.0.0:
webpack-sources "^1.1.0"
worker-farm "^1.5.2"
uiv@^0.31.5:
version "0.31.5"
resolved "https://registry.yarnpkg.com/uiv/-/uiv-0.31.5.tgz#bfb87833b9abb59d8f0bb4f630943449b053b733"
integrity sha512-VcizUxkJCr4XhnFJ3KWCuiVaDvU3H5yDLX4F1yhrmK5hz+2+OIuCShJd6Is366oJdJagoh42sjWQqZL74uMEmw==
dependencies:
vue-functional-data-merge "^2.0.3"
unbzip2-stream@^1.0.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
@ -8252,6 +8266,11 @@ vm-browserify@0.0.4:
dependencies:
indexof "0.0.1"
vue-functional-data-merge@^2.0.3:
version "2.0.7"
resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-2.0.7.tgz#bdee655181eacdcb1f96ce95a4cc14e75313d1da"
integrity sha512-pvLc+H+x2prwBj/uSEIITyxjz/7ZUVVK8uYbrYMmhDvMXnzh9OvQvVEwcOSBQjsubd4Eq41/CSJaWzy4hemMNQ==
vue-hot-reload-api@^2.2.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2"
@ -8297,6 +8316,11 @@ vue-template-es2015-compiler@^1.6.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz#dc42697133302ce3017524356a6c61b7b69b4a18"
integrity sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==
vue@^2.5.16:
version "2.6.10"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
vue@^2.5.7:
version "2.5.21"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85"