7 Commits

Author SHA1 Message Date
7ac6c5f9b8 Merge pull request 'dev/pdf' (#69) from dev/pdf into main
Some checks failed
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 4m43s
Tests / Run Go Tests (push) Failing after 3m44s
Reviewed-on: #69
2026-01-05 12:14:46 +01:00
f9fc3d91d1 improved logging + fixed error from log folder
Some checks failed
Tests / Run Go Tests (push) Failing after 3m7s
2026-01-05 12:13:49 +01:00
f0de9961dc removed doc-creator 2026-01-05 12:13:24 +01:00
4ded8632e5 fixed overtime calc issue
Some checks failed
Arbeitszeitmessung Deploy / Build Document Creator (push) Successful in 2m30s
Tests / Run Go Tests (push) Failing after 2m35s
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 3m19s
2026-01-05 00:39:00 +01:00
b2af48463c updated git action to build right image
Some checks failed
Arbeitszeitmessung Deploy / Build Document Creator (push) Successful in 2m11s
Tests / Run Go Tests (push) Failing after 2m46s
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 3m5s
2026-01-05 00:02:33 +01:00
0b72147e02 Merge pull request 'version 2.0.0 rc' (#68) from dev/feiertage into main
Some checks failed
Tests / Run Go Tests (push) Failing after 2m29s
Arbeitszeitmessung Deploy / Build Document Creator (push) Successful in 4m1s
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 4m4s
Reviewed-on: #68
2026-01-04 23:56:04 +01:00
d1b46cf894 fixed sonarqube errors
Some checks failed
Tests / Run Go Tests (push) Failing after 1m33s
2026-01-04 23:53:16 +01:00
12 changed files with 167 additions and 54 deletions

View File

@@ -39,34 +39,34 @@ jobs:
push: true
context: Backend
tags: ${{ steps.meta.outputs.tags }}
document-creator:
name: Build Document Creator
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: git.letsstein.de
username: ${{ gitea.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: git.letsstein.de/tom/arbeitszeitmessung-doc-creator
tags: |
type=raw,value=latest
type=pep440,pattern={{version}}
- name: Build and push
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
push: true
context: Backend
tags: ${{ steps.meta.outputs.tags }}
# document-creator:
# name: Build Document Creator
# runs-on: ubuntu-latest
# steps:
# - name: Checkout
# uses: actions/checkout@v4
# - name: Login to GitHub Container Registry
# uses: docker/login-action@v3
# with:
# registry: git.letsstein.de
# username: ${{ gitea.actor }}
# password: ${{ secrets.REGISTRY_TOKEN }}
# - name: Set up QEMU
# uses: docker/setup-qemu-action@v3
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
# - name: Extract metadata (tags, labels) for Docker
# id: meta
# uses: docker/metadata-action@v5
# with:
# images: git.letsstein.de/tom/arbeitszeitmessung-doc-creator
# tags: |
# type=raw,value=latest
# type=pep440,pattern={{version}}
# - name: Build and push
# uses: docker/build-push-action@v6
# with:
# platforms: linux/amd64,linux/arm64
# push: true
# context: DocumentCreator
# tags: ${{ steps.meta.outputs.tags }}

View File

@@ -14,9 +14,11 @@ COPY . .
RUN go build -o server .
FROM alpine:3.22
RUN apk add --no-cache tzdata
RUN apk add --no-cache tzdata typst
WORKDIR /app
COPY --from=build /app/server /app/server
COPY ./doc/static /doc/static
COPY ./doc/templates /doc/templates
COPY /static /app/static
ENTRYPOINT ["./server"]

BIN
Backend/doc/static/logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

97
Backend/doc/templates/abrechnung.typ vendored Normal file
View File

@@ -0,0 +1,97 @@
#let table-header(..headers) = {
table.header(
..headers.pos().map(h => strong(h))
)
}
#let abrechnung(meta, days) = {
set page(paper: "a4", margin: (x:1.5cm, y:2.25cm),
footer:[#grid(
columns: (3fr, .65fr),
align: left + horizon,
inset: .5em,
[#meta.EmployeeName -- #meta.TimeRange], grid.cell(rowspan: 2)[#image("/static/logo.png")],
[Arbeitszeitrechnung maschinell erstellt am #meta.CurrentTimestamp],
)
])
set text(font: "Noto Sans", size:10pt, fill: luma(10%))
set table(
stroke: 0.5pt + luma(10%),
inset: .5em,
align: center + horizon,
)
show text: it => {
if it.text == "0min"{
text(oklch(70.8%, 0, 0deg))[#it]
}else if it.text.starts-with("-"){
text(red)[#it]
}else{
it
}
}
[= Abrechnung Arbeitszeit -- #meta.EmployeeName]
[Zeitraum: #meta.TimeRange]
table(
columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, .875fr, 1.25fr),
fill: (x, y) =>
if y == 0 { oklch(87%, 0, 0deg) },
table-header(
[Datum], [Kommen], [Gehen], [Arbeitsart], [Stunden], [Kurzarbeit], [Pause], [Überstunden]
),
.. for day in days {
(
[#day.Date],
if day.DayParts.len() == 0{
table.cell(colspan: 3)[Keine Buchungen]
}else if day.DayParts.len() == 1 and not day.DayParts.first().IsWorkDay{
table.cell(colspan: 3)[#day.DayParts.first().WorkType]
}
else {
table.cell(colspan: 3, inset: 0em)[
#table(
columns: (1fr, 1fr, 1fr),
.. for Zeit in day.DayParts {
(
if Zeit.IsWorkDay{
(
table.cell()[#Zeit.BookingFrom],
table.cell()[#Zeit.BookingTo],
table.cell()[#Zeit.WorkType],
)
}else{
(table.cell(colspan: 3)[#Zeit.WorkType],)
}
)
},
)
]
},
[#day.Worktime],
[#day.Kurzarbeit],
[#day.Pausetime],
[#day.Overtime],
)
if day.IsFriday {
( table.cell(colspan: 8, fill: oklch(87%, 0, 0deg))[Wochenende], ) // note the trailing comma
}
}
)
table(
columns: (3fr, 1fr),
align: right,
inset: (x: .25em, y:.75em),
stroke: none,
table.hline(start: 0, end: 2, stroke: stroke(dash:"dashed", thickness:.5pt)),
[Arbeitszeit :], table.cell(align: left)[#meta.WorkTime],
[Kurzarbeit :], table.cell(align: left)[#meta.Kurzarbeit],
[Überstunden :], table.cell(align: left)[#meta.Overtime],
[Überstunden lfd. :],table.cell(align: left)[#meta.OvertimeTotal],
table.hline(start: 0, end: 2),
)
}

View File

@@ -21,7 +21,7 @@ func LogoutHandler(w http.ResponseWriter, r *http.Request) {
func autoLogout(w http.ResponseWriter) {
users, err := models.GetAllUsers()
var logged_out_users []models.User
var loggedOutUsers []models.User
if err != nil {
fmt.Printf("Error getting user list %v\n", err)
}
@@ -31,7 +31,7 @@ func autoLogout(w http.ResponseWriter) {
if err != nil {
fmt.Printf("Error logging out user %v\n", err)
} else {
logged_out_users = append(logged_out_users, user)
loggedOutUsers = append(loggedOutUsers, user)
log.Printf("Automaticaly logged out user %s, %s ", user.Name, user.Vorname)
}
}
@@ -39,6 +39,6 @@ func autoLogout(w http.ResponseWriter) {
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(logged_out_users)
json.NewEncoder(w).Encode(loggedOutUsers)
}

View File

@@ -198,8 +198,9 @@ func renderPDFSingle(data []typstData) (bytes.Buffer, error) {
var markup bytes.Buffer
var output bytes.Buffer
typstCLI := typst.DockerExec{
ContainerName: helper.GetEnv("TYPST_CONTAINER", "arbeitszeitmessung-doc-creator"),
typstCLI := typst.CLI{
WorkingDirectory: "/doc/",
// ContainerName: helper.GetEnv("TYPST_CONTAINER", "arbeitszeitmessung-doc-creator"),
}
if err := typst.InjectValues(&markup, map[string]any{"data": data}); err != nil {
@@ -225,8 +226,9 @@ func renderPDFSingle(data []typstData) (bytes.Buffer, error) {
func renderPDFMulti(data []typstData) ([]bytes.Buffer, error) {
var outputMulti []bytes.Buffer
typstRender := typst.DockerExec{
ContainerName: helper.GetEnv("TYPST_CONTAINER", "arbeitszeitmessung-doc-creator"),
typstRender := typst.CLI{
WorkingDirectory: "/doc/",
// ContainerName: helper.GetEnv("TYPST_CONTAINER", "arbeitszeitmessung-doc-creator"),
}
for _, d := range data {

View File

@@ -94,7 +94,7 @@ func getBookings(w http.ResponseWriter, r *http.Request) {
if day.Date().Before(lastSub) {
continue
}
aggregatedOvertime += day.GetOvertime(user, models.WorktimeBaseDay, false)
aggregatedOvertime += day.GetOvertime(user, models.WorktimeBaseDay, true)
}
if reportedOvertime, err := user.GetReportedOvertime(); err == nil {
user.Overtime = (reportedOvertime + aggregatedOvertime).Round(time.Minute)

View File

@@ -17,7 +17,16 @@ import (
func main() {
var err error
var logLevel slog.LevelVar
switch helper.GetEnv("LOG_LEVEL", "warn") {
case "debug":
logLevel.Set(slog.LevelDebug)
case "info":
logLevel.Set(slog.LevelInfo)
case "warn":
logLevel.Set(slog.LevelWarn)
case "error":
logLevel.Set(slog.LevelError)
}
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: &logLevel}))
slog.SetDefault(logger)

View File

@@ -28,7 +28,7 @@ func (c *CompoundDay) GetWorkDay() WorkDay {
// IsEmpty implements [IWorkDay].
func (c *CompoundDay) IsEmpty() bool {
return len(c.DayParts) > 0
return len(c.DayParts) == 0
}
// Date implements [IWorkDay].
@@ -47,12 +47,16 @@ func (c *CompoundDay) GetDayProgress(u User) int8 {
// GetOvertime implements [IWorkDay].
func (c *CompoundDay) GetOvertime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
work := c.GetWorktime(u, base, includeKurzarbeit)
var targetHours time.Duration
var overtime time.Duration
for _, day := range c.DayParts {
overtime += day.GetOvertime(u, base, includeKurzarbeit)
switch base {
case WorktimeBaseDay:
targetHours = u.ArbeitszeitProTagFrac(1)
case WorktimeBaseWeek:
targetHours = u.ArbeitszeitProWocheFrac(.2)
}
return overtime
return (work - targetHours).Round(time.Minute)
}
// GetPausetime implements [IWorkDay].

View File

@@ -115,7 +115,7 @@ templ defaultDayComponent(day models.IWorkDay) {
if pause > 0 {
<p class="text-neutral-500 flex flex-row items-center"><span class="icon-[material-symbols-light--motion-photos-paused-outline]"></span>{ helper.FormatDuration(pause) }</p>
}
if overtime != 0 && day.IsEmpty() == false {
if !day.IsEmpty() && overtime != 0 {
<p class="text-neutral-500 flex flex-row items-center">
<span class="icon-[material-symbols-light--more-time]"></span>
{ helper.FormatDuration(overtime) }

View File

@@ -346,7 +346,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if overtime != 0 && day.IsEmpty() == false {
if !day.IsEmpty() && overtime != 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<p class=\"text-neutral-500 flex flex-row items-center\"><span class=\"icon-[material-symbols-light--more-time]\"></span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err

View File

@@ -25,12 +25,11 @@ services:
- ${WEB_PORT}:8080
depends_on:
- db
- document-creator
volumes:
- ../logs:/app/Backend/logs
- ${LOG_PATH}:/app/logs
restart: unless-stopped
document-creator:
image: git.letsstein.de/tom/arbeitszeitmessung-doc-creator
container_name: ${TYPST_CONTAINER}
restart: unless-stopped
# document-creator:
# image: git.letsstein.de/tom/arbeitszeitmessung-doc-creator
# container_name: ${TYPST_CONTAINER}
# restart: unless-stopped