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,
|
"deleteRule": null,
|
||||||
"options": {}
|
"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",
|
"id": "5msnfxat1sc2c1r",
|
||||||
"name": "companyTransactions",
|
"name": "companyTransactions",
|
||||||
|
@ -219,79 +106,6 @@
|
||||||
"deleteRule": null,
|
"deleteRule": null,
|
||||||
"options": {}
|
"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",
|
"id": "s854d2w72fvyl54",
|
||||||
"name": "companies",
|
"name": "companies",
|
||||||
|
@ -431,5 +245,191 @@
|
||||||
"options": {
|
"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"
|
"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",
|
"name": "hgoe-sas",
|
||||||
"private": true,
|
"private": true,
|
||||||
"author": "Simon Giesel",
|
"author": "Simon Giesel",
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
|
|
|
@ -1,24 +1,61 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<AtomInput
|
<input
|
||||||
ref="input"
|
:value="value"
|
||||||
v-model="modelValue"
|
|
||||||
type="number"
|
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"
|
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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<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 */
|
function onKeyDown(event: KeyboardEvent) {
|
||||||
const modelValue = defineModel();
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped></style>
|
||||||
|
|
||||||
</style>
|
|
|
@ -29,12 +29,4 @@ const modelValue = defineModel();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<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>
|
</style>
|
|
@ -30,7 +30,7 @@
|
||||||
{{ DateService.toShortString(entry[header.key]) }}
|
{{ DateService.toShortString(entry[header.key]) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="header.type === TableHeaderType.DATETIME">
|
<span v-else-if="header.type === TableHeaderType.DATETIME">
|
||||||
{{ entry[header.key] ? DateService.toString(parseISO(entry[header.key])): 'N/A' }}
|
{{ entry[header.key] ? DateService.toString(parseISO(entry[header.key])) : 'N/A' }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="header.type === TableHeaderType.CURRENCY">
|
<span v-else-if="header.type === TableHeaderType.CURRENCY">
|
||||||
{{ CurrencyService.toString(entry[header.key]) }}
|
{{ CurrencyService.toString(entry[header.key]) }}
|
||||||
|
@ -40,14 +40,21 @@
|
||||||
<label
|
<label
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="btn bg-base-300 normal-case"
|
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
|
<ul
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="dropdown-content menu rounded-box z-[1] w-52 bg-base-100 p-2 shadow"
|
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 @click="$emit('selectShift', { id: entry.id, shift: Shifts.NONE }); removeFocus();"><a>{{
|
||||||
<li><a @click="$emit('selectShift', {id: entry.id, shift: Shifts.EARLY}); removeFocus();">{{ Shifts.EARLY }}</a></li>
|
Shifts.NONE
|
||||||
<li><a @click="$emit('selectShift', {id: entry.id, shift: Shifts.LATE}); removeFocus();">{{ Shifts.LATE }}</a></li>
|
}}</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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
@ -79,21 +86,15 @@
|
||||||
v-if="header.type === TableHeaderType.WAGE"
|
v-if="header.type === TableHeaderType.WAGE"
|
||||||
class="flex items-center gap-2"
|
class="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<div class="dropdown-bottom dropdown">
|
<AtomCurrencyInput
|
||||||
<label
|
:value="entry[header.key] / 100"
|
||||||
tabindex="0"
|
suffix-margin
|
||||||
class="btn bg-base-300 normal-case"
|
per-hour
|
||||||
>{{ CurrencyService.toString(entry[header.key] * settings.minWage) }} / Tag <ChevronDownIcon class="h-4 w-4" /></label>
|
class="min-w-[11rem]"
|
||||||
<ul
|
:min="settings.minWage / 100"
|
||||||
tabindex="0"
|
:max="settings.maxWage / 100"
|
||||||
class="dropdown-content menu rounded-box z-[1] w-52 bg-base-100 p-2 shadow"
|
@change="$emit('selectWage', { id: entry.id, wage: $event })"
|
||||||
>
|
/>
|
||||||
<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>
|
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -111,6 +112,7 @@ import { CurrencyService } from '../../services/currency.service';
|
||||||
import { SettingsService } from '../../services/settings.service';
|
import { SettingsService } from '../../services/settings.service';
|
||||||
import { Shifts } from '../../enums/shift.enum';
|
import { Shifts } from '../../enums/shift.enum';
|
||||||
import { ChevronDownIcon, CreditCardIcon, CurrencyDollarIcon } from '@heroicons/vue/24/outline';
|
import { ChevronDownIcon, CreditCardIcon, CurrencyDollarIcon } from '@heroicons/vue/24/outline';
|
||||||
|
import AtomCurrencyInput from '../atoms/AtomCurrencyInput.vue';
|
||||||
|
|
||||||
const settings = ref<SettingsResponse>();
|
const settings = ref<SettingsResponse>();
|
||||||
|
|
||||||
|
@ -138,8 +140,8 @@ onMounted(async () => {
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
click: [id: string],
|
click: [id: string],
|
||||||
selectShift: [{id: string, shift: Shifts}],
|
selectShift: [{ id: string, shift: Shifts }],
|
||||||
selectWage: [{id: string, wageFactor: number}],
|
selectWage: [{ id: string, wage: number }],
|
||||||
changeAccountNumber: [id: string],
|
changeAccountNumber: [id: string],
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -161,6 +163,4 @@ export enum TableHeaderType {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped></style>
|
||||||
|
|
||||||
</style>
|
|
|
@ -127,7 +127,7 @@
|
||||||
:table-headers="accountTableHeaders"
|
:table-headers="accountTableHeaders"
|
||||||
:data="accounts"
|
:data="accounts"
|
||||||
@select-shift="selectShift"
|
@select-shift="selectShift"
|
||||||
@select-wage="selectWage"
|
@select-wage="updateWage"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<MoleculeVerifyModal
|
<MoleculeVerifyModal
|
||||||
|
@ -224,7 +224,7 @@ const accountTableHeaders = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Lohn',
|
title: 'Lohn',
|
||||||
key: 'wageFactor',
|
key: 'wage',
|
||||||
type: TableHeaderType.WAGE,
|
type: TableHeaderType.WAGE,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -281,7 +281,7 @@ function getBalance(): number {
|
||||||
|
|
||||||
function getTotalCostsByShifts(shift: Shifts): number {
|
function getTotalCostsByShifts(shift: Shifts): number {
|
||||||
return accounts.value?.filter(
|
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;
|
) ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,8 +308,14 @@ async function selectShift(shiftChange: {id: string, shift: Shifts}): Promise<vo
|
||||||
await AccountService.updateShift(shiftChange.id, shiftChange.shift);
|
await AccountService.updateShift(shiftChange.id, shiftChange.shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function selectWage(wageChange: {id: string, wageFactor: number}): Promise<void> {
|
async function updateWage(wageChange: {id: string, wage: number}): Promise<void> {
|
||||||
await AccountService.updateWage(wageChange.id, wageChange.wageFactor);
|
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() {
|
function logout() {
|
||||||
|
|
|
@ -7,10 +7,12 @@
|
||||||
</template>
|
</template>
|
||||||
<div class="mt-4 flex h-full flex-col gap-4">
|
<div class="mt-4 flex h-full flex-col gap-4">
|
||||||
<div class="flex place-items-center justify-between">
|
<div class="flex place-items-center justify-between">
|
||||||
<span>Mindestlohn pro Tag</span>
|
<span>Mindestlohn</span>
|
||||||
<AtomCurrencyInput
|
<AtomCurrencyInput
|
||||||
v-model="minWage"
|
:value="minWage"
|
||||||
class="w-2/4"
|
class="w-2/4"
|
||||||
|
per-hour
|
||||||
|
@change="minWage = $event"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex place-items-center justify-between">
|
<div class="flex place-items-center justify-between">
|
||||||
|
@ -21,10 +23,13 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex place-items-center justify-between">
|
<div class="flex place-items-center justify-between">
|
||||||
<span>Höchstlohn-Faktor</span>
|
<span>Höchstlohn</span>
|
||||||
<AtomFactorInput
|
<AtomCurrencyInput
|
||||||
v-model="maxWageFactor"
|
:value="maxWage"
|
||||||
|
:min="minWage"
|
||||||
|
per-hour
|
||||||
class="w-2/4"
|
class="w-2/4"
|
||||||
|
@change="maxWage = $event"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex place-items-center justify-between">
|
<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 AtomInput from '../atoms/AtomInput.vue';
|
||||||
import AtomCurrencyInput from '../atoms/AtomCurrencyInput.vue';
|
import AtomCurrencyInput from '../atoms/AtomCurrencyInput.vue';
|
||||||
import AtomPercentInput from '../atoms/AtomPercentInput.vue';
|
import AtomPercentInput from '../atoms/AtomPercentInput.vue';
|
||||||
import AtomFactorInput from '../atoms/AtomFactorInput.vue';
|
|
||||||
import OrganismAuthWrapper from '../organisms/OrganismAuthWrapper.vue';
|
import OrganismAuthWrapper from '../organisms/OrganismAuthWrapper.vue';
|
||||||
import MoleculeImportDataModal from '../molecules/MoleculeImportDataModal.vue';
|
import MoleculeImportDataModal from '../molecules/MoleculeImportDataModal.vue';
|
||||||
import MoleculeErrorModal from '../molecules/MoleculeErrorModal.vue';
|
import MoleculeErrorModal from '../molecules/MoleculeErrorModal.vue';
|
||||||
|
@ -260,7 +264,7 @@ const verifySeedCapitalModal = ref<InstanceType<typeof MoleculeVerifyModal>>();
|
||||||
const errorModal = ref<InstanceType<typeof MoleculeErrorModal>>();
|
const errorModal = ref<InstanceType<typeof MoleculeErrorModal>>();
|
||||||
const minWage = ref<number>();
|
const minWage = ref<number>();
|
||||||
const incomeTax = ref<number>();
|
const incomeTax = ref<number>();
|
||||||
const maxWageFactor = ref<number>();
|
const maxWage = ref<number>();
|
||||||
const incomeTaxRecipient = ref<string>();
|
const incomeTaxRecipient = ref<string>();
|
||||||
const radioUrl = ref<string>();
|
const radioUrl = ref<string>();
|
||||||
const settings = ref<SettingsResponse>();
|
const settings = ref<SettingsResponse>();
|
||||||
|
@ -293,7 +297,6 @@ async function importAccounts(file: File): Promise<void> {
|
||||||
grade: grade.trim(),
|
grade: grade.trim(),
|
||||||
company: company?.id,
|
company: company?.id,
|
||||||
shift: Shifts.NONE,
|
shift: Shifts.NONE,
|
||||||
wageFactor: 1,
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
if(errors.length) {
|
if(errors.length) {
|
||||||
|
@ -382,18 +385,18 @@ onMounted(async () => {
|
||||||
settings.value = await SettingsService.getSettings();
|
settings.value = await SettingsService.getSettings();
|
||||||
minWage.value = settings.value.minWage / 100;
|
minWage.value = settings.value.minWage / 100;
|
||||||
incomeTax.value = settings.value.incomeTax;
|
incomeTax.value = settings.value.incomeTax;
|
||||||
maxWageFactor.value = settings.value.maxWageFactor;
|
maxWage.value = settings.value.maxWage / 100;
|
||||||
incomeTaxRecipient.value = settings.value.incomeTaxRecipient;
|
incomeTaxRecipient.value = settings.value.incomeTaxRecipient;
|
||||||
radioUrl.value = settings.value.radioUrl;
|
radioUrl.value = settings.value.radioUrl;
|
||||||
serverTime.value = await DateService.getServerTime();
|
serverTime.value = await DateService.getServerTime();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function saveSettings() {
|
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({
|
settings.value = await SettingsService.setSettings({
|
||||||
minWage: minWage.value,
|
minWage: minWage.value,
|
||||||
incomeTax: incomeTax.value,
|
incomeTax: incomeTax.value,
|
||||||
maxWageFactor: maxWageFactor.value,
|
maxWage: maxWage.value,
|
||||||
incomeTaxRecipient: incomeTaxRecipient.value,
|
incomeTaxRecipient: incomeTaxRecipient.value,
|
||||||
radioUrl: radioUrl.value,
|
radioUrl: radioUrl.value,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@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> {
|
public static async updateShift(accountId: string, shift: string): Promise<AccountsResponse> {
|
||||||
return COLLECTION.update(accountId, { shift });
|
return COLLECTION.update(accountId, { shift });
|
||||||
}
|
}
|
||||||
public static async updateWage(accountId: string, wageFactor: number): Promise<AccountsResponse> {
|
public static async updateWage(accountId: string, wage: number): Promise<AccountsResponse> {
|
||||||
return COLLECTION.update(accountId, { wageFactor });
|
return COLLECTION.update(accountId, { wage: wage * 100 });
|
||||||
}
|
}
|
||||||
public static async getMissingStudents(): Promise<AccountsResponse[]> {
|
public static async getMissingStudents(): Promise<AccountsResponse[]> {
|
||||||
return ((await COLLECTION.getFullList<AccountsResponse>()).filter(
|
return ((await COLLECTION.getFullList<AccountsResponse>()).filter(
|
||||||
|
|
|
@ -63,7 +63,7 @@ export class CompanyService {
|
||||||
const settings = await SettingsService.getSettings();
|
const settings = await SettingsService.getSettings();
|
||||||
const balance = (await BankService.getCompanyTransactions(company.id)).reduce((acc, transaction) => acc + transaction.amount, 0);
|
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 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;
|
let taxes = 0;
|
||||||
if(balance >= totalWages) {
|
if(balance >= totalWages) {
|
||||||
await BankService.addTransaction(company.id, -totalWages / 100, `Lohn für ${shift}`, AccountType.COMPANY);
|
await BankService.addTransaction(company.id, -totalWages / 100, `Lohn für ${shift}`, AccountType.COMPANY);
|
||||||
|
@ -71,10 +71,10 @@ export class CompanyService {
|
||||||
accounts.forEach(async account => {
|
accounts.forEach(async account => {
|
||||||
promises.push(BankService.addTransaction(
|
promises.push(BankService.addTransaction(
|
||||||
account.id,
|
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}`,
|
`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 Promise.all(promises);
|
||||||
await BankService.addTransaction(settings.incomeTaxRecipient,
|
await BankService.addTransaction(settings.incomeTaxRecipient,
|
||||||
|
|
|
@ -29,7 +29,6 @@ export class DateService {
|
||||||
}
|
}
|
||||||
public static isToday(date: Date): boolean {
|
public static isToday(date: Date): boolean {
|
||||||
this.getServerTime();
|
this.getServerTime();
|
||||||
console.log('isToday', date, serverTime);
|
|
||||||
if(!serverTime) { return false; }
|
if(!serverTime) { return false; }
|
||||||
return date.getDate() === serverTime.getDate() &&
|
return date.getDate() === serverTime.getDate() &&
|
||||||
date.getMonth() === serverTime.getMonth() &&
|
date.getMonth() === serverTime.getMonth() &&
|
||||||
|
|
|
@ -9,6 +9,7 @@ export class SettingsService {
|
||||||
}
|
}
|
||||||
public static async setSettings(settings: SettingsRecord): Promise<SettingsResponse> {
|
public static async setSettings(settings: SettingsRecord): Promise<SettingsResponse> {
|
||||||
settings.minWage = settings.minWage * 100;
|
settings.minWage = settings.minWage * 100;
|
||||||
|
settings.maxWage = settings.maxWage * 100;
|
||||||
// TODO: Enforce maxWageFactor to all account records
|
// TODO: Enforce maxWageFactor to all account records
|
||||||
return COLLECTION.create(settings);
|
return COLLECTION.create(settings);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ export type AccountsRecord = {
|
||||||
lastCheckIn?: IsoDateString
|
lastCheckIn?: IsoDateString
|
||||||
company?: RecordIdString
|
company?: RecordIdString
|
||||||
shift: string
|
shift: string
|
||||||
wageFactor: number
|
wage?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AccountsListRecord<Tbalance = unknown, Tname = unknown> = {
|
export type AccountsListRecord<Tbalance = unknown, Tname = unknown> = {
|
||||||
|
@ -71,7 +71,7 @@ export type SettingsRecord = {
|
||||||
minWage: number
|
minWage: number
|
||||||
incomeTax: number
|
incomeTax: number
|
||||||
incomeTaxRecipient: RecordIdString
|
incomeTaxRecipient: RecordIdString
|
||||||
maxWageFactor: number
|
maxWage: number
|
||||||
radioUrl: string
|
radioUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue