1
0
Fork 0
forked from Kispi/Core

feat(webapp): allow wage to be set freely
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Simon Giesel 2023-07-17 19:40:07 +02:00
parent dc3c065d1a
commit 0b087a38bf
13 changed files with 304 additions and 257 deletions

View file

@ -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": {}
} }
] ]

View file

@ -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",

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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() {

View file

@ -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,
}); });

View file

@ -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;
}

View file

@ -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(

View file

@ -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,

View file

@ -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() &&

View file

@ -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);
} }

View file

@ -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
} }