dev/pdf #69

Merged
tom_trgr merged 2 commits from dev/pdf into main 2026-01-05 12:14:47 +01:00
7 changed files with 152 additions and 43 deletions

View File

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

View File

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

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

View File

@@ -17,7 +17,16 @@ import (
func main() { func main() {
var err error var err error
var logLevel slog.LevelVar 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) logLevel.Set(slog.LevelWarn)
case "error":
logLevel.Set(slog.LevelError)
}
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: &logLevel})) logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: &logLevel}))
slog.SetDefault(logger) slog.SetDefault(logger)

View File

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