From 0b087a38bfd10ab084c8c1614aca9a4d8decc8c7 Mon Sep 17 00:00:00 2001 From: Simon Giesel Date: Mon, 17 Jul 2023 19:40:07 +0200 Subject: [PATCH] feat(webapp): allow wage to be set freely --- server/pb_schema.json | 372 +++++++++--------- webapp/package.json | 2 +- .../components/atoms/AtomCurrencyInput.vue | 61 ++- webapp/src/components/atoms/AtomInput.vue | 8 - .../molecules/MoleculeDataTable.vue | 50 +-- webapp/src/components/views/ViewEmployer.vue | 16 +- .../src/components/views/ViewStatePortal.vue | 25 +- webapp/src/index.css | 11 +- webapp/src/services/account.service.ts | 4 +- webapp/src/services/company.service.ts | 6 +- webapp/src/services/date.service.ts | 1 - webapp/src/services/settings.service.ts | 1 + webapp/src/types/pocketbase.types.ts | 4 +- 13 files changed, 304 insertions(+), 257 deletions(-) diff --git a/server/pb_schema.json b/server/pb_schema.json index 9e46f46..b76175b 100644 --- a/server/pb_schema.json +++ b/server/pb_schema.json @@ -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": {} } ] \ No newline at end of file diff --git a/webapp/package.json b/webapp/package.json index 0717015..08f4c93 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -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", diff --git a/webapp/src/components/atoms/AtomCurrencyInput.vue b/webapp/src/components/atoms/AtomCurrencyInput.vue index ef6f57d..b5ab8df 100644 --- a/webapp/src/components/atoms/AtomCurrencyInput.vue +++ b/webapp/src/components/atoms/AtomCurrencyInput.vue @@ -1,24 +1,61 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/webapp/src/components/atoms/AtomInput.vue b/webapp/src/components/atoms/AtomInput.vue index de0b397..e7d050a 100644 --- a/webapp/src/components/atoms/AtomInput.vue +++ b/webapp/src/components/atoms/AtomInput.vue @@ -29,12 +29,4 @@ const modelValue = defineModel(); \ No newline at end of file diff --git a/webapp/src/components/molecules/MoleculeDataTable.vue b/webapp/src/components/molecules/MoleculeDataTable.vue index fd1b446..d3e67ff 100644 --- a/webapp/src/components/molecules/MoleculeDataTable.vue +++ b/webapp/src/components/molecules/MoleculeDataTable.vue @@ -30,7 +30,7 @@ {{ DateService.toShortString(entry[header.key]) }} - {{ entry[header.key] ? DateService.toString(parseISO(entry[header.key])): 'N/A' }} + {{ entry[header.key] ? DateService.toString(parseISO(entry[header.key])) : 'N/A' }} {{ CurrencyService.toString(entry[header.key]) }} @@ -40,14 +40,21 @@ + >{{ entry[header.key] }} + + @@ -79,21 +86,15 @@ v-if="header.type === TableHeaderType.WAGE" class="flex items-center gap-2" > - + @@ -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(); @@ -138,8 +140,8 @@ onMounted(async () => { defineEmits<{ click: [id: string], - selectShift: [{id: string, shift: Shifts}], - selectWage: [{id: string, wageFactor: number}], + selectShift: [{ id: string, shift: Shifts }], + selectWage: [{ id: string, wage: number }], changeAccountNumber: [id: string], }>(); @@ -161,6 +163,4 @@ export enum TableHeaderType { } - \ No newline at end of file + \ No newline at end of file diff --git a/webapp/src/components/views/ViewEmployer.vue b/webapp/src/components/views/ViewEmployer.vue index b1c21a6..f645693 100644 --- a/webapp/src/components/views/ViewEmployer.vue +++ b/webapp/src/components/views/ViewEmployer.vue @@ -127,7 +127,7 @@ :table-headers="accountTableHeaders" :data="accounts" @select-shift="selectShift" - @select-wage="selectWage" + @select-wage="updateWage" /> 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 { - await AccountService.updateWage(wageChange.id, wageChange.wageFactor); +async function updateWage(wageChange: {id: string, wage: number}): Promise { + 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() { diff --git a/webapp/src/components/views/ViewStatePortal.vue b/webapp/src/components/views/ViewStatePortal.vue index 05a77eb..a5cf29f 100644 --- a/webapp/src/components/views/ViewStatePortal.vue +++ b/webapp/src/components/views/ViewStatePortal.vue @@ -7,10 +7,12 @@
- Mindestlohn pro Tag + Mindestlohn
@@ -21,10 +23,13 @@ />
- Höchstlohn-Faktor - Höchstlohn +
@@ -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>(); const errorModal = ref>(); const minWage = ref(); const incomeTax = ref(); -const maxWageFactor = ref(); +const maxWage = ref(); const incomeTaxRecipient = ref(); const radioUrl = ref(); const settings = ref(); @@ -293,7 +297,6 @@ async function importAccounts(file: File): Promise { 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, }); diff --git a/webapp/src/index.css b/webapp/src/index.css index bd6213e..079126d 100644 --- a/webapp/src/index.css +++ b/webapp/src/index.css @@ -1,3 +1,12 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +input[type=number] { + -moz-appearance: textfield; +} \ No newline at end of file diff --git a/webapp/src/services/account.service.ts b/webapp/src/services/account.service.ts index 4128396..bc45e85 100644 --- a/webapp/src/services/account.service.ts +++ b/webapp/src/services/account.service.ts @@ -30,8 +30,8 @@ export class AccountService { public static async updateShift(accountId: string, shift: string): Promise { return COLLECTION.update(accountId, { shift }); } - public static async updateWage(accountId: string, wageFactor: number): Promise { - return COLLECTION.update(accountId, { wageFactor }); + public static async updateWage(accountId: string, wage: number): Promise { + return COLLECTION.update(accountId, { wage: wage * 100 }); } public static async getMissingStudents(): Promise { return ((await COLLECTION.getFullList()).filter( diff --git a/webapp/src/services/company.service.ts b/webapp/src/services/company.service.ts index 00e73ed..e4c46eb 100644 --- a/webapp/src/services/company.service.ts +++ b/webapp/src/services/company.service.ts @@ -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, diff --git a/webapp/src/services/date.service.ts b/webapp/src/services/date.service.ts index 93a67a8..a741b73 100644 --- a/webapp/src/services/date.service.ts +++ b/webapp/src/services/date.service.ts @@ -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() && diff --git a/webapp/src/services/settings.service.ts b/webapp/src/services/settings.service.ts index a42acde..a6f4192 100644 --- a/webapp/src/services/settings.service.ts +++ b/webapp/src/services/settings.service.ts @@ -9,6 +9,7 @@ export class SettingsService { } public static async setSettings(settings: SettingsRecord): Promise { settings.minWage = settings.minWage * 100; + settings.maxWage = settings.maxWage * 100; // TODO: Enforce maxWageFactor to all account records return COLLECTION.create(settings); } diff --git a/webapp/src/types/pocketbase.types.ts b/webapp/src/types/pocketbase.types.ts index 331836c..59c0584 100644 --- a/webapp/src/types/pocketbase.types.ts +++ b/webapp/src/types/pocketbase.types.ts @@ -43,7 +43,7 @@ export type AccountsRecord = { lastCheckIn?: IsoDateString company?: RecordIdString shift: string - wageFactor: number + wage?: number } export type AccountsListRecord = { @@ -71,7 +71,7 @@ export type SettingsRecord = { minWage: number incomeTax: number incomeTaxRecipient: RecordIdString - maxWageFactor: number + maxWage: number radioUrl: string }