1
0
Fork 0
forked from Kispi/Core

feat(core): add wage functionality to the employer portal
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Simon Giesel 2023-07-12 15:30:25 +02:00
parent 97785d17ad
commit 019d0ae566
14 changed files with 554 additions and 238 deletions

View file

@ -15,7 +15,7 @@ require (
atomicgo.dev/schedule v0.0.2 // indirect
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go v1.44.298 // indirect
github.com/aws/aws-sdk-go v1.44.299 // indirect
github.com/aws/aws-sdk-go-v2 v1.18.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
github.com/aws/aws-sdk-go-v2/config v1.18.27 // indirect
@ -83,7 +83,7 @@ require (
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.130.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/grpc v1.56.2 // indirect
google.golang.org/protobuf v1.31.0 // indirect
lukechampine.com/uint128 v1.3.0 // indirect
@ -93,7 +93,7 @@ require (
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.6.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/sqlite v1.23.1 // indirect
modernc.org/sqlite v1.24.0 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.1.0 // indirect
)

View file

@ -185,7 +185,6 @@ cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOV
cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI=
cloud.google.com/go/compute v1.20.0 h1:cUOcywWuowO9It2i1KX1lIb0HH7gLv6nENKuZGnlcSo=
cloud.google.com/go/compute v1.20.0/go.mod h1:kn5BhC++qUWR/AM3Dn21myV7QbgqejW04cAOrtppaQI=
cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
@ -790,10 +789,8 @@ github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4
github.com/aws/aws-sdk-go v1.44.156/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.245/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.284/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.294 h1:3x7GaEth+pDU9HwFcAU0awZlEix5CEdyIZvV08SlHa8=
github.com/aws/aws-sdk-go v1.44.294/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.298 h1:5qTxdubgV7PptZJmp/2qDwD2JL187ePL7VOxsSh1i3g=
github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.299 h1:HVD9lU4CAFHGxleMJp95FV/sRhtg7P4miHD1v88JAQk=
github.com/aws/aws-sdk-go v1.44.299/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
github.com/aws/aws-sdk-go-v2 v1.18.1 h1:+tefE750oAb7ZQGzla6bLkOwfcQCEtC5y2RqoqCeqKo=
github.com/aws/aws-sdk-go-v2 v1.18.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
@ -1303,7 +1300,6 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
@ -1453,7 +1449,6 @@ github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38
github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
github.com/googleapis/gax-go/v2 v2.9.1/go.mod h1:4FG3gMrVZlyMp5itSYKMU9z/lBE7+SbnUOvzH2HqbEY=
github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw=
github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4=
github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
@ -1937,8 +1932,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pocketbase/dbx v1.10.0 h1:58VIT7r6T+BnVbYVosvGBsPjQEic3/VFRYGT823vWSQ=
github.com/pocketbase/dbx v1.10.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
github.com/pocketbase/pocketbase v0.16.6 h1:zE+9SPXlhimZpuiD4KrmY+cp4b2lyL8gLAbkjj5YqFY=
github.com/pocketbase/pocketbase v0.16.6/go.mod h1:xXXL26RVy0vGxDHOffoaeEv7CvZSJHivnfAeygmEfD8=
github.com/pocketbase/pocketbase v0.16.8 h1:uILzBla2DNMFGZFRu0cStIv3AVjYgicOQs2F1AeXpGw=
github.com/pocketbase/pocketbase v0.16.8/go.mod h1:vmajDvROzq//BiQ4RoNqHmjHf5i8RMgK665zQ14oo/g=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
@ -2351,7 +2344,6 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
@ -2387,8 +2379,6 @@ golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeap
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
golang.org/x/image v0.9.0 h1:QrzfX26snvCM20hIhBwuHI/ThTg18b/+kcKdXHvnR+g=
golang.org/x/image v0.9.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -2423,8 +2413,6 @@ golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -2521,7 +2509,6 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
@ -2558,7 +2545,6 @@ golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
@ -2739,7 +2725,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -2756,7 +2741,6 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
@ -2776,7 +2760,6 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
@ -2886,8 +2869,6 @@ golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -2978,8 +2959,6 @@ google.golang.org/api v0.124.0/go.mod h1:xu2HQurE5gi/3t1aFCvhPD781p0a3p11sdunTJ2
google.golang.org/api v0.125.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw=
google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw=
google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750=
google.golang.org/api v0.129.0 h1:2XbdjjNfFPXQyufzQVwPf1RRnHH8Den2pfNE2jw7L8w=
google.golang.org/api v0.129.0/go.mod h1:dFjiXlanKwWE3612X97llhsoI36FAoIiRj3aTl5b/zE=
google.golang.org/api v0.130.0 h1:A50ujooa1h9iizvfzA4rrJr2B7uRmWexwbekQ2+5FPQ=
google.golang.org/api v0.130.0/go.mod h1:J/LCJMYSDFvAVREGCbrESb53n4++NMBDetSHGL5I5RY=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@ -3140,8 +3119,8 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY=
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao=
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8=
google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8=
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
@ -3152,10 +3131,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 h1:2FZP5XuJY9zQyGM5N0rtovnoXjiMUEIUMvw0m9wlpLc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@ -3204,8 +3181,6 @@ google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwS
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/grpc v1.56.0/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ=
google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
@ -3389,8 +3364,8 @@ modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI=
modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=

View file

@ -1,9 +1,15 @@
package main
import (
"net/http"
"time"
"cliffbreak.de/hgoe-sas-server/pkg/middlewares"
"cliffbreak.de/hgoe-sas-server/pkg/utils"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
"github.com/pterm/pterm"
)
@ -15,6 +21,18 @@ func main() {
app.OnBeforeServe().Add(middlewares.ServeSPA(utils.GetEnv("WEBAPP", "./webapp")))
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/time",
Handler: func(c echo.Context) error {
return c.String(http.StatusOK, time.Now().Format(time.RFC3339))
},
})
return nil
})
if err := app.Start(); err != nil {
pterm.Fatal.Println(err)
}

