forked from Kispi/Core
feat(core): add company bank accounts
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
5aea42d33a
commit
a2d4d89776
5 changed files with 178 additions and 45 deletions
|
@ -300,6 +300,18 @@
|
|||
"type": "auth",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"id": "h5ogbj93",
|
||||
"name": "accountNumber",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cvcgnf4x",
|
||||
"name": "name",
|
||||
|
@ -329,5 +341,57 @@
|
|||
"onlyEmailDomains": null,
|
||||
"requireEmail": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5msnfxat1sc2c1r",
|
||||
"name": "companyTransactions",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"id": "7rnyupog",
|
||||
"name": "account",
|
||||
"type": "relation",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"collectionId": "s854d2w72fvyl54",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ruby9fp9",
|
||||
"name": "label",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "4hiu46ib",
|
||||
"name": "amount",
|
||||
"type": "number",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"viewRule": "",
|
||||
"createRule": "",
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
}
|
||||
]
|
|
@ -1,11 +1,22 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="account"
|
||||
v-if="account || company"
|
||||
class="hero-content flex-col pt-0 text-center lg:p-6"
|
||||
>
|
||||
<h1 class="mt-5 text-8xl">{{ CurrencyService.toString(getBalance()) }}</h1>
|
||||
<h3 class="mt-10 text-5xl font-bold">Kontoinhaber: {{ `${account.firstName} ${account.lastName}` }}</h3>
|
||||
<h4 class="mb-10 text-4xl">Kontonummer: {{ account.accountNumber }}</h4>
|
||||
<h3
|
||||
v-if="account"
|
||||
class="mt-10 text-5xl font-bold"
|
||||
>
|
||||
Kontoinhaber: {{ `${account.firstName} ${account.lastName}` }}
|
||||
</h3>
|
||||
<h3
|
||||
v-if="company"
|
||||
class="mt-10 text-5xl font-bold"
|
||||
>
|
||||
Firma: {{ company.name }}
|
||||
</h3>
|
||||
<h4 class="mb-10 text-4xl">Kontonummer: {{ (account ? account : company ? company : null)?.accountNumber }}</h4>
|
||||
<MoleculeTransactionTable
|
||||
v-if="transactions"
|
||||
class="mb-24 max-w-[42rem]"
|
||||
|
@ -14,7 +25,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="account"
|
||||
v-if="account || company"
|
||||
class="fixed bottom-0 flex w-full place-content-center gap-16 bg-base-100 p-5 lg:w-[calc(100%-16rem)] lg:gap-32"
|
||||
>
|
||||
<MoleculeInputModal
|
||||
|
@ -72,10 +83,11 @@
|
|||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { onKeyStroke, promiseTimeout } from '@vueuse/core';
|
||||
import { UnsubscribeFunc } from 'pocketbase';
|
||||
import { AccountsResponse, TransactionsResponse } from '../../types/pocketbase.types';
|
||||
import { RecordSubscription, UnsubscribeFunc } from 'pocketbase';
|
||||
import { AccountsResponse, CompaniesResponse, TransactionsResponse } from '../../types/pocketbase.types';
|
||||
import { AccountService } from '../../services/account.service';
|
||||
import { BankService, TransactionType } from '../../services/bank.service';
|
||||
import { AccountType, BankService, TransactionType } from '../../services/bank.service';
|
||||
import { CompanyService } from '../../services/company.service';
|
||||
import { CurrencyService } from '../../services/currency.service';
|
||||
import { ChevronDownIcon, ChevronUpIcon, XCircleIcon } from '@heroicons/vue/24/outline';
|
||||
import AtomHeroText from '../atoms/AtomHeroText.vue';
|
||||
|
@ -84,8 +96,10 @@ import MoleculeTransactionTable from '../molecules/MoleculeTransactionTable.vue'
|
|||
|
||||
const router = useRouter();
|
||||
const subscription = ref<UnsubscribeFunc>();
|
||||
const companySubscription = ref<UnsubscribeFunc>();
|
||||
|
||||
const account = ref<AccountsResponse|null>();
|
||||
const company = ref<CompaniesResponse|null>();
|
||||
const transactions = ref<TransactionsResponse[]>();
|
||||
const depositModal = ref<InstanceType<typeof MoleculeInputModal>>();
|
||||
const withdrawModal = ref<InstanceType<typeof MoleculeInputModal>>();
|
||||
|
@ -138,14 +152,20 @@ async function getAccount() {
|
|||
transactions.value = await BankService.getTransactions(account.value.id);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (e: any) {
|
||||
if(e.status == 404) {
|
||||
error.value = 'Der Account wurde nicht gefunden.';
|
||||
await promiseTimeout(4000);
|
||||
error.value = '';
|
||||
} else {
|
||||
error.value = 'Ein unerwarteter Fehler ist aufgetreten. Bitte kontaktiere einen Administrator.';
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
try {
|
||||
company.value = await CompanyService.getCompanyByAccountNumber(accountId.value);
|
||||
transactions.value = await BankService.getCompanyTransactions(company.value.id);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (e: any) {
|
||||
if(e.status == 404) {
|
||||
error.value = 'Der Account wurde nicht gefunden.';
|
||||
await promiseTimeout(4000);
|
||||
error.value = '';
|
||||
} else {
|
||||
error.value = 'Ein unerwarteter Fehler ist aufgetreten. Bitte kontaktiere einen Administrator.';
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,50 +178,61 @@ async function handleDeposit(amount: string) {
|
|||
const amountNumber = parseInt(amount);
|
||||
if(!isNaN(amountNumber) && account.value) {
|
||||
await BankService.addTransaction(account.value.id, amountNumber, TransactionType.DEPOSIT);
|
||||
} else if(!isNaN(amountNumber) && company.value) {
|
||||
await BankService.addTransaction(company.value.id, amountNumber, TransactionType.DEPOSIT, AccountType.COMPANY);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleWithdraw(amount: string) {
|
||||
const amountNumber = parseInt(amount);
|
||||
if(!isNaN(amountNumber) && account.value) {
|
||||
if(parseInt(amount) * 100 <= getBalance()) {
|
||||
if(parseInt(amount) * 100 <= getBalance()) {
|
||||
if(!isNaN(amountNumber) && account.value) {
|
||||
// TODO: add max amount to input field and display custom error message
|
||||
await BankService.addTransaction(account.value.id, -amountNumber, TransactionType.WITHDRAW);
|
||||
} else {
|
||||
depositError.value = true;
|
||||
const timeout = setTimeout(() => {
|
||||
depositError.value = false;
|
||||
clearTimeout(timeout);
|
||||
}, 5000);
|
||||
} else if(!isNaN(amountNumber) && company.value) {
|
||||
await BankService.addTransaction(company.value.id, -amountNumber, TransactionType.WITHDRAW, AccountType.COMPANY);
|
||||
}
|
||||
} else {
|
||||
depositError.value = true;
|
||||
const timeout = setTimeout(() => {
|
||||
depositError.value = false;
|
||||
clearTimeout(timeout);
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function handleRealtimeUpdates(transactionSubscription: RecordSubscription<TransactionsResponse>) {
|
||||
if(
|
||||
transactions.value &&
|
||||
(transactionSubscription.record.account === account.value?.id || transactionSubscription.record.account === company.value?.id)
|
||||
) {
|
||||
switch (transactionSubscription.action) {
|
||||
case 'create':
|
||||
transactions.value.unshift(transactionSubscription.record);
|
||||
break;
|
||||
case 'update':
|
||||
transactions.value = transactions.value.map((transaction) => {
|
||||
if(transaction.id == transactionSubscription.record.id) {
|
||||
transaction = transactionSubscription.record;
|
||||
}
|
||||
return transaction;
|
||||
});
|
||||
break;
|
||||
case 'delete':
|
||||
transactions.value = transactions.value.filter((transaction) => transaction.id !== transactionSubscription.record.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
subscription.value = await BankService.subscribeToTransactionChanges((transactionSubscription) => {
|
||||
if(transactions.value && transactionSubscription.record.account === account.value?.id) {
|
||||
switch (transactionSubscription.action) {
|
||||
case 'create':
|
||||
transactions.value.unshift(transactionSubscription.record);
|
||||
break;
|
||||
case 'update':
|
||||
transactions.value = transactions.value.map((transaction) => {
|
||||
if(transaction.id == transactionSubscription.record.id) {
|
||||
transaction = transactionSubscription.record;
|
||||
}
|
||||
return transaction;
|
||||
});
|
||||
break;
|
||||
case 'delete':
|
||||
transactions.value = transactions.value.filter((transaction) => transaction.id !== transactionSubscription.record.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
subscription.value = await BankService.subscribeToTransactionChanges(handleRealtimeUpdates);
|
||||
companySubscription.value = await BankService.subscribeToCompanyTransactionChanges(handleRealtimeUpdates);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
subscription.value?.();
|
||||
companySubscription.value?.();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Collections, TransactionsResponse } from '../types/pocketbase.types';
|
|||
import { PocketbaseService } from './pocketbase.service';
|
||||
|
||||
const COLLECTION = PocketbaseService.getApi().collection(Collections.Transactions);
|
||||
const COMPANY_COLLECTION = PocketbaseService.getApi().collection(Collections.CompanyTransactions);
|
||||
|
||||
const DEPOSIT_LABEL = 'Bargeldeinzahlung';
|
||||
const SALARY_LABEL = 'Gehalt';
|
||||
|
@ -13,7 +14,14 @@ export class BankService {
|
|||
public static async getTransactions(accountId: string): Promise<TransactionsResponse[]> {
|
||||
return COLLECTION.getFullList({ filter: `account = "${accountId}"`, sort: '-created' });
|
||||
}
|
||||
public static async addTransaction(accountId: string, amount: number, type: TransactionType): Promise<TransactionsResponse> {
|
||||
public static async getCompanyTransactions(companyId: string): Promise<TransactionsResponse[]> {
|
||||
return COMPANY_COLLECTION.getFullList({ filter: `account = "${companyId}"`, sort: '-created' });
|
||||
}
|
||||
public static async addTransaction(accountId: string,
|
||||
amount: number,
|
||||
type: TransactionType,
|
||||
accountType: AccountType = AccountType.ACCOUNT,
|
||||
): Promise<TransactionsResponse> {
|
||||
let label = '';
|
||||
switch (type) {
|
||||
case TransactionType.DEPOSIT:
|
||||
|
@ -29,13 +37,24 @@ export class BankService {
|
|||
label = OPEN_ACCOUNT_LABEL;
|
||||
break;
|
||||
}
|
||||
return COLLECTION.create({ account: accountId, label, amount: amount * 100 });
|
||||
if(accountType === AccountType.COMPANY) {
|
||||
return COMPANY_COLLECTION.create({ account: accountId, label, amount: amount * 100 });
|
||||
} else {
|
||||
return COLLECTION.create({ account: accountId, label, amount: amount * 100 });
|
||||
}
|
||||
}
|
||||
public static async subscribeToTransactionChanges(callback: (data: RecordSubscription<TransactionsResponse>)=> void): Promise<UnsubscribeFunc> {
|
||||
return await COLLECTION.subscribe<TransactionsResponse>('*', (data) => {
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
public static async subscribeToCompanyTransactionChanges(
|
||||
callback: (data: RecordSubscription<TransactionsResponse>)=> void,
|
||||
): Promise<UnsubscribeFunc> {
|
||||
return await COMPANY_COLLECTION.subscribe<TransactionsResponse>('*', (data) => {
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export enum TransactionType {
|
||||
|
@ -43,4 +62,9 @@ export enum TransactionType {
|
|||
SALARY,
|
||||
WITHDRAW,
|
||||
OPEN_ACCOUNT,
|
||||
}
|
||||
|
||||
export enum AccountType {
|
||||
ACCOUNT,
|
||||
COMPANY,
|
||||
}
|
|
@ -40,4 +40,7 @@ export class CompanyService {
|
|||
}
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
public static async getCompanyByAccountNumber(accountNumber: string): Promise<CompaniesResponse> {
|
||||
return COLLECTION.getFirstListItem(`accountNumber="${accountNumber}"`);
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ export enum Collections {
|
|||
Accounts = "accounts",
|
||||
AccountsList = "accountsList",
|
||||
Companies = "companies",
|
||||
CompanyTransactions = "companyTransactions",
|
||||
Settings = "settings",
|
||||
Transactions = "transactions",
|
||||
}
|
||||
|
@ -54,9 +55,16 @@ export type AccountsListRecord<Tbalance = unknown, Tname = unknown> = {
|
|||
}
|
||||
|
||||
export type CompaniesRecord = {
|
||||
accountNumber?: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type CompanyTransactionsRecord = {
|
||||
account: RecordIdString
|
||||
label: string
|
||||
amount?: number
|
||||
}
|
||||
|
||||
export type SettingsRecord = {
|
||||
minWage: number
|
||||
incomeTax: number
|
||||
|
@ -74,6 +82,7 @@ export type TransactionsRecord = {
|
|||
export type AccountsResponse<Texpand = unknown> = Required<AccountsRecord> & BaseSystemFields<Texpand>
|
||||
export type AccountsListResponse<Tbalance = unknown, Tname = unknown, Texpand = unknown> = Required<AccountsListRecord<Tbalance, Tname>> & BaseSystemFields<Texpand>
|
||||
export type CompaniesResponse<Texpand = unknown> = Required<CompaniesRecord> & AuthSystemFields<Texpand>
|
||||
export type CompanyTransactionsResponse<Texpand = unknown> = Required<CompanyTransactionsRecord> & BaseSystemFields<Texpand>
|
||||
export type SettingsResponse<Texpand = unknown> = Required<SettingsRecord> & BaseSystemFields<Texpand>
|
||||
export type TransactionsResponse<Texpand = unknown> = Required<TransactionsRecord> & BaseSystemFields<Texpand>
|
||||
|
||||
|
@ -83,6 +92,7 @@ export type CollectionRecords = {
|
|||
accounts: AccountsRecord
|
||||
accountsList: AccountsListRecord
|
||||
companies: CompaniesRecord
|
||||
companyTransactions: CompanyTransactionsRecord
|
||||
settings: SettingsRecord
|
||||
transactions: TransactionsRecord
|
||||
}
|
||||
|
@ -91,6 +101,7 @@ export type CollectionResponses = {
|
|||
accounts: AccountsResponse
|
||||
accountsList: AccountsListResponse
|
||||
companies: CompaniesResponse
|
||||
companyTransactions: CompanyTransactionsResponse
|
||||
settings: SettingsResponse
|
||||
transactions: TransactionsResponse
|
||||
}
|
Loading…
Reference in a new issue