mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
New code for transaction processing and frontend
This commit is contained in:
parent
f67ff98d78
commit
2404f5299c
43
app/Api/V2/Controllers/Model/Transaction/StoreController.php
Normal file
43
app/Api/V2/Controllers/Model/Transaction/StoreController.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/*
|
||||
* StoreController.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Api\V2\Controllers\Model\Transaction;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
* Class StoreController
|
||||
*/
|
||||
class StoreController extends Controller
|
||||
{
|
||||
/**
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function post(): JsonResponse
|
||||
{
|
||||
|
||||
return response()->json([]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -871,7 +871,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
public function setLimit(int $limit): GroupCollectorInterface
|
||||
{
|
||||
$this->limit = $limit;
|
||||
app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
|
||||
//app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -976,7 +976,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
{
|
||||
$page = 0 === $page ? 1 : $page;
|
||||
$this->page = $page;
|
||||
app('log')->debug(sprintf('GroupCollector: page is now %d', $page));
|
||||
//app('log')->debug(sprintf('GroupCollector: page is now %d', $page));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
28
resources/assets/v2/api/v2/model/transaction/post.js
Normal file
28
resources/assets/v2/api/v2/model/transaction/post.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* post.js
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {api} from "../../../../boot/axios";
|
||||
|
||||
export default class Post {
|
||||
post(submission) {
|
||||
let url = '/api/v2/transactions';
|
||||
return api.post(url, submission);
|
||||
}
|
||||
}
|
58
resources/assets/v2/boot/bootstrap.js
vendored
58
resources/assets/v2/boot/bootstrap.js
vendored
@ -29,39 +29,53 @@ import axios from 'axios';
|
||||
import store from "store";
|
||||
import observePlugin from 'store/plugins/observe';
|
||||
import Alpine from "alpinejs";
|
||||
import * as bootstrap from 'bootstrap'
|
||||
import * as bootstrap from 'bootstrap';
|
||||
import {getFreshVariable} from "../store/get-fresh-variable.js";
|
||||
|
||||
store.addPlugin(observePlugin);
|
||||
window.store = store;
|
||||
|
||||
|
||||
// import even more
|
||||
import {getVariable} from "../store/get-variable.js";
|
||||
import {getViewRange} from "../support/get-viewrange.js";
|
||||
|
||||
// wait for 3 promises, because we need those later on.
|
||||
window.bootstrapped = false;
|
||||
Promise.all([
|
||||
getVariable('viewRange'),
|
||||
getVariable('darkMode'),
|
||||
getVariable('locale'),
|
||||
getVariable('language'),
|
||||
]).then((values) => {
|
||||
if (!store.get('start') || !store.get('end')) {
|
||||
// calculate new start and end, and store them.
|
||||
const range = getViewRange(values[0], new Date);
|
||||
store.set('start', range.start);
|
||||
store.set('end', range.end);
|
||||
}
|
||||
window.store = store;
|
||||
|
||||
// save local in window.__ something
|
||||
window.__localeId__ = values[2];
|
||||
store.set('language', values[3]);
|
||||
store.set('locale', values[3]);
|
||||
|
||||
const event = new Event('firefly-iii-bootstrapped');
|
||||
document.dispatchEvent(event);
|
||||
window.bootstrapped = true;
|
||||
// always grab the preference "marker" from Firefly III.
|
||||
getFreshVariable('lastActivity').then((serverValue) => {
|
||||
const localValue = store.get('lastActivity');
|
||||
store.set('cacheValid', localValue === serverValue);
|
||||
store.set('lastActivity', serverValue);
|
||||
console.log('Server value: ' + serverValue);
|
||||
console.log('Local value: ' + localValue);
|
||||
console.log('Cache valid: ' + (localValue === serverValue));
|
||||
}).then(() => {
|
||||
Promise.all([
|
||||
getVariable('viewRange'),
|
||||
getVariable('darkMode'),
|
||||
getVariable('locale'),
|
||||
getVariable('language'),
|
||||
]).then((values) => {
|
||||
if (!store.get('start') || !store.get('end')) {
|
||||
// calculate new start and end, and store them.
|
||||
const range = getViewRange(values[0], new Date);
|
||||
store.set('start', range.start);
|
||||
store.set('end', range.end);
|
||||
}
|
||||
|
||||
// save local in window.__ something
|
||||
window.__localeId__ = values[2];
|
||||
store.set('language', values[3]);
|
||||
store.set('locale', values[3]);
|
||||
|
||||
const event = new Event('firefly-iii-bootstrapped');
|
||||
document.dispatchEvent(event);
|
||||
window.bootstrapped = true;
|
||||
});
|
||||
});
|
||||
// wait for 3 promises, because we need those later on.
|
||||
|
||||
window.axios = axios;
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
@ -33,6 +33,8 @@ let chart = null;
|
||||
let chartData = null;
|
||||
let afterPromises = false;
|
||||
|
||||
const CHART_CACHE_KEY = 'dashboard-accounts-chart';
|
||||
const ACCOUNTS_CACHE_KEY = 'dashboard-accounts-data';
|
||||
export default () => ({
|
||||
loading: false,
|
||||
loadingAccounts: false,
|
||||
@ -44,12 +46,28 @@ export default () => ({
|
||||
setVariable('autoConversion', this.autoConversion);
|
||||
},
|
||||
getFreshData() {
|
||||
const dashboard = new Dashboard();
|
||||
dashboard.dashboard(new Date(window.store.get('start')), new Date(window.store.get('end')), null).then((response) => {
|
||||
this.chartData = response.data;
|
||||
this.drawChart(this.generateOptions(this.chartData));
|
||||
const cacheValid = window.store.get('cacheValid');
|
||||
let cachedData = window.store.get(CHART_CACHE_KEY);
|
||||
|
||||
if (cacheValid && typeof cachedData !== 'undefined') {
|
||||
let options = window.store.get(CHART_CACHE_KEY);
|
||||
this.drawChart(options);
|
||||
this.loading = false;
|
||||
});
|
||||
console.log('Chart from cache');
|
||||
}
|
||||
if (!cacheValid || typeof cachedData === 'undefined') {
|
||||
const dashboard = new Dashboard();
|
||||
dashboard.dashboard(new Date(window.store.get('start')), new Date(window.store.get('end')), null).then((response) => {
|
||||
this.chartData = response.data;
|
||||
// cache generated options:
|
||||
let options = this.generateOptions(this.chartData);
|
||||
window.store.set(CHART_CACHE_KEY, options);
|
||||
this.drawChart(options);
|
||||
this.loading = false;
|
||||
console.log('Chart FRESH');
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
generateOptions(data) {
|
||||
currencies = [];
|
||||
@ -145,6 +163,15 @@ export default () => ({
|
||||
this.loadingAccounts = false;
|
||||
return;
|
||||
}
|
||||
const cacheValid = window.store.get('cacheValid');
|
||||
let cachedData = window.store.get(ACCOUNTS_CACHE_KEY);
|
||||
|
||||
if (cacheValid && typeof cachedData !== 'undefined') {
|
||||
this.accountList = cachedData;
|
||||
this.loadingAccounts = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log('loadAccounts continue!');
|
||||
const max = 10;
|
||||
let totalAccounts = 0;
|
||||
@ -180,7 +207,10 @@ export default () => ({
|
||||
group.transactions.push({
|
||||
description: currentTransaction.description,
|
||||
id: current.id,
|
||||
type: currentTransaction.type,
|
||||
amount_raw: parseFloat(currentTransaction.amount),
|
||||
amount: formatMoney(currentTransaction.amount, currentTransaction.currency_code),
|
||||
native_amount_raw: parseFloat(currentTransaction.native_amount),
|
||||
native_amount: formatMoney(currentTransaction.native_amount, currentTransaction.native_code),
|
||||
});
|
||||
}
|
||||
@ -190,7 +220,9 @@ export default () => ({
|
||||
accounts.push({
|
||||
name: parent.attributes.name,
|
||||
id: parent.id,
|
||||
balance_raw: parseFloat(parent.attributes.current_balance),
|
||||
balance: formatMoney(parent.attributes.current_balance, parent.attributes.currency_code),
|
||||
native_balance_raw: parseFloat(parent.attributes.native_current_balance),
|
||||
native_balance: formatMoney(parent.attributes.native_current_balance, parent.attributes.native_code),
|
||||
groups: groups,
|
||||
});
|
||||
@ -198,6 +230,7 @@ export default () => ({
|
||||
if (count === totalAccounts) {
|
||||
this.accountList = accounts;
|
||||
this.loadingAccounts = false;
|
||||
window.store.set(ACCOUNTS_CACHE_KEY, accounts);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -21,16 +21,66 @@
|
||||
import '../../boot/bootstrap.js';
|
||||
import dates from '../../pages/shared/dates.js';
|
||||
import {createEmptySplit} from "./shared/create-empty-split.js";
|
||||
import {parseFromEntries} from "./shared/parse-from-entries.js";
|
||||
import formatMoney from "../../util/format-money.js";
|
||||
//import Autocomplete from "bootstrap5-autocomplete";
|
||||
import Post from "../../api/v2/model/transaction/post.js";
|
||||
|
||||
let transactions = function () {
|
||||
return {
|
||||
count: 0,
|
||||
totalAmount: 0,
|
||||
entries: [],
|
||||
|
||||
// error and success messages:
|
||||
showError: false,
|
||||
showSuccess: false,
|
||||
|
||||
init() {
|
||||
this.entries.push(createEmptySplit());
|
||||
const opts = {
|
||||
onSelectItem: console.log,
|
||||
};
|
||||
let src = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
src.push({
|
||||
title: "Option " + i,
|
||||
id: "opt" + i,
|
||||
data: {
|
||||
key: i,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// for each thing, make autocomplete?
|
||||
|
||||
|
||||
this.addSplit();
|
||||
console.log('Ik ben init hoera');
|
||||
|
||||
// let element = document.getElementById('source_0');
|
||||
// new Autocomplete(element, {
|
||||
// items: src,
|
||||
// valueField: "id",
|
||||
// labelField: "title",
|
||||
// highlightTyped: true,
|
||||
// onSelectItem: console.log,
|
||||
// });
|
||||
},
|
||||
submitTransaction() {
|
||||
let transactions = parseFromEntries(this.entries);
|
||||
let submission = {
|
||||
group_title: null,
|
||||
fire_webhooks: false,
|
||||
apply_rules: false,
|
||||
transactions: transactions
|
||||
};
|
||||
let poster = new Post();
|
||||
console.log(submission);
|
||||
poster.post(submission).then((response) => {
|
||||
console.log(response);
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
addSplit() {
|
||||
this.entries.push(createEmptySplit());
|
||||
|
@ -19,6 +19,8 @@
|
||||
*/
|
||||
|
||||
|
||||
import format from "date-fns/format";
|
||||
|
||||
function getAccount() {
|
||||
return {
|
||||
id: '',
|
||||
@ -27,10 +29,13 @@ function getAccount() {
|
||||
}
|
||||
|
||||
export function createEmptySplit() {
|
||||
let now = new Date();
|
||||
let formatted = format(now, 'yyyy-MM-dd HH:mm');
|
||||
return {
|
||||
description: 'I am descr',
|
||||
description: 'OK then',
|
||||
amount: '',
|
||||
source_account: getAccount(),
|
||||
destination_account: getAccount(),
|
||||
date: formatted
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* parse-from-entries.js
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param entries
|
||||
*/
|
||||
export function parseFromEntries(entries) {
|
||||
let returnArray = [];
|
||||
for (let i in entries) {
|
||||
if (entries.hasOwnProperty(i)) {
|
||||
const entry = entries[i];
|
||||
let current = {};
|
||||
|
||||
// fields for transaction
|
||||
current.description = entry.description;
|
||||
current.source_name = entry.source_account.name;
|
||||
current.destination_name = entry.destination_account.name;
|
||||
current.amount = entry.amount;
|
||||
current.date = entry.date;
|
||||
|
||||
// TODO transaction type is hard coded:
|
||||
current.type = 'withdrawal';
|
||||
|
||||
|
||||
returnArray.push(current);
|
||||
}
|
||||
}
|
||||
return returnArray;
|
||||
}
|
42
resources/assets/v2/store/get-fresh-variable.js
Normal file
42
resources/assets/v2/store/get-fresh-variable.js
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* get-variable.js
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Get from "../api/v1/preferences/index.js";
|
||||
import Post from "../api/v1/preferences/post.js";
|
||||
|
||||
export function getFreshVariable(name, defaultValue = null) {
|
||||
let getter = (new Get);
|
||||
return getter.getByName(name).then((response) => {
|
||||
// console.log('Get from API');
|
||||
return Promise.resolve(parseResponse(name, response));
|
||||
}).catch(() => {
|
||||
// preference does not exist (yet).
|
||||
// POST it and then return it anyway.
|
||||
let poster = (new Post);
|
||||
poster.post(name, defaultValue).then((response) => {
|
||||
return Promise.resolve(parseResponse(name, response));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function parseResponse(name, response) {
|
||||
return response.data.data.attributes.data;
|
||||
}
|
||||
|
@ -23,27 +23,30 @@ import Post from "../api/v1/preferences/post.js";
|
||||
|
||||
export function getVariable(name, defaultValue = null) {
|
||||
|
||||
const validCache = window.store.get('cacheValid');
|
||||
// currently unused, window.X can be used by the blade template
|
||||
// to make things available quicker than if the store has to grab it through the API.
|
||||
// then again, it's not that slow.
|
||||
if (window.hasOwnProperty(name)) {
|
||||
if (validCache && window.hasOwnProperty(name)) {
|
||||
// console.log('Get from window');
|
||||
return Promise.resolve(window[name]);
|
||||
}
|
||||
// load from store2, if it's present.
|
||||
if (window.store.get(name)) {
|
||||
// console.log('Get from store');
|
||||
return Promise.resolve(window.store.get(name));
|
||||
const fromStore = window.store.get(name);
|
||||
if (validCache && typeof fromStore !== 'undefined') {
|
||||
// console.log('Get "' + name + '" from store');
|
||||
return Promise.resolve(fromStore);
|
||||
}
|
||||
let getter = (new Get);
|
||||
return getter.getByName(name).then((response) => {
|
||||
// console.log('Get from API');
|
||||
// console.log('Get "' + name + '" from API');
|
||||
return Promise.resolve(parseResponse(name, response));
|
||||
}).catch(() => {
|
||||
// preference does not exist (yet).
|
||||
// POST it and then return it anyway.
|
||||
let poster = (new Post);
|
||||
poster.post(name, defaultValue).then((response) => {
|
||||
|
||||
return Promise.resolve(parseResponse(name, response));
|
||||
});
|
||||
});
|
||||
@ -52,7 +55,7 @@ export function getVariable(name, defaultValue = null) {
|
||||
function parseResponse(name, response) {
|
||||
let value = response.data.data.attributes.data;
|
||||
window.store.set(name, value);
|
||||
// console.log('Store from API');
|
||||
// console.log('Store "' + name + '" in localStorage');
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -88,12 +88,9 @@
|
||||
<a :href="'{{ route('accounts.show', '') }}/' + account.id"
|
||||
x-text="account.name"></a>
|
||||
|
||||
<span class="small text-muted">(<template x-if="autoConversion">
|
||||
<span x-text="account.native_balance"></span><br>
|
||||
</template>
|
||||
<template x-if="!autoConversion">
|
||||
<span x-text="account.balance"></span><br>
|
||||
</template>)</span>
|
||||
<span class="small">
|
||||
@include('partials.elements.amount', ['autoConversion' => true,'amount' => 'account.balance','native' => 'account.native_balance'])
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@ -106,7 +103,9 @@
|
||||
<tr>
|
||||
<td>
|
||||
<template x-if="group.title">
|
||||
<span><a
|
||||
<span>
|
||||
TODO ICON
|
||||
<a
|
||||
:href="'{{route('transactions.show', '') }}/' + group.id"
|
||||
x-text="group.title"></a><br/></span>
|
||||
</template>
|
||||
@ -119,7 +118,22 @@
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="!group.title">
|
||||
<span><a
|
||||
<span>
|
||||
<!-- withdrawal -->
|
||||
<template
|
||||
x-if="transaction.type == 'withdrawal'">
|
||||
<span
|
||||
class="text-muted fa-solid fa-arrow-left fa-fw"></span>
|
||||
</template>
|
||||
<template x-if="transaction.type == 'deposit'">
|
||||
<span
|
||||
class="text-muted fa-solid fa-arrow-right fa-fw"></span>
|
||||
</template>
|
||||
<template x-if="transaction.type == 'transfer'">
|
||||
<span
|
||||
class="text-muted fa-solid fa-arrows-rotate fa-fw"></span>
|
||||
</template>
|
||||
<a
|
||||
:href="'{{route('transactions.show', '') }}/' + group.id"
|
||||
x-text="transaction.description"></a><br>
|
||||
</span>
|
||||
@ -133,12 +147,7 @@
|
||||
</template>
|
||||
<template x-for="transaction in group.transactions">
|
||||
<span>
|
||||
<template x-if="autoConversion">
|
||||
<span x-text="transaction.native_amount"></span><br>
|
||||
</template>
|
||||
<template x-if="!autoConversion">
|
||||
<span x-text="transaction.amount"></span><br>
|
||||
</template>
|
||||
@include('partials.elements.amount', ['autoConversion' => true,'amount' => 'transaction.amount','native' => 'transaction.native_amount'])
|
||||
</span>
|
||||
</template>
|
||||
</td>
|
||||
|
41
resources/views/v2/partials/elements/amount.blade.php
Normal file
41
resources/views/v2/partials/elements/amount.blade.php
Normal file
@ -0,0 +1,41 @@
|
||||
@if($autoConversion)
|
||||
<template x-if="autoConversion">
|
||||
<span>
|
||||
<template x-if="{{ $native }}_raw < 0">
|
||||
<span class="text-danger">
|
||||
<span x-text="{{ $native }}"></span>
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="{{ $native }}_raw >= 0">
|
||||
<span class="text-success">
|
||||
<span x-text="{{ $native }}"></span>
|
||||
</span>
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="!autoConversion">
|
||||
<span>
|
||||
<template x-if="{{ $amount }}_raw < 0">
|
||||
<span class="text-danger">
|
||||
<span x-text="{{ $amount }}"></span>
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="{{ $amount }}_raw >= 0">
|
||||
<span class="text-success">
|
||||
<span x-text="{{ $amount }}"></span>
|
||||
</span>
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
@else
|
||||
<template x-if="{{ $amount }}_raw < 0">
|
||||
<span class="text-danger">
|
||||
<span x-text="{{ $amount }}"></span>
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="{{ $amount }}_raw >= 0">
|
||||
<span class="text-success">
|
||||
<span x-text="{{ $amount }}"></span>
|
||||
</span>
|
||||
</template>
|
||||
@endif
|
@ -153,6 +153,20 @@ Route::group(
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* V2 API route for transactions
|
||||
*/
|
||||
Route::group(
|
||||
[
|
||||
'namespace' => 'FireflyIII\Api\V2\Controllers\Model\Transaction',
|
||||
'prefix' => 'v2/transactions',
|
||||
'as' => 'api.v2.transactions.',
|
||||
],
|
||||
static function () {
|
||||
Route::post('', ['uses' => 'StoreController@post', 'as' => 'store']);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* V2 API route for budgets and budget limits:
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user