View file

@ -1,63 +1,4 @@
[
{
"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": "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": "w85pgrtrmovf916",
"name": "transactions",
@ -294,54 +235,6 @@
"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": "s854d2w72fvyl54",
"name": "companies",
"type": "auth",
"system": false,
"schema": [
{
"id": "h5ogbj93",
"name": "accountNumber",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "cvcgnf4x",
"name": "name",
"type": "text",
"system": false,
"required": true,
"options": {
"min": null,
"max": null,
"pattern": ""
}
}
],
"indexes": [],
"listRule": "",
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {
"allowEmailAuth": true,
"allowOAuth2Auth": true,
"allowUsernameAuth": true,
"exceptEmailDomains": null,
"manageRule": null,
"minPasswordLength": 8,
"onlyEmailDomains": null,
"requireEmail": false
}
},
{
"id": "5msnfxat1sc2c1r",
"name": "companyTransactions",
@ -393,5 +286,150 @@
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "s854d2w72fvyl54",
"name": "companies",
"type": "auth",
"system": false,
"schema": [
{
"id": "h5ogbj93",
"name": "accountNumber",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "cvcgnf4x",
"name": "name",
"type": "text",
"system": false,
"required": true,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "ujqd3vik",
"name": "earlyShiftPayed",
"type": "date",
"system": false,
"required": false,
"options": {
"min": "",
"max": ""
}
},
{
"id": "t1kakf3f",
"name": "lateShiftPayed",
"type": "date",
"system": false,
"required": false,
"options": {
"min": "",
"max": ""
}
}
],
"indexes": [
"CREATE INDEX `idx_eX1Vqqz` ON `companies` (`accountNumber`)"
],
"listRule": "",
"viewRule": null,
"createRule": null,
"updateRule": "(@request.data.id = null && @request.data.username = null && @request.data.email = null && @request.data.accountNumber = null && @request.data.created = null && @request.data.updated = null && @request.data.name = null && @request.data.emailVisibility = null && @request.data.verified = null) && @request.data.id = @request.auth.id",
"deleteRule": null,
"options": {
"allowEmailAuth": true,
"allowOAuth2Auth": true,
"allowUsernameAuth": true,
"exceptEmailDomains": null,
"manageRule": null,
"minPasswordLength": 8,
"onlyEmailDomains": null,
"requireEmail": false
}
},
{
"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": {}
}
]

