Compare commits
7 Commits
2.0.0-rc.1
...
2.0.0-rc.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ac6c5f9b8 | |||
| f9fc3d91d1 | |||
| f0de9961dc | |||
| 4ded8632e5 | |||
| b2af48463c | |||
| 0b72147e02 | |||
| d1b46cf894 |
@@ -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 }}
|
||||
|
||||
@@ -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
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
97
Backend/doc/templates/abrechnung.typ
vendored
Normal 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),
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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].
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user