Compare commits

..

5 Commits

9 changed files with 103 additions and 32 deletions

View File

@ -1,6 +1,6 @@
{ {
"license": "MIT", "license": "MIT",
"version": "1.0.0-alpha.3", "version": "1.0.0",
"name": "@flaschengeist/balance", "name": "@flaschengeist/balance",
"author": "Ferdinand Thiessen <rpm@fthiessen.de>", "author": "Ferdinand Thiessen <rpm@fthiessen.de>",
"homepage": "https://flaschengeist.dev/Flaschengeist", "homepage": "https://flaschengeist.dev/Flaschengeist",
@ -19,8 +19,8 @@
"lint": "eslint --ext .js,.ts,.vue ./src" "lint": "eslint --ext .js,.ts,.vue ./src"
}, },
"devDependencies": { "devDependencies": {
"@flaschengeist/api": "^1.0.0-alpha.8", "@flaschengeist/api": "^1.0.0",
"@flaschengeist/types": "^1.0.0-alpha.10", "@flaschengeist/types": "^1.0.0",
"@quasar/app-webpack": "^3.7.2", "@quasar/app-webpack": "^3.7.2",
"@typescript-eslint/eslint-plugin": "^5.8.0", "@typescript-eslint/eslint-plugin": "^5.8.0",
"@typescript-eslint/parser": "^5.8.0", "@typescript-eslint/parser": "^5.8.0",
@ -37,8 +37,8 @@
"typescript": "^4.5.4" "typescript": "^4.5.4"
}, },
"peerDependencies": { "peerDependencies": {
"@flaschengeist/api": "1.0.0-alpha.8", "@flaschengeist/api": "1.0.0",
"@flaschengeist/users": "1.0.0-alpha.4" "@flaschengeist/users": "1.0.0"
}, },
"prettier": { "prettier": {
"singleQuote": true, "singleQuote": true,

View File

@ -1,6 +1,16 @@
<template> <template>
<div class="col-sm-4 col-xs-12"> <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" suffix="€" /> <q-input
v-model.number="amount"
ref="refAmount"
type="number"
filled
label="Eigener Betrag"
step="0.1"
min="0"
suffix="€"
:rules="[check_cent_step]"
/>
</div> </div>
<div class="col-sm-4 col-xs-6"> <div class="col-sm-4 col-xs-6">
<q-btn style="width: 100%" color="primary" label="Anschreiben" @click="changeBalance(amount * -1)"> <q-btn style="width: 100%" color="primary" label="Anschreiben" @click="changeBalance(amount * -1)">
@ -22,9 +32,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, PropType, computed, watch } from 'vue'; import { defineComponent, ref, PropType, computed, watch } from 'vue';
import { QInput } from 'quasar';
import { useBalanceStore } from '../store'; import { useBalanceStore } from '../store';
import { hasPermission, useUserStore } from '@flaschengeist/api'; import { hasPermission, useUserStore } from '@flaschengeist/api';
import PERMISSIONS from '../permissions'; import PERMISSIONS from '../permissions';
import { check_cent_step } from '../utils';
export default defineComponent({ export default defineComponent({
name: 'BalanceAddBody', name: 'BalanceAddBody',
props: { props: {
@ -44,12 +56,15 @@ export default defineComponent({
const store = useBalanceStore(); const store = useBalanceStore();
const userStore = useUserStore(); const userStore = useUserStore();
const amount = ref<number>(0); const amount = ref<number>(0);
const refAmount = ref<QInput>();
async function changeBalance(amount: number) { async function changeBalance(amount: number) {
await store.changeBalance(amount, user.value); await refAmount.value?.validate();
if (amount != 0 && !refAmount.value?.hasError) await store.changeBalance(amount, user.value);
emit('change-balance', user.value); emit('change-balance', user.value);
} }
function addShortcut() { async function addShortcut() {
if (amount.value != 0) void store.createShortcut(amount.value * -1); await refAmount.value?.validate();
if (amount.value != 0 && !refAmount.value?.hasError) void store.createShortcut(amount.value * -1);
} }
const canAddCredit = hasPermission(PERMISSIONS.CREDIT); const canAddCredit = hasPermission(PERMISSIONS.CREDIT);
const user = computed(() => const user = computed(() =>
@ -60,7 +75,7 @@ export default defineComponent({
watch(amount, (a) => { watch(amount, (a) => {
amount.value = Math.abs(a); amount.value = Math.abs(a);
}); });
return { changeBalance, addShortcut, canAddCredit, amount }; return { changeBalance, addShortcut, canAddCredit, amount, check_cent_step, refAmount };
}, },
}); });
</script> </script>

View File

@ -10,9 +10,11 @@
<div v-if="showSelector" class="col-6"> <div v-if="showSelector" class="col-6">
<UserSelector v-model="user" /> <UserSelector v-model="user" />
</div> </div>
<div class="col-1 justify-end"> <div class="col">
<div class="row fit justify-end content-end items-end">
<q-btn round flat icon="mdi-format-list-checks" @click="openHistory" /> <q-btn round flat icon="mdi-format-list-checks" @click="openHistory" />
</div> </div>
</div>
</q-card-section> </q-card-section>
</template> </template>

View File

@ -1,6 +1,16 @@
<template> <template>
<div class="col-sm-4 col-xs-12"> <div class="col-sm-4 col-xs-12">
<q-input v-model.number="amount" type="number" filled label="Betrag" step="0.1" min="0" suffix="€" /> <q-input
v-model.number="amount"
ref="refAmount"
type="number"
filled
label="Betrag"
step="0.1"
min="0"
suffix="€"
:rules="[check_cent_step]"
/>
</div> </div>
<div class="col-sm-4 col-xs-6"> <div class="col-sm-4 col-xs-6">
<UserSelector v-model="receiver" label="Empfänger" /> <UserSelector v-model="receiver" label="Empfänger" />
@ -11,8 +21,10 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, PropType, ref } from 'vue'; import { computed, defineComponent, PropType, ref } from 'vue';
import { QInput } from 'quasar';
import UserSelector from '@flaschengeist/users/src/components/UserSelector.vue'; import UserSelector from '@flaschengeist/users/src/components/UserSelector.vue';
import { useBalanceStore } from '../store'; import { useBalanceStore } from '../store';
import { check_cent_step } from '../utils';
import { useUserStore } from '@flaschengeist/api'; import { useUserStore } from '@flaschengeist/api';
export default defineComponent({ export default defineComponent({
name: 'BalanceTransferBody', name: 'BalanceTransferBody',
@ -31,6 +43,7 @@ export default defineComponent({
const userStore = useUserStore(); const userStore = useUserStore();
const receiver = ref<FG.User | undefined>(undefined); const receiver = ref<FG.User | undefined>(undefined);
const amount = ref<number>(0); const amount = ref<number>(0);
const refAmount = ref<QInput>();
const sender = computed(() => const sender = computed(() =>
(<FG.User>props.user).userid (<FG.User>props.user).userid
? <FG.User>props.user ? <FG.User>props.user
@ -38,11 +51,18 @@ export default defineComponent({
); );
const sendDisabled = computed(() => { const sendDisabled = computed(() => {
return !(receiver.value && sender.value && sender.value.userid != receiver.value.userid && amount.value > 0); return !(
receiver.value &&
sender.value &&
sender.value.userid != receiver.value.userid &&
amount.value > 0 &&
!refAmount.value?.hasError
);
}); });
async function sendAmount() { async function sendAmount() {
if (receiver.value) { await refAmount.value?.validate();
if (receiver.value && !refAmount.value?.hasError) {
await store.changeBalance(amount.value, receiver.value, sender.value); await store.changeBalance(amount.value, receiver.value, sender.value);
emit('changeBalance', { sender: sender.value, receiver: receiver.value }); emit('changeBalance', { sender: sender.value, receiver: receiver.value });
} }
@ -53,6 +73,8 @@ export default defineComponent({
amount, amount,
sendAmount, sendAmount,
sendDisabled, sendDisabled,
refAmount,
check_cent_step,
}; };
}, },
}); });

View File

@ -11,7 +11,7 @@ const BalanceTypes = {
sub_from: 0x04, sub_from: 0x04,
}; };
function transpile(msg: FG_Plugin.Notification) { function transpile (msg: FG_Plugin.Notification) {
console.log('notification:', msg); console.log('notification:', msg);
const message = msg as BalanceNotification; const message = msg as BalanceNotification;
message.icon = 'mdi-cash'; message.icon = 'mdi-cash';
@ -20,8 +20,7 @@ function transpile(msg: FG_Plugin.Notification) {
if (message.data.type === BalanceTypes.send_from) { if (message.data.type === BalanceTypes.send_from) {
const receiver = <FG.User>store.findUser((<SendFromNotification>message.data).receiver_id); const receiver = <FG.User>store.findUser((<SendFromNotification>message.data).receiver_id);
const author = <FG.User>store.findUser((<SendFromNotification>message.data).author_id); const author = <FG.User>store.findUser((<SendFromNotification>message.data).author_id);
message.text = `${author.display_name} hat ${message.data.amount.toFixed(2)}€ von dir zu ${ message.text = `${author.display_name} hat ${message.data.amount.toFixed(2)}€ von dir zu ${receiver.display_name
receiver.display_name
} überwiesen.`; } überwiesen.`;
} else if (message.data.type === BalanceTypes.send_to) { } else if (message.data.type === BalanceTypes.send_to) {
const sender = <FG.User>store.findUser((<SendToNotification>message.data).sender_id); const sender = <FG.User>store.findUser((<SendToNotification>message.data).sender_id);
@ -40,7 +39,7 @@ const plugin: FG_Plugin.Plugin = {
name: 'Balance', name: 'Balance',
innerRoutes: routes, innerRoutes: routes,
requiredModules: [['balance']], requiredModules: [['balance']],
version: '0.0.2', version: '1.0.0',
notification: transpile, notification: transpile,
widgets: [ widgets: [
{ {

View File

@ -15,7 +15,17 @@
<q-icon name="mdi-magnify" /> <q-icon name="mdi-magnify" />
</template> </template>
</q-input> </q-input>
<q-input v-model.number="limit" label="Limit" type="number" step="0.01" suffix="€" filled dense> <q-input
ref="refLimit"
v-model.number="limit"
label="Limit"
type="number"
step="0.01"
suffix="€"
filled
dense
:rules="[check_cent_step]"
>
<template #prepend> <template #prepend>
<q-btn <q-btn
:icon="sign === '+' ? 'mdi-plus' : 'mdi-minus'" :icon="sign === '+' ? 'mdi-plus' : 'mdi-minus'"
@ -107,6 +117,8 @@
<script lang="ts"> <script lang="ts">
import { ref, defineComponent, computed, onBeforeMount } from 'vue'; import { ref, defineComponent, computed, onBeforeMount } from 'vue';
import { QInput } from 'quasar';
import { check_cent_step } from '../utils';
import { useBalanceStore } from '../store'; import { useBalanceStore } from '../store';
import { useUserStore } from '@flaschengeist/api'; import { useUserStore } from '@flaschengeist/api';
import BalanceAddBody from '../components/BalanceAddBody.vue'; import BalanceAddBody from '../components/BalanceAddBody.vue';
@ -120,6 +132,7 @@ export default defineComponent({
const userStore = useUserStore(); const userStore = useUserStore();
const showMenu = ref<{ [userid: string]: boolean }>({}); const showMenu = ref<{ [userid: string]: boolean }>({});
const refLimit = ref<QInput>();
onBeforeMount(() => { onBeforeMount(() => {
void userStore.getUsers(); void userStore.getUsers();
@ -241,6 +254,10 @@ export default defineComponent({
if (sign.value === '-') { if (sign.value === '-') {
l = -l; l = -l;
} }
await refLimit.value?.validate();
if (refLimit.value?.hasError) {
return;
}
await store.setLimit(l, userid); await store.setLimit(l, userid);
limit.value = undefined; limit.value = undefined;
} }
@ -257,10 +274,14 @@ export default defineComponent({
sign.value = sign.value === '+' ? '-' : '+'; sign.value = sign.value === '+' ? '-' : '+';
} }
function setLimits(l: number) { async function setLimits(l: number) {
if (sign.value === '-') { if (sign.value === '-') {
l = -l; l = -l;
} }
await refLimit.value?.validate();
if (refLimit.value?.hasError) {
return;
}
void store.setLimits(l); void store.setLimits(l);
} }
@ -285,6 +306,8 @@ export default defineComponent({
showMenu, showMenu,
sign, sign,
changeSign, changeSign,
check_cent_step,
refLimit,
}; };
}, },
}); });

View File

@ -28,7 +28,7 @@
</q-list> </q-list>
<q-list v-if="show"> <q-list v-if="show">
<div v-for="(transaction, index) in transactions" :key="index" class="col-sm-12"> <div v-for="(transaction, index) in transactions" :key="index" class="col-sm-12">
<balance-transaction v-model:transaction="transactions[index]" /> <balance-transaction v-model:transaction="transactions[index]" @update:transaction="updateBalance" />
</div> </div>
</q-list> </q-list>
</q-drawer> </q-drawer>
@ -128,6 +128,7 @@ export default defineComponent({
tabs, tabs,
transactions, transactions,
show, show,
updateBalance: () => balanceStore.getBalance(mainStore.currentUser),
}; };
}, },
}); });

View File

@ -146,7 +146,13 @@ export const useBalanceStore = defineStore({
params: filter, params: filter,
}); });
data.transactions.forEach((t) => fixTransaction(t)); data.transactions.forEach((t) => fixTransaction(t));
if (data.transactions) this.transactions.push(...data.transactions); if (data.transactions) {
data.transactions.forEach((t) => {
const idx = this.transactions.findIndex((x) => x.id === t.id);
if (idx == -1) this.transactions.push(t);
else this.transactions[idx] = t;
});
}
return data; return data;
}, },

3
src/utils/index.ts Normal file
View File

@ -0,0 +1,3 @@
export function check_cent_step(value: number): boolean | string {
return (value * 100) % 1 === 0 || 'Betrag muss in 1-Cent-Schritten angegeben werden';
}