View file

@ -66,11 +66,11 @@
<span
v-if="header.type === TableHeaderType.BUTTON_CHANGE_ACCOUNT_NUMBER"
class="btn-ghost btn-sm btn p-1"
@click="$emit('changeAccountNumber', entry.id)"
>
<div
class="tooltip normal-case"
data-tip="Kontonummer ändern"
@click="$emit('changeAccountNumber', entry.id)"
>
<CreditCardIcon class="h-6 w-6" />
</div>

View file

@ -0,0 +1,61 @@
<template>
<AtomModal
:id="id"
ref="modal"
:title="title"
>
<div class="flex flex-col gap-4">
<slot />
</div>
<template #action>
<button
class="btn gap-2"
:for="id"
@click="modal?.close()"
>
<XCircleIcon class="h-6 w-6" />
Abbrechen
</button>
<button
class="btn gap-2"
@click="$emit('confirm')"
>
<CheckCircleIcon class="h-6 w-6" />
Bestätigen
</button>
</template>
</AtomModal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { CheckCircleIcon, XCircleIcon } from '@heroicons/vue/24/outline';
import AtomModal from '../atoms/AtomModal.vue';
const modal = ref<InstanceType<typeof AtomModal>>();
defineProps({
id: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
});
defineExpose({
show: () => modal.value?.show(),
close: () => modal.value?.close(),
isOpen: () => modal.value?.isOpen(),
});
defineEmits<{
confirm: [],
}>();
</script>
<style lang="scss" scoped>
</style>

View file

@ -1,80 +1,94 @@
<template>
<div
v-if="account || company"
class="hero-content flex-col pt-0 text-center lg:p-6"
>
<h1 class="mt-5 text-8xl">{{ CurrencyService.toString(getBalance()) }}</h1>
<h3
v-if="account"
class="mt-10 text-5xl font-bold"
>
Kontoinhaber: {{ `${account.firstName} ${account.lastName}` }}
</h3>
<h3
v-if="company"
class="mt-10 text-5xl font-bold"
>
Firma: {{ company.name }}
</h3>
<h4 class="mb-10 text-4xl">Kontonummer: {{ (account ? account : company ? company : null)?.accountNumber }}</h4>
<MoleculeTransactionTable
v-if="transactions"
class="mb-24 max-w-[42rem]"
:balance="getBalance()"
:transactions="transactions"
/>
</div>
<div
v-if="account || company"
class="fixed bottom-0 flex w-full place-content-center gap-16 bg-base-100 p-5 lg:w-[calc(100%-16rem)] lg:gap-32"
>
<MoleculeInputModal
id="deposit-modal"
ref="depositModal"
title="Einzahlung"
action-label="Einzahlen"
input-label="Einzuzahlender Betrag"
@submit="handleDeposit"
/>
<button
class="btn gap-2"
@click="() => depositModal?.show()"
>
<ChevronUpIcon class="h-6 w-6 text-success" />
Einzahlen
</button>
<MoleculeInputModal
id="withdraw-modal"
ref="withdrawModal"
title="Auszahlung"
action-label="Auszahlen"
input-label="Auszuzahlender Betrag"
@submit="handleWithdraw"
/>
<button
class="btn gap-2"
@click="() => withdrawModal?.show()"
>
<ChevronDownIcon class="h-6 w-6 text-error" />
Auszahlen
</button>
</div>
<AtomHeroText v-else>
Bitte Karte scannen...
<div>
<div
v-if="error"
class="alert alert-error mt-6 shadow-lg"
v-if="account || company"
class="hero-content flex-col pt-0 text-center lg:p-6"
>
<XCircleIcon class="h-6 w-6" />
<span class="text-base font-normal"><b>Fehler: </b>{{ error }}</span>
<h1 class="mt-5 text-8xl">{{ CurrencyService.toString(getBalance()) }}</h1>
<h3
v-if="account"
class="mt-10 text-5xl font-bold"
>
Kontoinhaber: {{ `${account.firstName} ${account.lastName}` }}
</h3>
<h3
v-if="company && company.id == settings?.incomeTaxRecipient"
class="mt-10 text-5xl font-bold"
>
<div class="flex items-center gap-4">
<AtomLogo class="h-14 w-14" />
{{ company.name }}
</div>
</h3>
<h3
v-else-if="company"
class="mt-10 text-5xl font-bold"
>
Firma: {{ company.name }}
</h3>
<h4 class="mb-10 text-4xl">Kontonummer: {{ (account ? account : company ? company : null)?.accountNumber }}</h4>
<MoleculeTransactionTable
v-if="transactions"
class="mb-24 max-w-[42rem]"
:balance="getBalance()"
:transactions="transactions"
/>
</div>
</AtomHeroText>
<div
v-if="depositError"
class="toast z-10"
>
<div class="alert alert-error">
<span>Transaktion nicht möglich. Konto ist nicht ausreichend gedeckt.</span>
<div
v-if="account || company"
class="fixed bottom-0 flex w-full place-content-center gap-16 bg-base-100 p-5 lg:w-[calc(100%-16rem)] lg:gap-32"
>
<MoleculeInputModal
id="deposit-modal"
ref="depositModal"
title="Einzahlung"
action-label="Einzahlen"
input-label="Einzuzahlender Betrag"
@submit="handleDeposit"
/>
<button
class="btn gap-2"
@click="() => depositModal?.show()"
>
<ChevronUpIcon class="h-6 w-6 text-success" />
Einzahlen
</button>
<MoleculeInputModal
id="withdraw-modal"
ref="withdrawModal"
title="Auszahlung"
action-label="Auszahlen"
input-label="Auszuzahlender Betrag"
@submit="handleWithdraw"
/>
<button
class="btn gap-2"
@click="() => withdrawModal?.show()"
>
<ChevronDownIcon class="h-6 w-6 text-error" />
Auszahlen
</button>
</div>
<AtomHeroText
v-else
class="h-full"
>
Bitte Karte scannen...
<div
v-if="error"
class="alert alert-error mt-6 shadow-lg"
>
<XCircleIcon class="h-6 w-6" />
<span class="text-base font-normal"><b>Fehler: </b>{{ error }}</span>
</div>
</AtomHeroText>
<div
v-if="depositError"
class="toast z-10"
>
<div class="alert alert-error">
<span>Transaktion nicht möglich. Konto ist nicht ausreichend gedeckt.</span>
</div>
</div>
</div>
</template>
@ -84,7 +98,7 @@ import { onMounted, onUnmounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { onKeyStroke, promiseTimeout } from '@vueuse/core';
import { RecordSubscription, UnsubscribeFunc } from 'pocketbase';
import { AccountsResponse, CompaniesResponse, TransactionsResponse } from '../../types/pocketbase.types';
import { AccountsResponse, CompaniesResponse, SettingsResponse, TransactionsResponse } from '../../types/pocketbase.types';
import { AccountService } from '../../services/account.service';
import { AccountType, BankService, TransactionType } from '../../services/bank.service';
import { CompanyService } from '../../services/company.service';
@ -93,6 +107,8 @@ import { ChevronDownIcon, ChevronUpIcon, XCircleIcon } from '@heroicons/vue/24/o
import AtomHeroText from '../atoms/AtomHeroText.vue';
import MoleculeInputModal from '../molecules/MoleculeInputModal.vue';
import MoleculeTransactionTable from '../molecules/MoleculeTransactionTable.vue';
import { SettingsService } from '../../services/settings.service';
import AtomLogo from '../atoms/AtomLogo.vue';
const router = useRouter();
const subscription = ref<UnsubscribeFunc>();
@ -107,6 +123,7 @@ const accountId = ref<string>('');
const inputBuffer = ref<string>('');
const error = ref('');
const depositError = ref(false);
const settings = ref<SettingsResponse>();
if(router.currentRoute.value.query.accountNumber) {
accountId.value = router.currentRoute.value.query.accountNumber as string;
@ -228,6 +245,7 @@ function handleRealtimeUpdates(transactionSubscription: RecordSubscription<Trans
onMounted(async () => {
subscription.value = await BankService.subscribeToTransactionChanges(handleRealtimeUpdates);
companySubscription.value = await BankService.subscribeToCompanyTransactionChanges(handleRealtimeUpdates);
settings.value = await SettingsService.getSettings();
});
onUnmounted(() => {

View file

@ -42,35 +42,149 @@
Abmelden
</button>
</div>
<div class="flex items-center justify-between">
<h2 class="text-2xl">500,00 Batzen</h2>
<!-- <div class="flex items-center justify-between">
<h2 class="text-2xl">Kontostand: {{ CurrencyService.toString(getBalance()) }}</h2>
<button class="btn-primary btn-disabled btn w-48">
<BanknotesIcon class="h-6 w-6" />
Transaktionen
</button>
</div> -->
<div class="stats shadow">
<div class="stat place-items-center">
<div class="stat-title">Kontostand</div>
<div class="stat-value">{{ CurrencyService.toString(getBalance(), false) }}</div>
<div class="stat-desc">Batzen</div>
</div>
<div class="stat place-items-center">
<div class="stat-title">Arbeitnehmer</div>
<div class="stat-value">{{ accounts.filter(account => (account.shift != Shifts.EARLY && account.shift != Shifts.LATE)).length }}</div>
<div class="stat-desc">ohne Schicht</div>
</div>
<div class="stat place-items-center">
<div class="stat-title">Arbeitnehmer</div>
<div class="stat-value">{{ accounts.filter(account => account.shift == Shifts.EARLY).length }}</div>
<div class="stat-desc">in Frühschicht</div>
</div>
<div class="stat place-items-center">
<div class="stat-title">Arbeitnehmer</div>
<div class="stat-value">{{ accounts.filter(account => account.shift == Shifts.LATE).length }}</div>
<div class="stat-desc">in Spätschicht</div>
</div>
</div>
<div class="flex items-center justify-center gap-4">
<button
class="btn-primary btn"
:class="{'btn-disabled': (getBalance() < getTotalCostsByShifts(Shifts.EARLY) || DateService.isToday(new Date(company.earlyShiftPayed)))}"
@click="wageType = Shifts.EARLY; verifyModal?.show()"
>
<SunIcon class="h-6 w-6" />
Frühschicht bezahlen ({{ CurrencyService.toString(getTotalCostsByShifts(Shifts.EARLY)) }})
</button>
<button
class="btn-primary btn"
:class="{'btn-disabled': (getBalance() < getTotalCostsByShifts(Shifts.LATE) || DateService.isToday(new Date(company.lateShiftPayed)))}"
@click="wageType = Shifts.LATE; verifyModal?.show()"
>
<MoonIcon class="h-6 w-6" />
Spätschicht bezahlen ({{ CurrencyService.toString(getTotalCostsByShifts(Shifts.LATE)) }})
</button>
</div>
<div class="flex flex-col gap-2">
<div
v-if="DateService.isToday(new Date(company.earlyShiftPayed))"
class="alert alert-info"
>
<InformationCircleIcon class="h-6 w-6" />
Die Frühschicht wurde heute bereits bezahlt.
</div>
<div
v-if="DateService.isToday(new Date(company.lateShiftPayed))"
class="alert alert-info"
>
<InformationCircleIcon class="h-6 w-6" />
Die Spätschicht wurde heute bereits bezahlt.
</div>
<div
v-if="getBalance() < getTotalCostsByShifts(Shifts.EARLY)"
class="alert alert-warning"
>
<ExclamationTriangleIcon class="h-6 w-6" />
Konto nicht ausreichend gedeckt um die Frühschicht zu bezahlen.
</div>
<div
v-if="getBalance() < getTotalCostsByShifts(Shifts.LATE)"
class="alert alert-warning"
>
<ExclamationTriangleIcon class="h-6 w-6" />
Konto nicht ausreichend gedeckt um die Spätschicht zu bezahlen.
</div>
</div>
<MoleculeDataTable
v-if="accounts"
:table-headers="accountTableHeaders"
:data="accounts"
@select-shift="selectShift"
@select-wage="selectWage"
/>
</div>
<MoleculeDataTable
v-if="accounts"
:table-headers="accountTableHeaders"
:data="accounts"
@select-shift="selectShift"
@select-wage="selectWage"
/>
<MoleculeVerifyModal
id="verify-modal"
ref="verifyModal"
title="Lohnauszahlung bestätigen"
@confirm="async () => {
verifyModal?.close();
if(wageType) {
await CompanyService.payWage(wageType);
}
wageType = undefined;
init();
}"
>
<div class="flex flex-col gap-1">
<p>Lohn wirklich für die {{ wageType == Shifts.EARLY ? 'Frühschicht' : 'Spätschicht' }} auszahlen?</p>
<p>
Das Konto wird mit <b>{{
CurrencyService.toString(
wageType == Shifts.EARLY ?
getTotalCostsByShifts(Shifts.EARLY) : getTotalCostsByShifts(Shifts.LATE)
)
}}</b> belastet.<br />
Dies beinhaltet die Lohnsteuer von {{ settings?.incomeTax }}% ({{
CurrencyService.toString(
wageType == Shifts.EARLY ?
getTotalCostsByShifts(Shifts.EARLY) * (settings?.incomeTax ?? 0) / 100 :
getTotalCostsByShifts(Shifts.LATE) * (settings?.incomeTax ?? 0) / 100
)
}}).
</p>
<p><b>Hinweis:</b> Diese Aktion kann nur 1x am Tag ausgeführt werden.</p>
</div>
</MoleculeVerifyModal>
</div>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, watch } from 'vue';
import { RecordSubscription, UnsubscribeFunc } from 'pocketbase';
import { AccountsResponse, CompaniesResponse } from '../../types/pocketbase.types';
import { AccountsResponse, CompaniesResponse, SettingsResponse, TransactionsResponse } from '../../types/pocketbase.types';
import { AccountService } from '../../services/account.service';
import { BankService } from '../../services/bank.service';
import { CompanyService } from '../../services/company.service';
import { CurrencyService } from '../../services/currency.service';
import { DateService } from '../../services/date.service';
import { SettingsService } from '../../services/settings.service';
import { Shifts } from '../../enums/shift.enum';
import { ArrowLeftOnRectangleIcon, BanknotesIcon } from '@heroicons/vue/24/outline';
import { ArrowLeftOnRectangleIcon,
BanknotesIcon,
ExclamationTriangleIcon,
InformationCircleIcon,
MoonIcon,
SunIcon,
} from '@heroicons/vue/24/outline';
import AtomInput from '../atoms/AtomInput.vue';
import MoleculeDataTable, { TableHeaderType } from '../molecules/MoleculeDataTable.vue';
import MoleculeLoginModal from '../molecules/MoleculeLoginModal.vue';
import MoleculeVerifyModal from '../molecules/MoleculeVerifyModal.vue';
const loginModal = ref<InstanceType<typeof MoleculeLoginModal>>();
const searchQuery = ref('');
@ -80,9 +194,13 @@ const initCompanies = ref<CompaniesResponse[]>([]);
const selectedCompany = ref<CompaniesResponse>();
const company = ref<CompaniesResponse>();
const accounts = ref<AccountsResponse[]>([]);
const transactions = ref<TransactionsResponse[]>([]);
const isAuthenticated = ref(false);
const accountsSubscription = ref<UnsubscribeFunc>();
const settings = ref<SettingsResponse>();
const error = ref('');
const verifyModal = ref<InstanceType<typeof MoleculeVerifyModal>>();
const wageType = ref<Shifts|undefined>();
const companyTableHeaders = [
{
@ -137,6 +255,7 @@ function search(value: string): void {
onMounted(async () => {
isAuthenticated.value = CompanyService.isAuthenticated();
settings.value = await SettingsService.getSettings();
if(isAuthenticated.value) {
init();
} else {
@ -147,6 +266,7 @@ onMounted(async () => {
async function init() {
company.value = await CompanyService.getCompany();
accounts.value = await AccountService.getAccountsByCompanyId(company.value.id);
transactions.value = await BankService.getCompanyTransactions(company.value.id);
accountsSubscription.value = await AccountService.subscribeToAccountChanges(async (data: RecordSubscription<AccountsResponse>) => {
if(data.action === 'update') {
accounts.value = accounts.value.map((account) => {
@ -159,6 +279,16 @@ async function init() {
});
}
function getBalance(): number {
return transactions.value?.reduce((acc, transaction) => acc + transaction.amount, 0) ?? 0;
}
function getTotalCostsByShifts(shift: Shifts): number {
return accounts.value?.filter(
(account) => account.shift === shift).reduce((acc, account) => acc + account.wageFactor * (settings.value?.minWage ?? 0), 0,
) ?? 0;
}
function showLoginDialog(id: string): void {
selectedCompany.value = companies.value.find((company) => company.id === id);
loginModal.value?.show();

View file

@ -27,6 +27,13 @@
class="w-2/4"
/>
</div>
<div class="flex place-items-center justify-between">
<span>Steuer-Empfänger-ID</span>
<AtomInput
v-model="incomeTaxRecipient"
class="!w-2/4"
/>
</div>
<div class="flex flex-col gap-2">
<span>Radio URL</span>
<AtomInput v-model="radioUrl" />
@ -196,6 +203,7 @@ const errorModal = ref<InstanceType<typeof MoleculeErrorModal>>();
const minWage = ref<number>();
const incomeTax = ref<number>();
const maxWageFactor = ref<number>();
const incomeTaxRecipient = ref<string>();
const radioUrl = ref<string>();
const settings = ref<SettingsResponse>();
const settingsSaveSuccess = ref<boolean>(false);
@ -289,15 +297,17 @@ onMounted(async () => {
minWage.value = settings.value.minWage / 100;
incomeTax.value = settings.value.incomeTax;
maxWageFactor.value = settings.value.maxWageFactor;
incomeTaxRecipient.value = settings.value.incomeTaxRecipient;
radioUrl.value = settings.value.radioUrl;
});
async function saveSettings() {
if(minWage.value && incomeTax.value && maxWageFactor.value && radioUrl.value) {
if(minWage.value && incomeTax.value && maxWageFactor.value && incomeTaxRecipient.value && radioUrl.value) {
settings.value = await SettingsService.setSettings({
minWage: minWage.value,
incomeTax: incomeTax.value,
maxWageFactor: maxWageFactor.value,
incomeTaxRecipient: incomeTaxRecipient.value,
radioUrl: radioUrl.value,
});
settingsSaveSuccess.value = true;

View file

@ -19,7 +19,7 @@ export class BankService {
}
public static async addTransaction(accountId: string,
amount: number,
type: TransactionType,
type: TransactionType | string,
accountType: AccountType = AccountType.ACCOUNT,
): Promise<TransactionsResponse> {
let label = '';
@ -36,11 +36,13 @@ export class BankService {
case TransactionType.OPEN_ACCOUNT:
label = OPEN_ACCOUNT_LABEL;
break;
default:
label = type;
}
if(accountType === AccountType.COMPANY) {
return COMPANY_COLLECTION.create({ account: accountId, label, amount: amount * 100 });
} else {
return COLLECTION.create({ account: accountId, label, amount: amount * 100 });
return COLLECTION.create({ account: accountId, label, amount: amount * 100 }, { $cancelKey: `${accountId}` });
}
}
public static async subscribeToTransactionChanges(callback: (data: RecordSubscription<TransactionsResponse>)=> void): Promise<UnsubscribeFunc> {

View file

@ -1,8 +1,12 @@
import { RecordAuthResponse, RecordSubscription, UnsubscribeFunc } from 'pocketbase';
import { Collections, CompaniesRecord, CompaniesResponse } from '../types/pocketbase.types';
import { PocketbaseService } from './pocketbase.service';
import { useEventBus } from '@vueuse/core';
import { RecordAuthResponse, RecordSubscription, UnsubscribeFunc } from 'pocketbase';
import { Collections, CompaniesRecord, CompaniesResponse, TransactionsResponse } from '../types/pocketbase.types';
import { AccountService } from './account.service';
import { AuthService } from './auth.service';
import { AccountType, BankService } from './bank.service';
import { PocketbaseService } from './pocketbase.service';
import { SettingsService } from './settings.service';
import { Shifts } from '../enums/shift.enum';
const API = PocketbaseService.getApi();
const COLLECTION = API.collection(Collections.Companies);
@ -43,8 +47,8 @@ export class CompanyService {
public static async getCompanyByAccountNumber(accountNumber: string): Promise<CompaniesResponse> {
return COLLECTION.getFirstListItem(`accountNumber="${accountNumber}"`);
}
public static async updateAccountNumber(accountId: string, accountNumber: string): Promise<CompaniesResponse> {
return COLLECTION.update(accountId, { accountNumber });
public static async updateAccountNumber(companyId: string, accountNumber: string): Promise<CompaniesResponse> {
return COLLECTION.update(companyId, { accountNumber });
}
public static async subscribeToCompanyChanges(
callback: (data: RecordSubscription<CompaniesResponse>)=> void,
@ -53,4 +57,40 @@ export class CompanyService {
callback(data);
});
}
public static async payWage(shift: Shifts): Promise<boolean> {
const company = await this.getCompany();
if(company) {
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);
let taxes = 0;
if(balance >= totalWages) {
await BankService.addTransaction(company.id, -totalWages / 100, `Lohn für ${shift}`, AccountType.COMPANY);
const promises: Promise<TransactionsResponse>[] = [];
accounts.forEach(async account => {
promises.push(BankService.addTransaction(
account.id,
(((account.wageFactor * settings.minWage) * (1 - (settings.incomeTax / 100)))) / 100,
// 300 - 1 * -
`Lohn für ${shift} bei ${company.name}`,
));
taxes += (account.wageFactor * settings.minWage) * (settings.incomeTax / 100);
});
await Promise.all(promises);
await BankService.addTransaction(settings.incomeTaxRecipient,
taxes / 100,
`Lohnsteuer von ${company.name} für ${shift}`,
AccountType.COMPANY,
);
if(shift == Shifts.EARLY) {
await COLLECTION.update<CompaniesResponse>(company.id, { earlyShiftPayed: new Date() });
} else {
await COLLECTION.update(company.id, { lateShiftPayed: new Date() });
}
return true;
}
}
return false;
}
}

View file

@ -1,9 +1,9 @@
export class CurrencyService {
public static toString(value: number): string {
public static toString(value: number, suffix = true): string {
return `${(value / 100).toLocaleString('de-DE', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})} Batzen`;
})}${suffix ? ' Batzen' : ''}`;
}
public static toSignedString(value: number): string {
return `${(value / 100) > 0 ? '+' : ''}${(value / 100).toLocaleString('de', {

View file

@ -1,3 +1,8 @@
import { PocketbaseService } from './pocketbase.service';
const API = PocketbaseService.getApi();
let serverTime: Date;
export class DateService {
public static toString(date: Date): string {
return date.toLocaleDateString('de-DE', {
@ -23,4 +28,20 @@ export class DateService {
second: '2-digit',
});
}
public static isToday(date: Date): boolean {
this.getServerTime();
if(!serverTime) { return false; }
return date.getDate() === serverTime.getDate() &&
date.getMonth() === serverTime.getMonth() &&
date.getFullYear() === serverTime.getFullYear();
}
public static isOlderThanAMinute(date: Date): boolean {
return date.getTime() < new Date().getTime() - 60 * 1000;
}
public static async getServerTime(): Promise<Date> {
if(!serverTime || this.isOlderThanAMinute(serverTime)) {
serverTime = new Date(await (await fetch(API.baseUrl + '/api/time')).text());
}
return serverTime;
}
}

View file

@ -57,6 +57,8 @@ export type AccountsListRecord<Tbalance = unknown, Tname = unknown> = {
export type CompaniesRecord = {
accountNumber?: string
name: string
earlyShiftPayed?: IsoDateString
lateShiftPayed?: IsoDateString
}
export type CompanyTransactionsRecord = {
@ -68,6 +70,7 @@ export type CompanyTransactionsRecord = {
export type SettingsRecord = {
minWage: number
incomeTax: number
incomeTaxRecipient: RecordIdString
maxWageFactor: number
radioUrl: string
}