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 php artisan package:discover
echo "Run various artisan commands..." echo "Run various artisan commands..."
. $FIREFLY_PATH/.env #. $FIREFLY_PATH/.env
if [[ -z "$DB_PORT" ]]; then if [[ -z "$DB_PORT" ]]; then
if [[ $DB_CONNECTION == "pgsql" ]]; then if [[ $DB_CONNECTION == "pgsql" ]]; then
DB_PORT=5432 DB_PORT=5432

View File

@ -519,22 +519,22 @@ return [
], ],
'test-triggers' => [ 'test-triggers' => [
'limit' => 10, 'limit' => 10,
'range' => 200, 'range' => 200,
], ],
'default_currency' => 'EUR', 'default_currency' => 'EUR',
'default_language' => 'en_US', 'default_language' => 'en_US',
'search_modifiers' => ['amount_is', 'amount', 'amount_max', 'amount_min', 'amount_less', 'amount_more', 'source', 'destination', 'category', 'search_modifiers' => ['amount_is', 'amount', 'amount_max', 'amount_min', 'amount_less', 'amount_more', 'source', 'destination', 'category',
'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after'], 'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after'],
// tag notes has_attachments // tag notes has_attachments
'cer_providers' => [ 'cer_providers' => [
'fixer' => FixerIOv2::class, 'fixer' => FixerIOv2::class,
'ratesapi' => RatesApiIOv1::class, 'ratesapi' => RatesApiIOv1::class,
], ],
// expected source types for each transaction type, in order of preference. // expected source types for each transaction type, in order of preference.
'expected_source_types' => [ 'expected_source_types' => [
'source' => [ 'source' => [
TransactionTypeModel::WITHDRAWAL => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], TransactionTypeModel::WITHDRAWAL => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
TransactionTypeModel::DEPOSIT => [AccountType::REVENUE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, TransactionTypeModel::DEPOSIT => [AccountType::REVENUE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE,
@ -543,6 +543,15 @@ return [
TransactionTypeModel::OPENING_BALANCE => [AccountType::INITIAL_BALANCE, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, TransactionTypeModel::OPENING_BALANCE => [AccountType::INITIAL_BALANCE, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT,
AccountType::MORTGAGE], AccountType::MORTGAGE],
TransactionTypeModel::RECONCILIATION => [AccountType::RECONCILIATION, AccountType::ASSET], 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' => [ 'destination' => [
TransactionTypeModel::WITHDRAWAL => [AccountType::EXPENSE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, TransactionTypeModel::WITHDRAWAL => [AccountType::EXPENSE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT,
@ -554,9 +563,130 @@ return [
TransactionTypeModel::RECONCILIATION => [AccountType::RECONCILIATION, AccountType::ASSET], 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. // allowed source / destination accounts.
'source_dests' => [ 'source_dests' => [
TransactionTypeModel::WITHDRAWAL => [ TransactionTypeModel::WITHDRAWAL => [
AccountType::ASSET => [AccountType::EXPENSE, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, AccountType::CASH], AccountType::ASSET => [AccountType::EXPENSE, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, AccountType::CASH],
AccountType::LOAN => [AccountType::EXPENSE], AccountType::LOAN => [AccountType::EXPENSE],

View File

@ -13,12 +13,14 @@
"axios": "^0.17", "axios": "^0.17",
"bootstrap-sass": "^3.3.7", "bootstrap-sass": "^3.3.7",
"cross-env": "^5.1", "cross-env": "^5.1",
"jquery": "^3.1.1",
"laravel-mix": "^1.0", "laravel-mix": "^1.0",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"vue": "^2.5.7" "vue": "^2.5.7"
}, },
"dependencies": { "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", "/v1/js/app.js": "/v1/js/app.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"
}

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/>. * 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 { .split_amount_input {
width: 40%; width: 40%;
border-radius: 0; 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 { .split_amount_input:focus {
border-color: #98cbe8; border-color: #98cbe8;
outline: 0; 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 * includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel. * building robust, powerful web applications using Vue and Laravel.
*/ */
/* TODO REMOVE ME */
require('./bootstrap'); require('./bootstrap');
window.Vue = require('vue'); 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. * Components for OAuth2 tokens.
@ -14,6 +30,7 @@ window.Vue = require('vue');
Vue.component('passport-clients', require('./components/passport/Clients.vue')); Vue.component('passport-clients', require('./components/passport/Clients.vue'));
Vue.component('passport-authorized-clients', require('./components/passport/AuthorizedClients.vue')); Vue.component('passport-authorized-clients', require('./components/passport/AuthorizedClients.vue'));
Vue.component('passport-personal-access-tokens', require('./components/passport/PersonalAccessTokens.vue')); Vue.component('passport-personal-access-tokens', require('./components/passport/PersonalAccessTokens.vue'));
Vue.component('create-transaction', require('./components/transactions/CreateTransaction'));
const app = new Vue({ 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

@ -23,35 +23,36 @@
declare(strict_types=1); declare(strict_types=1);
return [ return [
'home' => 'Home', 'home' => 'Home',
'edit_currency' => 'Edit currency ":name"', 'edit_currency' => 'Edit currency ":name"',
'delete_currency' => 'Delete currency ":name"', 'delete_currency' => 'Delete currency ":name"',
'newPiggyBank' => 'Create a new piggy bank', 'newPiggyBank' => 'Create a new piggy bank',
'edit_piggyBank' => 'Edit piggy bank ":name"', 'edit_piggyBank' => 'Edit piggy bank ":name"',
'preferences' => 'Preferences', 'preferences' => 'Preferences',
'profile' => 'Profile', 'profile' => 'Profile',
'changePassword' => 'Change your password', 'changePassword' => 'Change your password',
'change_email' => 'Change your email address', 'change_email' => 'Change your email address',
'bills' => 'Bills', 'bills' => 'Bills',
'newBill' => 'New bill', 'newBill' => 'New bill',
'edit_bill' => 'Edit bill ":name"', 'edit_bill' => 'Edit bill ":name"',
'delete_bill' => 'Delete bill ":name"', 'delete_bill' => 'Delete bill ":name"',
'reports' => 'Reports', 'reports' => 'Reports',
'search_result' => 'Search results for ":query"', 'search_result' => 'Search results for ":query"',
'withdrawal_list' => 'Expenses', 'withdrawal_list' => 'Expenses',
'deposit_list' => 'Revenue, income and deposits', 'deposit_list' => 'Revenue, income and deposits',
'transfer_list' => 'Transfers', 'transfer_list' => 'Transfers',
'transfers_list' => 'Transfers', 'transfers_list' => 'Transfers',
'reconciliation_list' => 'Reconciliations', 'reconciliation_list' => 'Reconciliations',
'create_withdrawal' => 'Create new withdrawal', 'create_withdrawal' => 'Create new withdrawal',
'create_deposit' => 'Create new deposit', 'create_deposit' => 'Create new deposit',
'create_transfer' => 'Create new transfer', 'create_transfer' => 'Create new transfer',
'edit_journal' => 'Edit transaction ":description"', 'create_new_transaction' => 'Create a new transaction',
'edit_reconciliation' => 'Edit ":description"', 'edit_journal' => 'Edit transaction ":description"',
'delete_journal' => 'Delete transaction ":description"', 'edit_reconciliation' => 'Edit ":description"',
'tags' => 'Tags', 'delete_journal' => 'Delete transaction ":description"',
'createTag' => 'Create new tag', 'tags' => 'Tags',
'edit_tag' => 'Edit tag ":tag"', 'createTag' => 'Create new tag',
'delete_tag' => 'Delete tag ":tag"', 'edit_tag' => 'Edit tag ":tag"',
'delete_journal_link' => 'Delete link between transactions', 'delete_tag' => 'Delete tag ":tag"',
'delete_journal_link' => 'Delete link between transactions',
]; ];

View File

@ -1148,7 +1148,7 @@ return [
'deleted_piggy_bank' => 'Deleted piggy bank ":name"', 'deleted_piggy_bank' => 'Deleted piggy bank ":name"',
'added_amount_to_piggy' => 'Added :amount to ":name"', 'added_amount_to_piggy' => 'Added :amount to ":name"',
'removed_amount_from_piggy' => 'Removed :amount from ":name"', 'removed_amount_from_piggy' => 'Removed :amount from ":name"',
'piggy_events' => 'Related piggy banks', 'piggy_events' => 'Related piggy banks',
// tags // tags
'delete_tag' => 'Delete tag ":tag"', 'delete_tag' => 'Delete tag ":tag"',
@ -1158,49 +1158,57 @@ return [
'updated_tag' => 'Updated tag ":tag"', 'updated_tag' => 'Updated tag ":tag"',
'created_tag' => 'Tag ":tag" has been created!', 'created_tag' => 'Tag ":tag" has been created!',
'transaction_journal_information' => 'Transaction information', 'transaction_journal_information' => 'Transaction information',
'transaction_journal_meta' => 'Meta information', 'transaction_journal_meta' => 'Meta information',
'transaction_journal_more' => 'More information', 'transaction_journal_more' => 'More information',
'att_part_of_journal' => 'Stored under ":journal"', 'att_part_of_journal' => 'Stored under ":journal"',
'total_amount' => 'Total amount', 'total_amount' => 'Total amount',
'number_of_decimals' => 'Number of decimals', 'number_of_decimals' => 'Number of decimals',
// administration // administration
'administration' => 'Administration', 'administration' => 'Administration',
'user_administration' => 'User administration', 'user_administration' => 'User administration',
'list_all_users' => 'All users', 'list_all_users' => 'All users',
'all_users' => 'All users', 'all_users' => 'All users',
'instance_configuration' => 'Configuration', 'instance_configuration' => 'Configuration',
'firefly_instance_configuration' => 'Configuration options for Firefly III', 'firefly_instance_configuration' => 'Configuration options for Firefly III',
'setting_single_user_mode' => 'Single user mode', 'setting_single_user_mode' => 'Single user mode',
'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).',
'store_configuration' => 'Store configuration', 'store_configuration' => 'Store configuration',
'single_user_administration' => 'User administration for :email', 'single_user_administration' => 'User administration for :email',
'edit_user' => 'Edit user :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_data_information' => 'User data',
'user_information' => 'User information', 'user_information' => 'User information',
'total_size' => 'total size', 'total_size' => 'total size',
'budget_or_budgets' => 'budget(s)', 'budget_or_budgets' => 'budget(s)',
'budgets_with_limits' => 'budget(s) with configured amount', 'budgets_with_limits' => 'budget(s) with configured amount',
'nr_of_rules_in_total_groups' => ':count_rules rule(s) in :count_groups rule group(s)', 'nr_of_rules_in_total_groups' => ':count_rules rule(s) in :count_groups rule group(s)',
'tag_or_tags' => 'tag(s)', 'tag_or_tags' => 'tag(s)',
'configuration_updated' => 'The configuration has been updated', 'configuration_updated' => 'The configuration has been updated',
'setting_is_demo_site' => 'Demo site', 'setting_is_demo_site' => 'Demo site',
'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.', 'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.',
'block_code_bounced' => 'Email message(s) bounced', 'block_code_bounced' => 'Email message(s) bounced',
'block_code_expired' => 'Demo account expired', 'block_code_expired' => 'Demo account expired',
'no_block_code' => 'No reason for block or user not blocked', 'no_block_code' => 'No reason for block or user not blocked',
'block_code_email_changed' => 'User has not yet confirmed new email address', 'block_code_email_changed' => 'User has not yet confirmed new email address',
'admin_update_email' => 'Contrary to the profile page, the user will NOT be notified their email address has changed!', 'admin_update_email' => 'Contrary to the profile page, the user will NOT be notified their email address has changed!',
'update_user' => 'Update user', 'update_user' => 'Update user',
'updated_user' => 'User data has been changed.', 'updated_user' => 'User data has been changed.',
'delete_user' => 'Delete user :email', 'delete_user' => 'Delete user :email',
'user_deleted' => 'The user has been deleted', 'user_deleted' => 'The user has been deleted',
'send_test_email' => 'Send test email message', 'send_test_email' => 'Send test email message',
'send_test_email_text' => 'To see if your installation is capable of sending email, please press this button. You will not see an error here (if any), <strong>the log files will reflect any errors</strong>. You can press this button as many times as you like. There is no spam control. The message will be sent to <code>:email</code> and should arrive shortly.', 'send_test_email_text' => 'To see if your installation is capable of sending email, please press this button. You will not see an error here (if any), <strong>the log files will reflect any errors</strong>. You can press this button as many times as you like. There is no spam control. The message will be sent to <code>:email</code> and should arrive shortly.',
'send_message' => 'Send message', 'send_message' => 'Send message',
'send_test_triggered' => 'Test was triggered. Check your inbox and the log files.', '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 // links
'journal_link_configuration' => 'Transaction links configuration', 'journal_link_configuration' => 'Transaction links configuration',

View File

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

View File

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

View File

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

View File

@ -12,22 +12,22 @@
</span> </span>
</a> </a>
<ul class="treeview-menu"> <ul class="treeview-menu">
<li class="{{ activeRoutePartialWhat('accounts', 'asset') }}"> <li class="{{ activeRoutePartialObjectType('accounts', 'asset') }}">
<a href="{{ route('accounts.index','asset') }}"> <a href="{{ route('accounts.index','asset') }}">
<i class="fa fa-money fa-fw"></i> {{ 'asset_accounts'|_ }} <i class="fa fa-money fa-fw"></i> {{ 'asset_accounts'|_ }}
</a> </a>
</li> </li>
<li class="{{ activeRoutePartialWhat('accounts', 'expense') }}"> <li class="{{ activeRoutePartialObjectType('accounts', 'expense') }}">
<a href="{{ route('accounts.index','expense') }}"> <a href="{{ route('accounts.index','expense') }}">
<i class="fa fa-shopping-cart fa-fw"></i> {{ 'expense_accounts'|_ }} <i class="fa fa-shopping-cart fa-fw"></i> {{ 'expense_accounts'|_ }}
</a> </a>
</li> </li>
<li class="{{ activeRoutePartialWhat('accounts', 'revenue') }}"> <li class="{{ activeRoutePartialObjectType('accounts', 'revenue') }}">
<a href="{{ route('accounts.index','revenue') }}"> <a href="{{ route('accounts.index','revenue') }}">
<i class="fa fa-download fa-fw"></i> {{ 'revenue_accounts'|_ }} <i class="fa fa-download fa-fw"></i> {{ 'revenue_accounts'|_ }}
</a> </a>
</li> </li>
<li class="{{ activeRoutePartialWhat('accounts', 'liabilities') }}"> <li class="{{ activeRoutePartialObjectType('accounts', 'liabilities') }}">
<a href="{{ route('accounts.index','liabilities') }}"> <a href="{{ route('accounts.index','liabilities') }}">
<i class="fa fa-ticket fa-fw"></i> {{ 'liabilities_accounts'|_ }} <i class="fa fa-ticket fa-fw"></i> {{ 'liabilities_accounts'|_ }}
</a> </a>
@ -67,15 +67,15 @@
</span> </span>
</a> </a>
<ul class="treeview-menu"> <ul class="treeview-menu">
<li class="{{ activeRoutePartialWhat('transactions','withdrawal') }}"> <li class="{{ activeRoutePartialObjectType('transactions','withdrawal') }}">
<a href="{{ route('transactions.index','withdrawal') }}"> <a href="{{ route('transactions.index','withdrawal') }}">
<i class="fa fa-long-arrow-left fa-fw"></i> {{ 'expenses'|_ }}</a> <i class="fa fa-long-arrow-left fa-fw"></i> {{ 'expenses'|_ }}</a>
</li> </li>
<li class="{{ activeRoutePartialWhat('transactions','deposit') }}"> <li class="{{ activeRoutePartialObjectType('transactions','deposit') }}">
<a href="{{ route('transactions.index','deposit') }}"><i <a href="{{ route('transactions.index','deposit') }}"><i
class="fa fa-long-arrow-right fa-fw"></i> {{ 'income'|_ }}</a> class="fa fa-long-arrow-right fa-fw"></i> {{ 'income'|_ }}</a>
</li> </li>
<li class="{{ activeRoutePartialWhat('transactions','transfers') }}"> <li class="{{ activeRoutePartialObjectType('transactions','transfers') }}">
<a href="{{ route('transactions.index','transfers') }}"> <a href="{{ route('transactions.index','transfers') }}">
<i class="fa fa-fw fa-exchange"></i> {{ 'transfers'|_ }}</a> <i class="fa fa-fw fa-exchange"></i> {{ 'transfers'|_ }}</a>
</li> </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 %} {% endif %}
</div> </div>
{# actual list #} {# actual list #}
{% include 'list.transactions' %} {% include 'list.groups' %}
</div> </div>
<div class="box-footer"> <div class="box-footer">
{# links for other views #} {# links for other views #}

View File

@ -1022,9 +1022,9 @@ try {
Breadcrumbs::register( Breadcrumbs::register(
'transactions.create', 'transactions.create',
function (BreadcrumbsGenerator $breadcrumbs, string $what) { function (BreadcrumbsGenerator $breadcrumbs, string $objectType) {
$breadcrumbs->parent('transactions.index', $what); $breadcrumbs->parent('transactions.index', $objectType);
$breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what])); $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 () { ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'json', 'as' => 'json.'], function () {
// for auto complete // 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: // TODO improve 3 routes:
Route::get('transaction-journals/all', ['uses' => 'Json\AutoCompleteController@allTransactionJournals', 'as' => 'all-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/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/{what}', ['uses' => 'Json\AutoCompleteController@transactionJournals', 'as' => 'transaction-journals']);
// TODO end of improvement // TODO end of improvement
Route::get('transaction-types', ['uses' => 'Json\AutoCompleteController@transactionTypes', 'as' => 'transaction-types']); Route::get('transaction-types', ['uses' => 'Json\AutoCompleteController@transactionTypes', 'as' => 'transaction-types']);
// boxes // boxes
@ -577,7 +583,7 @@ Route::group(
Route::post('intro/enable/{route}/{specificPage?}', ['uses' => 'Json\IntroController@postEnable', 'as' => 'intro.enable']); 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('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( Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'transactions', 'as' => 'transactions.'], function () { ['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( Route::get('{what}/{start_date?}/{end_date?}', ['uses' => 'Transaction\IndexController@index', 'as' => 'index'])->where(
['what' => 'withdrawal|deposit|transfers|transfer'] ['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 // TODO improve these routes
@ -903,7 +911,7 @@ Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions', 'as' => 'transactions.'], ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions', 'as' => 'transactions.'],
function () { function () {
// TODO improve these routes // 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('edit/{tj}', ['uses' => 'SingleController@edit', 'as' => 'edit']);
Route::get('delete/{tj}', ['uses' => 'SingleController@delete', 'as' => 'delete']); Route::get('delete/{tj}', ['uses' => 'SingleController@delete', 'as' => 'delete']);
Route::post('store', ['uses' => 'SingleController@store', 'as' => 'store'])->where(['what' => 'withdrawal|deposit|transfer']); Route::post('store', ['uses' => 'SingleController@store', 'as' => 'store'])->where(['what' => 'withdrawal|deposit|transfer']);

2
webpack.mix.js vendored
View File

@ -11,4 +11,4 @@ let mix = require('laravel-mix');
| |
*/ */
mix.js('resources/assets/js/app.js', 'public/v1/js'); mix.js('resources/assets/js/app.js', 'public/v1/js');

View File

@ -2,6 +2,13 @@
# yarn lockfile v1 # 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": "@types/q@^1.5.1":
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.1.tgz#48fd98c1561fe718b61733daed46ff115b496e18" 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" webpack-sources "^1.1.0"
worker-farm "^1.5.2" 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: unbzip2-stream@^1.0.9:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
@ -8252,6 +8266,11 @@ vm-browserify@0.0.4:
dependencies: dependencies:
indexof "0.0.1" 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: vue-hot-reload-api@^2.2.0:
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2" 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" resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz#dc42697133302ce3017524356a6c61b7b69b4a18"
integrity sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg== 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: vue@^2.5.7:
version "2.5.21" version "2.5.21"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85" resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85"