Balance: Added Transfer and Admin view + more
* some work on reverting transactions. * Added TODO comments on incomplete features
This commit is contained in:
parent
7748d2d8a3
commit
01143e08e8
|
@ -0,0 +1,44 @@
|
||||||
|
<template>
|
||||||
|
<q-card-section class="fit row justify-left content-center items-center q-col-gutter-sm">
|
||||||
|
<div class="text-h6 col-6">
|
||||||
|
Aktueller Stand: {{ balance.balance.toFixed(2) }} €
|
||||||
|
<q-badge color="negative" align="top" v-if="isLocked"> gesperrt </q-badge>
|
||||||
|
</div>
|
||||||
|
<div v-if="showSelector" class="col-6">
|
||||||
|
<UserSelector :user="user" @update:user="userUpdated" />
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { ref, computed, defineComponent, onBeforeMount } from '@vue/composition-api';
|
||||||
|
import UserSelector from 'src/plugins/user/components/UserSelector.vue';
|
||||||
|
import { StateInterfaceBalance, UserBalance } from '../store/balance';
|
||||||
|
import { Store } from 'vuex';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
showSelector: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BalanceHeader',
|
||||||
|
components: { UserSelector },
|
||||||
|
props: ['showSelector'],
|
||||||
|
setup(props: Props, { root, emit }) {
|
||||||
|
onBeforeMount(() => void store.dispatch('balance/getBalance'));
|
||||||
|
const store = <Store<StateInterfaceBalance>>root.$store;
|
||||||
|
const user = ref(<FG.User>store.state.user.currentUser);
|
||||||
|
const balance = computed(() => {const balances = store.state.balance.balances; return balances.get(user.value.userid) || {balance: 0, limit: null} ;});
|
||||||
|
|
||||||
|
const isLocked = computed(() => balance.value.limit !== null && balance.value.balance >= balance.value.limit);
|
||||||
|
|
||||||
|
function userUpdated(selectedUser: FG.User) {
|
||||||
|
void store.dispatch('balance/getBalance', selectedUser);
|
||||||
|
user.value = selectedUser;
|
||||||
|
emit('update:user', selectedUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { user, balance, isLocked, userUpdated };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,76 @@
|
||||||
|
<template>
|
||||||
|
<q-card>
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn
|
||||||
|
:color="color()"
|
||||||
|
icon="mdi-trash-can"
|
||||||
|
aria-label="Löschen"
|
||||||
|
:disable="disabled()"
|
||||||
|
@click="reverse(transaction)"
|
||||||
|
/>
|
||||||
|
</q-card-actions>
|
||||||
|
<q-card-section>
|
||||||
|
<span>{{ timeStr }}: {{ transaction.amount }}</span>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
// TODO: Better styling
|
||||||
|
|
||||||
|
import { ref, computed, defineComponent, onUnmounted } from '@vue/composition-api';
|
||||||
|
import { hasPermission } from 'src/utils/permission';
|
||||||
|
import { formatDateTime } from 'src/utils/datetime';
|
||||||
|
import { StateInterfaceBalance } from 'src/plugins/balance/store/balance';
|
||||||
|
import { Store } from 'vuex';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
transaction: FG.Transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Transaction',
|
||||||
|
props: ['transaction'],
|
||||||
|
setup(props: Props, { root, emit }) {
|
||||||
|
const now = ref(Date.now());
|
||||||
|
const ival = setInterval(() => (now.value = Date.now()), 1000);
|
||||||
|
const store: Store<StateInterfaceBalance> = <Store<StateInterfaceBalance>>root.$store;
|
||||||
|
|
||||||
|
onUnmounted(() => clearInterval(ival));
|
||||||
|
|
||||||
|
function canReverse(transaction: FG.Transaction) {
|
||||||
|
return (
|
||||||
|
hasPermission('balance_reversal', store) ||
|
||||||
|
(transaction.sender_id === store.state.user.currentUser?.userid &&
|
||||||
|
Date.now() - transaction.time.getTime() < 10000)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function color() {
|
||||||
|
return canReverse(props.transaction) ? 'negative' : 'grey';
|
||||||
|
}
|
||||||
|
|
||||||
|
function disabled() {
|
||||||
|
return !canReverse(props.transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reverse(transaction: FG.Transaction) {
|
||||||
|
if (canReverse(transaction))
|
||||||
|
store
|
||||||
|
.dispatch('balance/revert', transaction)
|
||||||
|
.then(() => {
|
||||||
|
emit('reversed', transaction.id);
|
||||||
|
})
|
||||||
|
.catch(error => console.log(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeStr = computed(() => {
|
||||||
|
const elapsed = (now.value - props.transaction.time.getTime()) / 1000;
|
||||||
|
if (elapsed < 60) return `Vor ${elapsed.toFixed()} s`;
|
||||||
|
return formatDateTime(props.transaction.time, elapsed > 12 * 60 * 60, true, true) + ' Uhr';
|
||||||
|
});
|
||||||
|
|
||||||
|
return { timeStr, disabled, color, reverse };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -1,95 +1,123 @@
|
||||||
<template>
|
<template>
|
||||||
<q-page padding>
|
<q-page padding class="fit row justify-left q-col-gutter-sm">
|
||||||
|
<div class="col-12">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section class="row">
|
<BalanceHeader @update:user="userUpdated" :showSelector="showSelector" />
|
||||||
<div class="col-4 row q-pa-sm">
|
<q-separator />
|
||||||
|
|
||||||
|
<q-card-section class="row q-col-gutter-md" v-if="shortCuts">
|
||||||
|
<div v-for="(amount, index) in shortCuts" v-bind:key="index" class="col-4">
|
||||||
<q-btn
|
<q-btn
|
||||||
class="col"
|
color="primary"
|
||||||
color="green"
|
style="width: 100%"
|
||||||
label="2€"
|
:label="amount.toFixed(2).toString() + ' €'"
|
||||||
@click="changeBalance(-2)"
|
@click="changeBalance(amount)"
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col-4 row q-pa-sm">
|
|
||||||
<q-btn
|
|
||||||
class="col"
|
|
||||||
color="green"
|
|
||||||
label="1€"
|
|
||||||
@click="changeBalance(-1)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col-4 row q-pa-sm">
|
|
||||||
<q-btn
|
|
||||||
class="col"
|
|
||||||
color="green"
|
|
||||||
label="0,50€"
|
|
||||||
@click="changeBalance(-0.5)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col-4 row q-pa-sm">
|
|
||||||
<q-btn
|
|
||||||
class="col"
|
|
||||||
color="green"
|
|
||||||
label="0,40€"
|
|
||||||
@click="changeBalance(-0.4)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col-4 row q-pa-sm">
|
|
||||||
<q-btn
|
|
||||||
class="col"
|
|
||||||
color="green"
|
|
||||||
label="0,20€"
|
|
||||||
@click="changeBalance(-0.2)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col-4 row q-pa-sm">
|
|
||||||
<q-btn
|
|
||||||
class="col"
|
|
||||||
color="green"
|
|
||||||
label="0,10€"
|
|
||||||
@click="changeBalance(-0.1)"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section>
|
<q-card-section class="row q-col-gutter-md items-center">
|
||||||
<div class="text-h6">{{ balance.toFixed(2) }} €</div>
|
<div class="col-sm-4 col-xs-12">
|
||||||
|
<q-input
|
||||||
|
v-model.number="amount"
|
||||||
|
type="number"
|
||||||
|
filled
|
||||||
|
label="Eigener Betrag"
|
||||||
|
step="0.1"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 col-xs-6">
|
||||||
|
<q-btn
|
||||||
|
style="width: 100%"
|
||||||
|
color="primary"
|
||||||
|
label="Anschreiben"
|
||||||
|
@click="changeBalance(amount * -1)"
|
||||||
|
><q-tooltip>Rechtsklick um Betrag als Verknüpfung hinzuzufügen</q-tooltip>
|
||||||
|
<q-popup-proxy context-menu v-model="showAddShortcut">
|
||||||
|
<q-btn label="neue Verknüpfung" @click="addShortcut" /></q-popup-proxy
|
||||||
|
></q-btn>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 col-xs-6">
|
||||||
|
<q-btn
|
||||||
|
style="width: 100%"
|
||||||
|
color="secondary"
|
||||||
|
label="Gutschreiben"
|
||||||
|
@click="changeBalance(amount)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions>
|
|
||||||
<q-btn label="test" @click="$store.dispatch('balance/getBalance')" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card>
|
</q-card>
|
||||||
|
</div>
|
||||||
|
<div v-for="(transaction, index) in transactions" v-bind:key="index" class="col-sm-4 col-xs-6">
|
||||||
|
<Transaction :transaction="transaction" @reversed="reversed" />
|
||||||
|
</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, onBeforeMount } from '@vue/composition-api';
|
// TODO: Shortcuts are not displayed when first loaded
|
||||||
import { BalanceInterface } from 'src/plugins/balance/store/balance';
|
|
||||||
|
import { computed, ref, defineComponent, onBeforeMount } from '@vue/composition-api';
|
||||||
|
import { hasPermission } from 'src/utils/permission';
|
||||||
|
import { StateInterfaceBalance } from '../store/balance';
|
||||||
import { Store } from 'vuex';
|
import { Store } from 'vuex';
|
||||||
|
import Transaction from '../components/Transaction.vue';
|
||||||
|
import BalanceHeader from '../components/BalanceHeader.vue';
|
||||||
|
import PERMISSIONS from '../permissions';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
// name: 'PageName'
|
name: 'BalanceAdd',
|
||||||
|
components: { Transaction, BalanceHeader },
|
||||||
setup(_, { root }) {
|
setup(_, { root }) {
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => void store.dispatch('balance/getShortcuts'));
|
||||||
store.dispatch('balance/getBalance').catch(err => {
|
const store = <Store<StateInterfaceBalance>>root.$store;
|
||||||
console.warn(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const store: Store<{ balance: BalanceInterface }> = <
|
const amount = ref<number>(0);
|
||||||
Store<{ balance: BalanceInterface }>
|
const showAddShortcut = ref(false);
|
||||||
>root.$store;
|
const transactions = ref<FG.Transaction[]>([]);
|
||||||
|
const user = ref(store.state.user.currentUser);
|
||||||
|
const shortCuts = ref(store.state.balance.shortcuts);
|
||||||
|
|
||||||
const balance = computed<number>(() => {
|
const showSelector = computed(
|
||||||
return store.state.balance.balance;
|
() => hasPermission(PERMISSIONS.DEBIT, store) || hasPermission(PERMISSIONS.CREDIT, store)
|
||||||
});
|
);
|
||||||
|
|
||||||
|
function addShortcut() {
|
||||||
|
// TODO: Save shortcuts on backend
|
||||||
|
showAddShortcut.value = false;
|
||||||
|
console.log(amount);
|
||||||
|
}
|
||||||
|
function userUpdated(selectedUser: FG.User) {
|
||||||
|
user.value = selectedUser;
|
||||||
|
}
|
||||||
function changeBalance(amount: number) {
|
function changeBalance(amount: number) {
|
||||||
store
|
store
|
||||||
.dispatch('balance/changeBalance', amount)
|
.dispatch('balance/changeBalance', { amount: amount, user: user.value?.userid })
|
||||||
|
.then((transaction: FG.Transaction) => {
|
||||||
|
if (transactions.value.length > 5) transactions.value.pop();
|
||||||
|
transaction.time = new Date(transaction.time);
|
||||||
|
transactions.value.unshift(transaction);
|
||||||
|
console.log(transactions.value);
|
||||||
|
})
|
||||||
.catch(err => console.log(err));
|
.catch(err => console.log(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
return { balance, changeBalance };
|
function reversed(id: number) {
|
||||||
|
transactions.value = transactions.value.filter(t => t.id != id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
addShortcut,
|
||||||
|
showAddShortcut,
|
||||||
|
changeBalance,
|
||||||
|
transactions,
|
||||||
|
amount,
|
||||||
|
showSelector,
|
||||||
|
shortCuts,
|
||||||
|
reversed,
|
||||||
|
userUpdated
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<q-page padding>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<q-table :data="rows" row-key="userid" :columns="columns" />
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-page>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
// TODO: Load all users / balances
|
||||||
|
|
||||||
|
// TODO: Fill usefull data
|
||||||
|
|
||||||
|
import { computed, defineComponent } from '@vue/composition-api';
|
||||||
|
import { StateInterfaceBalance } from '../store/balance';
|
||||||
|
import { Store } from 'vuex';
|
||||||
|
export default defineComponent({
|
||||||
|
// name: 'PageName'
|
||||||
|
setup(_, { root }) {
|
||||||
|
const store = <Store<StateInterfaceBalance>>root.$store;
|
||||||
|
|
||||||
|
const rows = computed(() => {
|
||||||
|
const fo: Array<{ userid: string; balance: number }> = [];
|
||||||
|
store.state.balance.balances.forEach((value, key) =>
|
||||||
|
fo.push(Object.assign(value, { userid: key }))
|
||||||
|
);
|
||||||
|
return fo;
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'userid',
|
||||||
|
label: 'Benutzer ID',
|
||||||
|
field: 'userid',
|
||||||
|
required: true,
|
||||||
|
align: 'left',
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{ name: 'balance', label: 'Kontostand', field: 'balance' },
|
||||||
|
{
|
||||||
|
name: 'limit',
|
||||||
|
label: 'Limit',
|
||||||
|
field: 'limit',
|
||||||
|
format: (val: number) => (val === null ? 'keins' : val)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return { rows, columns };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,111 @@
|
||||||
|
<template>
|
||||||
|
<q-page padding class="fit row justify-left q-col-gutter-sm">
|
||||||
|
<div class="col-12">
|
||||||
|
<q-card>
|
||||||
|
<BalanceHeader @update:user="senderUpdated" :showSelector="showSelector" />
|
||||||
|
<q-separator />
|
||||||
|
<q-card-section class="row q-col-gutter-md items-center">
|
||||||
|
<div class="col-sm-4 col-xs-12">
|
||||||
|
<q-input
|
||||||
|
v-model.number="amount"
|
||||||
|
type="number"
|
||||||
|
filled
|
||||||
|
label="Betrag"
|
||||||
|
step="0.1"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 col-xs-6">
|
||||||
|
<UserSelector :user="receiver" @update:user="receiverUpdated" label="Empfänger" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 col-xs-6">
|
||||||
|
<q-btn
|
||||||
|
style="width: 100%"
|
||||||
|
color="primary"
|
||||||
|
:disable="sendDisabled"
|
||||||
|
label="Senden"
|
||||||
|
@click="sendAmount"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
<div v-for="(transaction, index) in transactions" v-bind:key="index" class="col-sm-4 col-xs-6">
|
||||||
|
<Transaction :transaction="transaction" @reversed="reversed" />
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, ref, defineComponent } from '@vue/composition-api';
|
||||||
|
import { hasPermission } from 'src/utils/permission';
|
||||||
|
import { StateInterfaceBalance } from '../store/balance';
|
||||||
|
import { Store } from 'vuex';
|
||||||
|
import UserSelector from 'src/plugins/user/components/UserSelector.vue';
|
||||||
|
import Transaction from '../components/Transaction.vue';
|
||||||
|
import BalanceHeader from '../components/BalanceHeader.vue';
|
||||||
|
import PERMISSIONS from '../permissions';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BalanceTransfer',
|
||||||
|
components: { Transaction, BalanceHeader, UserSelector },
|
||||||
|
setup(_, { root }) {
|
||||||
|
const store: Store<StateInterfaceBalance> = <Store<StateInterfaceBalance>>root.$store;
|
||||||
|
|
||||||
|
const showSelector = computed(() => hasPermission(PERMISSIONS.SEND_OTHER, store));
|
||||||
|
const sender = ref(store.state.user.currentUser);
|
||||||
|
const receiver = ref<FG.User | undefined>(undefined);
|
||||||
|
const amount = ref<number>(0);
|
||||||
|
const transactions = ref<FG.Transaction[]>([]);
|
||||||
|
|
||||||
|
const sendDisabled = computed(() => {
|
||||||
|
return !(
|
||||||
|
receiver.value &&
|
||||||
|
sender.value &&
|
||||||
|
sender.value.userid != receiver.value.userid &&
|
||||||
|
amount.value > 0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function senderUpdated(selectedUser: FG.User) {
|
||||||
|
console.log(selectedUser);
|
||||||
|
sender.value = selectedUser;
|
||||||
|
}
|
||||||
|
function receiverUpdated(selectedUser: FG.User) {
|
||||||
|
receiver.value = selectedUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reversed(id: number) {
|
||||||
|
transactions.value = transactions.value.filter(value => value.id != id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendAmount() {
|
||||||
|
store
|
||||||
|
.dispatch('balance/changeBalance', {
|
||||||
|
amount: amount.value,
|
||||||
|
sender: sender.value?.userid,
|
||||||
|
user: receiver.value?.userid
|
||||||
|
})
|
||||||
|
.then((transaction: FG.Transaction) => {
|
||||||
|
if (transactions.value.length > 5) transactions.value.pop();
|
||||||
|
transaction.time = new Date(transaction.time);
|
||||||
|
transactions.value.unshift(transaction);
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
sender,
|
||||||
|
receiver,
|
||||||
|
amount,
|
||||||
|
sendAmount,
|
||||||
|
transactions,
|
||||||
|
showSelector,
|
||||||
|
senderUpdated,
|
||||||
|
receiverUpdated,
|
||||||
|
sendDisabled,
|
||||||
|
reversed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,21 @@
|
||||||
|
const PERMISSIONS = {
|
||||||
|
// Show own and others balance
|
||||||
|
SHOW: 'balance_show',
|
||||||
|
SHOW_OTHER: 'balance_show_others',
|
||||||
|
// Credit balance (give)
|
||||||
|
CREDIT: 'balance_credit',
|
||||||
|
// Debit balance (take)
|
||||||
|
DEBIT: 'balance_debit',
|
||||||
|
// Debit own balance only
|
||||||
|
DEBIT_OWN: 'balance_debit_own',
|
||||||
|
// Send from to other
|
||||||
|
SEND: 'balance_send',
|
||||||
|
// Send from other to another
|
||||||
|
SEND_OTHER: 'balance_send_others',
|
||||||
|
// Can set limit for users
|
||||||
|
SET_LIMIT: 'balance_set_limit',
|
||||||
|
//Allow sending / sub while exceeding the set limit
|
||||||
|
EXCEED_LIMIT: 'balance_exceed_limit'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PERMISSIONS;
|
|
@ -1,12 +1,12 @@
|
||||||
import { Module } from 'vuex';
|
import { Module } from 'vuex';
|
||||||
import { StateInterface } from 'src/store';
|
import { StateInterface } from 'src/store';
|
||||||
import mainRoutes from './routes';
|
import routes from './routes';
|
||||||
import { FG_Plugin } from 'src/plugins';
|
import { FG_Plugin } from 'src/plugins';
|
||||||
import balance, { BalanceInterface } from './store/balance';
|
import balance, { BalanceInterface } from './store/balance';
|
||||||
|
|
||||||
const plugin: FG_Plugin.Plugin = {
|
const plugin: FG_Plugin.Plugin = {
|
||||||
name: 'Balance',
|
name: 'Balance',
|
||||||
mainRoutes,
|
mainRoutes: routes,
|
||||||
requiredModules: ['User'],
|
requiredModules: ['User'],
|
||||||
requiredBackendModules: ['balance'],
|
requiredBackendModules: ['balance'],
|
||||||
version: '0.0.1',
|
version: '0.0.1',
|
||||||
|
|
|
@ -1,24 +1,5 @@
|
||||||
import { FG_Plugin } from 'src/plugins';
|
import { FG_Plugin } from 'src/plugins';
|
||||||
|
import permissions from '../permissions';
|
||||||
const permissions = {
|
|
||||||
// Show own and others balance
|
|
||||||
SHOW: 'balance_show',
|
|
||||||
SHOW_OTHER: 'balance_show_others',
|
|
||||||
// Credit balance (give)
|
|
||||||
CREDIT: 'balance_credit',
|
|
||||||
// Debit balance (take)
|
|
||||||
DEBIT: 'balance_debit',
|
|
||||||
// Debit own balance only
|
|
||||||
DEBIT_OWN: 'balance_debit_own',
|
|
||||||
// Send from to other
|
|
||||||
SEND: 'balance_send',
|
|
||||||
// Send from other to another
|
|
||||||
SEND_OTHER: 'balance_send_others',
|
|
||||||
// Can set limit for users
|
|
||||||
SET_LIMIT: 'balance_set_limit',
|
|
||||||
//Allow sending / sub while exceeding the set limit
|
|
||||||
EXCEED_LIMIT: 'balance_exceed_limit'
|
|
||||||
};
|
|
||||||
|
|
||||||
const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
|
const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
|
||||||
{
|
{
|
||||||
|
@ -26,17 +7,33 @@ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
|
||||||
icon: 'mdi-cash-100',
|
icon: 'mdi-cash-100',
|
||||||
path: 'balance',
|
path: 'balance',
|
||||||
name: 'balance',
|
name: 'balance',
|
||||||
component: () => import('../pages/MainPage.vue'),
|
redirect: { name: 'balance-add' },
|
||||||
meta: { permissions: ['user'] },
|
meta: { permissions: ['user'] },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
title: 'Anschreiben',
|
title: 'Anschreiben',
|
||||||
icon: 'mdi-cash-100',
|
icon: 'mdi-cash-plus',
|
||||||
path: 'balance-add',
|
path: 'add',
|
||||||
name: 'balance-add',
|
name: 'balance-add',
|
||||||
shortcut: true,
|
shortcut: true,
|
||||||
meta: { permissions: [permissions.DEBIT_OWN, permissions.SHOW] },
|
meta: { permissions: [permissions.DEBIT_OWN, permissions.SHOW] },
|
||||||
component: () => import('../pages/Add.vue')
|
component: () => import('../pages/Add.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Übertragen',
|
||||||
|
icon: 'mdi-cash-refund',
|
||||||
|
path: 'transfer',
|
||||||
|
name: 'balance-transfer',
|
||||||
|
meta: { permissions: [permissions.SEND] },
|
||||||
|
component: () => import('../pages/Transfer.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Verwaltung',
|
||||||
|
icon: 'mdi-account-cash',
|
||||||
|
path: 'admin',
|
||||||
|
name: 'balance-admin',
|
||||||
|
meta: { permissions: [permissions.DEBIT_OWN, permissions.SHOW] },
|
||||||
|
component: () => import('../pages/Admin.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,51 +9,75 @@ interface BalanceResponse {
|
||||||
debit: number;
|
debit: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BalanceInterface extends BalanceResponse {
|
export interface UserBalance extends BalanceResponse {
|
||||||
limit: number;
|
limit: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BalanceInterface {
|
||||||
|
balances: Map<string, UserBalance>;
|
||||||
|
shortcuts: Array<number>;
|
||||||
loading: number;
|
loading: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StateInterfaceBalance extends StateInterface {
|
||||||
|
balance: BalanceInterface;
|
||||||
|
}
|
||||||
|
|
||||||
const state: BalanceInterface = {
|
const state: BalanceInterface = {
|
||||||
balance: 0,
|
balances: new Map<string, UserBalance>(),
|
||||||
credit: 0,
|
shortcuts: [],
|
||||||
debit: 0,
|
|
||||||
limit: 0,
|
|
||||||
loading: 0
|
loading: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutations: MutationTree<BalanceInterface> = {
|
const mutations: MutationTree<BalanceInterface> = {
|
||||||
setBalance(state, data: number) {
|
setBalance(state, data: { userid: string; balance: BalanceResponse }) {
|
||||||
state.balance = data;
|
state.balances.set(
|
||||||
|
data.userid,
|
||||||
|
Object.assign({ limit: state.balances.get(data.userid)?.limit || null }, data.balance)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
setCredit(state, data: number) {
|
changeBalance(state, data: { userid: string; amount: number }) {
|
||||||
state.credit = data;
|
const user = <UserBalance>state.balances.get(data.userid);
|
||||||
|
if (data.amount < 0) user.debit += data.amount;
|
||||||
|
else user.credit += data.amount;
|
||||||
|
user.balance += data.amount;
|
||||||
},
|
},
|
||||||
setDebit(state, data: number) {
|
setLimit(state, data: { userid: string; limit: number | null }) {
|
||||||
state.debit = data;
|
if (state.balances.has(data.userid))
|
||||||
},
|
(<UserBalance>state.balances.get(data.userid)).limit = data.limit;
|
||||||
setLimit(state, data: number) {
|
else state.balances.set(data.userid, { balance: 0, debit: 0, credit: 0, limit: data.limit });
|
||||||
state.limit = data;
|
|
||||||
},
|
},
|
||||||
setLoading(state, data = true) {
|
setLoading(state, data = true) {
|
||||||
if (data) state.loading += 1;
|
if (data) state.loading += 1;
|
||||||
else state.loading -= 1;
|
else state.loading -= 1;
|
||||||
|
},
|
||||||
|
setShortcuts(state, data: Array<number>) {
|
||||||
|
state.shortcuts = data.sort().reverse();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions: ActionTree<BalanceInterface, StateInterface> = {
|
const actions: ActionTree<BalanceInterface, StateInterface> = {
|
||||||
getBalance({ commit, rootState }) {
|
getShortcuts({ commit, state, rootState }, force = false) {
|
||||||
|
if (force || state.shortcuts.length == 0) {
|
||||||
commit('setLoading');
|
commit('setLoading');
|
||||||
axios
|
const user = <FG.User>rootState.user.currentUser;
|
||||||
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
|
return axios
|
||||||
.get(`/users/${rootState.user.currentUser?.userid}/balance`)
|
.get(`/users/${user.userid}/balance/shortcuts`)
|
||||||
.then(({ data }: AxiosResponse<BalanceResponse>) => {
|
.then(({ data }: AxiosResponse<BalanceResponse>) => {
|
||||||
commit('setBalance', data.balance);
|
commit('setShortcuts', data);
|
||||||
commit('setCredit', data.credit);
|
return data;
|
||||||
commit('setDebit', data.debit);
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.finally(() => commit('setLoading', false));
|
||||||
console.warn(err);
|
}
|
||||||
|
},
|
||||||
|
getBalance({ commit, rootState }, user: FG.User | undefined = undefined) {
|
||||||
|
commit('setLoading');
|
||||||
|
if (!user) user = <FG.User>rootState.user.currentUser;
|
||||||
|
return axios
|
||||||
|
.get(`/users/${user.userid}/balance`)
|
||||||
|
.then(({ data }: AxiosResponse<BalanceResponse>) => {
|
||||||
|
commit('setBalance', { userid: user?.userid, balance: data });
|
||||||
|
return data;
|
||||||
})
|
})
|
||||||
.finally(() => commit('setLoading', false));
|
.finally(() => commit('setLoading', false));
|
||||||
},
|
},
|
||||||
|
@ -70,19 +94,32 @@ const actions: ActionTree<BalanceInterface, StateInterface> = {
|
||||||
})
|
})
|
||||||
.finally(() => commit('setLoading', false));
|
.finally(() => commit('setLoading', false));
|
||||||
},
|
},
|
||||||
changeBalance({ rootState, dispatch, commit }, amount: number) {
|
revert({ dispatch }, transaction: FG.Transaction) {
|
||||||
commit('setLoading');
|
return axios.delete(`/balance/${transaction.id}`).then(() => {
|
||||||
axios
|
|
||||||
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
|
|
||||||
.put(`/users/${rootState.user.currentUser?.userid}/balance`, <
|
|
||||||
{ amount: number }
|
|
||||||
>{
|
|
||||||
amount: amount
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
dispatch('getBalance').catch(err => console.warn(err));
|
dispatch('getBalance').catch(err => console.warn(err));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
changeBalance({ dispatch, commit }, data: { amount: number; user: string; sender?: string }) {
|
||||||
|
commit('setLoading');
|
||||||
|
return axios
|
||||||
|
.put(`/users/${data.user}/balance`, data)
|
||||||
|
.then((response: AxiosResponse<FG.Transaction>) => {
|
||||||
|
commit(state.balances.has(data.user) ? 'changeBalance' : 'setBalance', {
|
||||||
|
userid: data.user,
|
||||||
|
amount: data.amount
|
||||||
|
});
|
||||||
|
if (data.sender)
|
||||||
|
commit(state.balances.has(data.sender) ? 'changeBalance' : 'setBalance', {
|
||||||
|
userid: data.sender,
|
||||||
|
amount: -1 * data.amount
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
// Maybe Balance changed
|
||||||
|
dispatch('getBalance').catch(err => console.warn(err));
|
||||||
|
console.warn(err);
|
||||||
})
|
})
|
||||||
.catch(err => console.warn(err))
|
|
||||||
.finally(() => commit('setLoading', false));
|
.finally(() => commit('setLoading', false));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue