forked from Kispi/Core
feat(webapp): allow wage to be set freely
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
dc3c065d1a
commit
0b087a38bf
13 changed files with 304 additions and 257 deletions
|
@ -54,119 +54,6 @@
|
|||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "74ftooxenpeq14b",
|
||||
"name": "accounts",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"id": "as1gvc1r",
|
||||
"name": "accountNumber",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "vxeq20lk",
|
||||
"name": "firstName",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bsinpdk7",
|
||||
"name": "lastName",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "4lw6gmtc",
|
||||
"name": "grade",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "syfivtki",
|
||||
"name": "lastCheckIn",
|
||||
"type": "date",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "gwrzizpu",
|
||||
"name": "company",
|
||||
"type": "relation",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"options": {
|
||||
"collectionId": "s854d2w72fvyl54",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rtvjzpxw",
|
||||
"name": "shift",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ma5ckyk1",
|
||||
"name": "wageFactor",
|
||||
"type": "number",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": 1,
|
||||
"max": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE INDEX `idx_mPdvrAQ` ON `accounts` (`accountNumber`)"
|
||||
],
|
||||
"listRule": "",
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": "(@request.data.id = null && @request.data.accountNumber = null && @request.data.firstName = null && @request.data.lastName = null && @request.data.grade = null && @request.data.created = null && @request.data.updated = null && @request.data.company = null && @request.data.shift = null && @request.data.wageFactor = null) || (@request.data.id = null && @request.data.accountNumber = null && @request.data.firstName = null && @request.data.lastName = null && @request.data.created = null && @request.data.updated = null && @request.data.company = null && @request.auth.id = company.id)",
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "5msnfxat1sc2c1r",
|
||||
"name": "companyTransactions",
|
||||
|
@ -219,79 +106,6 @@
|
|||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "t4gewf713jqhz3i",
|
||||
"name": "settings",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"id": "z7pmr7wm",
|
||||
"name": "minWage",
|
||||
"type": "number",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": 0,
|
||||
"max": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "7wzv9qum",
|
||||
"name": "incomeTax",
|
||||
"type": "number",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": 0,
|
||||
"max": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3pgjcfai",
|
||||
"name": "incomeTaxRecipient",
|
||||
"type": "relation",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"collectionId": "s854d2w72fvyl54",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dawtvakx",
|
||||
"name": "maxWageFactor",
|
||||
"type": "number",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": 0,
|
||||
"max": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ftzmu1na",
|
||||
"name": "radioUrl",
|
||||
"type": "url",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"exceptDomains": [],
|
||||
"onlyDomains": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"viewRule": "",
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "s854d2w72fvyl54",
|
||||
"name": "companies",
|
||||
|
@ -431,5 +245,191 @@
|
|||
"options": {
|
||||
"query": "SELECT\n a.id AS id,\n a.accountNumber AS accountNumber,\n (a.firstName || ' ' || a.lastName) AS name,\n a.grade AS grade,\n a.lastCheckIn AS lastCheckIn,\n SUM(t.amount) AS balance\nFROM\n accounts AS a\nLEFT JOIN\n transactions AS t\nON\n a.id = t.account\nGROUP BY\n a.id\nORDER BY\n a.grade, a.lastName"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "t4gewf713jqhz3i",
|
||||
"name": "settings",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"id": "z7pmr7wm",
|
||||
"name": "minWage",
|
||||
"type": "number",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": 0,
|
||||
"max": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "7wzv9qum",
|
||||
"name": "incomeTax",
|
||||
"type": "number",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": 0,
|
||||
"max": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3pgjcfai",
|
||||
"name": "incomeTaxRecipient",
|
||||
"type": "relation",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"collectionId": "s854d2w72fvyl54",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dawtvakx",
|
||||
"name": "maxWage",
|
||||
"type": "number",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": 0,
|
||||
"max": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ftzmu1na",
|
||||
"name": "radioUrl",
|
||||
"type": "url",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"exceptDomains": [],
|
||||
"onlyDomains": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"viewRule": "",
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "74ftooxenpeq14b",
|
||||
"name": "accounts",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"id": "as1gvc1r",
|
||||
"name": "accountNumber",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "vxeq20lk",
|
||||
"name": "firstName",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bsinpdk7",
|
||||
"name": "lastName",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "4lw6gmtc",
|
||||
"name": "grade",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "syfivtki",
|
||||
"name": "lastCheckIn",
|
||||
"type": "date",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "gwrzizpu",
|
||||
"name": "company",
|
||||
"type": "relation",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"options": {
|
||||
"collectionId": "s854d2w72fvyl54",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rtvjzpxw",
|
||||
"name": "shift",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ggx1gyeq",
|
||||
"name": "wage",
|
||||
"type": "number",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE INDEX `idx_mPdvrAQ` ON `accounts` (`accountNumber`)"
|
||||
],
|
||||
"listRule": "",
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": "(@request.data.id = null && @request.data.accountNumber = null && @request.data.firstName = null && @request.data.lastName = null && @request.data.grade = null && @request.data.created = null && @request.data.updated = null && @request.data.company = null && @request.data.shift = null && @request.data.wage = null) || (@request.data.id = null && @request.data.accountNumber = null && @request.data.firstName = null && @request.data.lastName = null && @request.data.created = null && @request.data.updated = null && @request.data.company = null && @request.auth.id = company.id)",
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
}
|
||||
]
|
|
@ -2,7 +2,7 @@
|
|||
"name": "hgoe-sas",
|
||||
"private": true,
|
||||
"author": "Simon Giesel",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
|
|
|
@ -1,24 +1,61 @@
|
|||
<template>
|
||||
<div class="relative">
|
||||
<AtomInput
|
||||
ref="input"
|
||||
v-model="modelValue"
|
||||
<input
|
||||
:value="value"
|
||||
type="number"
|
||||
class="pr-[5.5rem] text-right"
|
||||
class="input w-full border-primary text-right"
|
||||
:class="{
|
||||
'pr-[5.5rem]': !perHour,
|
||||
'pr-[7rem]': perHour,
|
||||
}"
|
||||
step="1"
|
||||
min="1"
|
||||
:min="min"
|
||||
:max="max"
|
||||
@keyup="onKeyDown"
|
||||
/>
|
||||
<span class="pointer-events-none absolute top-2/4 -mt-3 ml-[-5.5rem] opacity-50">,00 Batzen</span>
|
||||
<span
|
||||
class="pointer-events-none absolute top-2/4 text-[1rem] opacity-50"
|
||||
:class="{
|
||||
'-mt-3': !suffixMargin,
|
||||
'mt-[-0.625rem]': suffixMargin,
|
||||
'ml-[-5.5rem]': !perHour,
|
||||
'ml-[-7rem]': perHour,
|
||||
}"
|
||||
>,00 Batzen{{ perHour ? ' / h' : '' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import AtomInput from './AtomInput.vue';
|
||||
const props = withDefaults(defineProps<{
|
||||
suffixMargin?: boolean,
|
||||
perHour?: boolean,
|
||||
min?: number,
|
||||
max?: number,
|
||||
value?: number,
|
||||
}>(), {
|
||||
suffixMargin: false,
|
||||
perHour: false,
|
||||
min: 1,
|
||||
max: 1_000_000,
|
||||
value: undefined,
|
||||
});
|
||||
|
||||
/* global defineModel */
|
||||
const modelValue = defineModel();
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
const value = parseInt((event.target as HTMLInputElement).value);
|
||||
if(value < props.min) {
|
||||
emit('change', props.min);
|
||||
(event.target as HTMLInputElement).value = props.min.toString();
|
||||
} else if(value > props.max) {
|
||||
emit('change', props.max);
|
||||
(event.target as HTMLInputElement).value = props.max.toString();
|
||||
} else {
|
||||
emit('change', value);
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [value: number],
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
|
@ -29,12 +29,4 @@ const modelValue = defineModel();
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
</style>
|
|
@ -40,14 +40,21 @@
|
|||
<label
|
||||
tabindex="0"
|
||||
class="btn bg-base-300 normal-case"
|
||||
>{{ entry[header.key] }} <ChevronDownIcon class="h-4 w-4" /></label>
|
||||
>{{ entry[header.key] }}
|
||||
<ChevronDownIcon class="h-4 w-4" />
|
||||
</label>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content menu rounded-box z-[1] w-52 bg-base-100 p-2 shadow"
|
||||
>
|
||||
<li @click="$emit('selectShift', {id: entry.id, shift: Shifts.NONE}); removeFocus();"><a>{{ Shifts.NONE }}</a></li>
|
||||
<li><a @click="$emit('selectShift', {id: entry.id, shift: Shifts.EARLY}); removeFocus();">{{ Shifts.EARLY }}</a></li>
|
||||
<li><a @click="$emit('selectShift', {id: entry.id, shift: Shifts.LATE}); removeFocus();">{{ Shifts.LATE }}</a></li>
|
||||
<li @click="$emit('selectShift', { id: entry.id, shift: Shifts.NONE }); removeFocus();"><a>{{
|
||||
Shifts.NONE
|
||||
}}</a></li>
|
||||
<li><a @click="$emit('selectShift', { id: entry.id, shift: Shifts.EARLY }); removeFocus();">{{
|
||||
Shifts.EARLY }}</a></li>
|
||||
<li><a @click="$emit('selectShift', { id: entry.id, shift: Shifts.LATE }); removeFocus();">{{
|
||||
Shifts.LATE
|
||||
}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</span>
|
||||
|
@ -79,21 +86,15 @@
|
|||
v-if="header.type === TableHeaderType.WAGE"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<div class="dropdown-bottom dropdown">
|
||||
<label
|
||||
tabindex="0"
|
||||
class="btn bg-base-300 normal-case"
|
||||
>{{ CurrencyService.toString(entry[header.key] * settings.minWage) }} / Tag <ChevronDownIcon class="h-4 w-4" /></label>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content menu rounded-box z-[1] w-52 bg-base-100 p-2 shadow"
|
||||
>
|
||||
<li
|
||||
v-for="i of settings.maxWageFactor"
|
||||
@click="$emit('selectWage', {id: entry.id, wageFactor: i}); removeFocus();"
|
||||
><a>{{ CurrencyService.toString(settings.minWage * i ) }} / Tag</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<AtomCurrencyInput
|
||||
:value="entry[header.key] / 100"
|
||||
suffix-margin
|
||||
per-hour
|
||||
class="min-w-[11rem]"
|
||||
:min="settings.minWage / 100"
|
||||
:max="settings.maxWage / 100"
|
||||
@change="$emit('selectWage', { id: entry.id, wage: $event })"
|
||||
/>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -111,6 +112,7 @@ import { CurrencyService } from '../../services/currency.service';
|
|||
import { SettingsService } from '../../services/settings.service';
|
||||
import { Shifts } from '../../enums/shift.enum';
|
||||
import { ChevronDownIcon, CreditCardIcon, CurrencyDollarIcon } from '@heroicons/vue/24/outline';
|
||||
import AtomCurrencyInput from '../atoms/AtomCurrencyInput.vue';
|
||||
|
||||
const settings = ref<SettingsResponse>();
|
||||
|
||||
|
@ -139,7 +141,7 @@ onMounted(async () => {
|
|||
defineEmits<{
|
||||
click: [id: string],
|
||||
selectShift: [{ id: string, shift: Shifts }],
|
||||
selectWage: [{id: string, wageFactor: number}],
|
||||
selectWage: [{ id: string, wage: number }],
|
||||
changeAccountNumber: [id: string],
|
||||
}>();
|
||||
|
||||
|
@ -161,6 +163,4 @@ export enum TableHeaderType {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
|
@ -127,7 +127,7 @@
|
|||
:table-headers="accountTableHeaders"
|
||||
:data="accounts"
|
||||
@select-shift="selectShift"
|
||||
@select-wage="selectWage"
|
||||
@select-wage="updateWage"
|
||||
/>
|
||||
</div>
|
||||
<MoleculeVerifyModal
|
||||
|
@ -224,7 +224,7 @@ const accountTableHeaders = [
|
|||
},
|
||||
{
|
||||
title: 'Lohn',
|
||||
key: 'wageFactor',
|
||||
key: 'wage',
|
||||
type: TableHeaderType.WAGE,
|
||||
},
|
||||
];
|
||||
|
@ -281,7 +281,7 @@ function getBalance(): number {
|
|||
|
||||
function getTotalCostsByShifts(shift: Shifts): number {
|
||||
return accounts.value?.filter(
|
||||
(account) => account.shift === shift).reduce((acc, account) => acc + account.wageFactor * (settings.value?.minWage ?? 0), 0,
|
||||
(account) => account.shift === shift).reduce((acc, account) => acc + account.wage, 0,
|
||||
) ?? 0;
|
||||
}
|
||||
|
||||
|
@ -308,8 +308,14 @@ async function selectShift(shiftChange: {id: string, shift: Shifts}): Promise<vo
|
|||
await AccountService.updateShift(shiftChange.id, shiftChange.shift);
|
||||
}
|
||||
|
||||
async function selectWage(wageChange: {id: string, wageFactor: number}): Promise<void> {
|
||||
await AccountService.updateWage(wageChange.id, wageChange.wageFactor);
|
||||
async function updateWage(wageChange: {id: string, wage: number}): Promise<void> {
|
||||
if(wageChange.wage * 100 < (settings.value?.minWage ?? 0)) {
|
||||
return;
|
||||
}
|
||||
if(wageChange.wage * 100 > (settings.value?.maxWage ?? 0)) {
|
||||
return;
|
||||
}
|
||||
await AccountService.updateWage(wageChange.id, wageChange.wage);
|
||||
}
|
||||
|
||||
function logout() {
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
</template>
|
||||
<div class="mt-4 flex h-full flex-col gap-4">
|
||||
<div class="flex place-items-center justify-between">
|
||||
<span>Mindestlohn pro Tag</span>
|
||||
<span>Mindestlohn</span>
|
||||
<AtomCurrencyInput
|
||||
v-model="minWage"
|
||||
:value="minWage"
|
||||
class="w-2/4"
|
||||
per-hour
|
||||
@change="minWage = $event"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex place-items-center justify-between">
|
||||
|
@ -21,10 +23,13 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="flex place-items-center justify-between">
|
||||
<span>Höchstlohn-Faktor</span>
|
||||
<AtomFactorInput
|
||||
v-model="maxWageFactor"
|
||||
<span>Höchstlohn</span>
|
||||
<AtomCurrencyInput
|
||||
:value="maxWage"
|
||||
:min="minWage"
|
||||
per-hour
|
||||
class="w-2/4"
|
||||
@change="maxWage = $event"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex place-items-center justify-between">
|
||||
|
@ -245,7 +250,6 @@ import AtomCard from '../atoms/AtomCard.vue';
|
|||
import AtomInput from '../atoms/AtomInput.vue';
|
||||
import AtomCurrencyInput from '../atoms/AtomCurrencyInput.vue';
|
||||
import AtomPercentInput from '../atoms/AtomPercentInput.vue';
|
||||
import AtomFactorInput from '../atoms/AtomFactorInput.vue';
|
||||
import OrganismAuthWrapper from '../organisms/OrganismAuthWrapper.vue';
|
||||
import MoleculeImportDataModal from '../molecules/MoleculeImportDataModal.vue';
|
||||
import MoleculeErrorModal from '../molecules/MoleculeErrorModal.vue';
|
||||
|
@ -260,7 +264,7 @@ const verifySeedCapitalModal = ref<InstanceType<typeof MoleculeVerifyModal>>();
|
|||
const errorModal = ref<InstanceType<typeof MoleculeErrorModal>>();
|
||||
const minWage = ref<number>();
|
||||
const incomeTax = ref<number>();
|
||||
const maxWageFactor = ref<number>();
|
||||
const maxWage = ref<number>();
|
||||
const incomeTaxRecipient = ref<string>();
|
||||
const radioUrl = ref<string>();
|
||||
const settings = ref<SettingsResponse>();
|
||||
|
@ -293,7 +297,6 @@ async function importAccounts(file: File): Promise<void> {
|
|||
grade: grade.trim(),
|
||||
company: company?.id,
|
||||
shift: Shifts.NONE,
|
||||
wageFactor: 1,
|
||||
}));
|
||||
}
|
||||
if(errors.length) {
|
||||
|
@ -382,18 +385,18 @@ onMounted(async () => {
|
|||
settings.value = await SettingsService.getSettings();
|
||||
minWage.value = settings.value.minWage / 100;
|
||||
incomeTax.value = settings.value.incomeTax;
|
||||
maxWageFactor.value = settings.value.maxWageFactor;
|
||||
maxWage.value = settings.value.maxWage / 100;
|
||||
incomeTaxRecipient.value = settings.value.incomeTaxRecipient;
|
||||
radioUrl.value = settings.value.radioUrl;
|
||||
serverTime.value = await DateService.getServerTime();
|
||||
});
|
||||
|
||||
async function saveSettings() {
|
||||
if(minWage.value && incomeTax.value && maxWageFactor.value && incomeTaxRecipient.value && radioUrl.value) {
|
||||
if(minWage.value && incomeTax.value && maxWage.value && incomeTaxRecipient.value && radioUrl.value) {
|
||||
settings.value = await SettingsService.setSettings({
|
||||
minWage: minWage.value,
|
||||
incomeTax: incomeTax.value,
|
||||
maxWageFactor: maxWageFactor.value,
|
||||
maxWage: maxWage.value,
|
||||
incomeTaxRecipient: incomeTaxRecipient.value,
|
||||
radioUrl: radioUrl.value,
|
||||
});
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
|
@ -30,8 +30,8 @@ export class AccountService {
|
|||
public static async updateShift(accountId: string, shift: string): Promise<AccountsResponse> {
|
||||
return COLLECTION.update(accountId, { shift });
|
||||
}
|
||||
public static async updateWage(accountId: string, wageFactor: number): Promise<AccountsResponse> {
|
||||
return COLLECTION.update(accountId, { wageFactor });
|
||||
public static async updateWage(accountId: string, wage: number): Promise<AccountsResponse> {
|
||||
return COLLECTION.update(accountId, { wage: wage * 100 });
|
||||
}
|
||||
public static async getMissingStudents(): Promise<AccountsResponse[]> {
|
||||
return ((await COLLECTION.getFullList<AccountsResponse>()).filter(
|
||||
|
|
|
@ -63,7 +63,7 @@ export class CompanyService {
|
|||
const settings = await SettingsService.getSettings();
|
||||
const balance = (await BankService.getCompanyTransactions(company.id)).reduce((acc, transaction) => acc + transaction.amount, 0);
|
||||
const accounts = (await AccountService.getAccountsByCompanyId(company.id)).filter(account => account.shift == shift);
|
||||
const totalWages = accounts.reduce((acc, account) => acc + account.wageFactor * (settings.minWage), 0);
|
||||
const totalWages = accounts.reduce((acc, account) => acc + account.wage, 0);
|
||||
let taxes = 0;
|
||||
if(balance >= totalWages) {
|
||||
await BankService.addTransaction(company.id, -totalWages / 100, `Lohn für ${shift}`, AccountType.COMPANY);
|
||||
|
@ -71,10 +71,10 @@ export class CompanyService {
|
|||
accounts.forEach(async account => {
|
||||
promises.push(BankService.addTransaction(
|
||||
account.id,
|
||||
(((account.wageFactor * settings.minWage) * (1 - (settings.incomeTax / 100)))) / 100,
|
||||
((account.wage * (1 - (settings.incomeTax / 100)))) / 100,
|
||||
`Lohn für ${shift} bei ${company.name}`,
|
||||
));
|
||||
taxes += (account.wageFactor * settings.minWage) * (settings.incomeTax / 100);
|
||||
taxes += account.wage * (settings.incomeTax / 100);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
await BankService.addTransaction(settings.incomeTaxRecipient,
|
||||
|
|
|
@ -29,7 +29,6 @@ export class DateService {
|
|||
}
|
||||
public static isToday(date: Date): boolean {
|
||||
this.getServerTime();
|
||||
console.log('isToday', date, serverTime);
|
||||
if(!serverTime) { return false; }
|
||||
return date.getDate() === serverTime.getDate() &&
|
||||
date.getMonth() === serverTime.getMonth() &&
|
||||
|
|
|
@ -9,6 +9,7 @@ export class SettingsService {
|
|||
}
|
||||
public static async setSettings(settings: SettingsRecord): Promise<SettingsResponse> {
|
||||
settings.minWage = settings.minWage * 100;
|
||||
settings.maxWage = settings.maxWage * 100;
|
||||
// TODO: Enforce maxWageFactor to all account records
|
||||
return COLLECTION.create(settings);
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ export type AccountsRecord = {
|
|||
lastCheckIn?: IsoDateString
|
||||
company?: RecordIdString
|
||||
shift: string
|
||||
wageFactor: number
|
||||
wage?: number
|
||||
}
|
||||
|
||||
export type AccountsListRecord<Tbalance = unknown, Tname = unknown> = {
|
||||
|
@ -71,7 +71,7 @@ export type SettingsRecord = {
|
|||
minWage: number
|
||||
incomeTax: number
|
||||
incomeTaxRecipient: RecordIdString
|
||||
maxWageFactor: number
|
||||
maxWage: number
|
||||
radioUrl: string
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue