307 lines
8.0 KiB
Go
307 lines
8.0 KiB
Go
package genpdf
|
|
|
|
import (
|
|
domain "business-engine/internal/domain"
|
|
"fmt"
|
|
"slices"
|
|
"time"
|
|
|
|
"codeberg.org/go-pdf/fpdf"
|
|
"golang.org/x/text/language"
|
|
"golang.org/x/text/message"
|
|
)
|
|
|
|
// LineItem repräsentiert eine einzelne Position in der Abrechnung.
|
|
type LineItem struct {
|
|
Lohnart string
|
|
Beschreibung string
|
|
Anzahl float64
|
|
Betrag float64
|
|
Faktor float64
|
|
}
|
|
|
|
type StatementInfo struct {
|
|
GearbeiteteStunden float64
|
|
GearbeiteteTage float64
|
|
Urlaubsgeld float64
|
|
Krankheitstage float64
|
|
}
|
|
|
|
// Total berechnet den Gesamtwert der Position.
|
|
func (item LineItem) Total() float64 {
|
|
if item.Faktor > 0 {
|
|
return item.Anzahl * item.Betrag * (item.Faktor / 100)
|
|
}
|
|
return item.Anzahl * item.Betrag
|
|
}
|
|
|
|
func GenerateSalaryStatement(stmt domain.PayrollStatement, filename string, month time.Time) error {
|
|
pdf := fpdf.New("P", "mm", "A4", "")
|
|
pdf.AddUTF8Font("DejaVu", "", "DejaVuSans.ttf")
|
|
pdf.AddUTF8Font("DejaVu", "B", "DejaVuSans-Bold.ttf")
|
|
pdf.AddPage()
|
|
|
|
pdf.SetFont("DejaVu", "", 12)
|
|
pdf.Cell(0, 10, month.Format("01-2006"))
|
|
pdf.Ln(12)
|
|
|
|
pdf.SetDrawColor(200, 200, 200)
|
|
pdf.SetLineWidth(0.5)
|
|
|
|
x, y := pdf.GetXY()
|
|
pdf.Line(x, y, x+40, y)
|
|
pdf.Ln(10)
|
|
|
|
pdf.SetFont("DejaVu", "B", 16)
|
|
title := fmt.Sprintf("Gehaltsaufstellung für %s %s", stmt.Vorname, stmt.Nachname)
|
|
pdf.Cell(0, 10, title)
|
|
pdf.Ln(15)
|
|
|
|
drawInfoBlock(pdf, StatementInfo{
|
|
GearbeiteteStunden: stmt.GearbeiteteStunden,
|
|
GearbeiteteTage: stmt.GearbeiteteTage,
|
|
Urlaubsgeld: stmt.Urlaubsgeld,
|
|
Krankheitstage: stmt.Krankheitstage,
|
|
})
|
|
|
|
pdf.Ln(15)
|
|
drawHeader(pdf)
|
|
|
|
items := genLineItemsFromStatement(stmt)
|
|
|
|
var gesamtbetrag float64
|
|
for _, item := range items {
|
|
drawRow(pdf, item)
|
|
gesamtbetrag += item.Total()
|
|
}
|
|
|
|
drawSummary(pdf, gesamtbetrag)
|
|
|
|
if err := pdf.OutputFileAndClose(filename); err != nil {
|
|
return err
|
|
}
|
|
if pdf.Err() {
|
|
return pdf.Error()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func genLineItemsFromStatement(stmt domain.PayrollStatement) []LineItem {
|
|
var items []LineItem
|
|
|
|
// --- Gehalt & Stunden ---
|
|
if stmt.Vertragsstunden > 0 && stmt.VertragStundenlohn > 0 {
|
|
items = append(items, LineItem{
|
|
Lohnart: "0005",
|
|
Beschreibung: "Gehalt",
|
|
Anzahl: stmt.Vertragsstunden,
|
|
Betrag: stmt.VertragStundenlohn,
|
|
})
|
|
}
|
|
if stmt.Ueberstunden > 0 && stmt.VertragStundenlohn > 0 {
|
|
items = append(items, LineItem{
|
|
Lohnart: "1250",
|
|
Beschreibung: "Überstundenvergütung",
|
|
Anzahl: stmt.Ueberstunden,
|
|
Betrag: stmt.VertragStundenlohn,
|
|
})
|
|
}
|
|
|
|
if stmt.Verpflegungspauschale > 0 {
|
|
items = append(items, LineItem{
|
|
Lohnart: "0227",
|
|
Beschreibung: "Verpflegungsmehrauf. 14€",
|
|
Anzahl: stmt.Verpflegungspauschale,
|
|
Betrag: 14.00,
|
|
})
|
|
}
|
|
if stmt.Urlaubsgeld > 0 {
|
|
items = append(items, LineItem{
|
|
Lohnart: "0052",
|
|
Beschreibung: "Urlaubsgeld Angestellte",
|
|
Anzahl: stmt.Urlaubsgeld,
|
|
Betrag: 44.00,
|
|
})
|
|
}
|
|
if stmt.TourengeldBonus > 0 {
|
|
items = append(items, LineItem{
|
|
Lohnart: "5019",
|
|
Beschreibung: "Bonus Tourengeld",
|
|
Anzahl: 1,
|
|
Betrag: stmt.TourengeldBonus,
|
|
})
|
|
}
|
|
if stmt.Tourengeld > 0 {
|
|
items = append(items, LineItem{
|
|
Lohnart: "1030",
|
|
Beschreibung: "Tourengeld",
|
|
Anzahl: stmt.Tourengeld,
|
|
Betrag: 2.5,
|
|
})
|
|
}
|
|
if stmt.PuenktlichkeitspraemieAnzahl > 0 {
|
|
items = append(items, LineItem{
|
|
Lohnart: "0109",
|
|
Beschreibung: "Verpflegung 25% (Pünktlichkeit)",
|
|
Anzahl: stmt.PuenktlichkeitspraemieAnzahl,
|
|
Betrag: 14.0,
|
|
})
|
|
}
|
|
if stmt.GesundheitsbonusAbzug == "" {
|
|
items = append(items, LineItem{
|
|
Lohnart: "1032",
|
|
Beschreibung: "Gesundheitsbonus",
|
|
Anzahl: 1,
|
|
Betrag: 200.0,
|
|
})
|
|
}
|
|
|
|
if stmt.PScheinZulage > 0 {
|
|
items = append(items, LineItem{
|
|
Lohnart: "1024",
|
|
Beschreibung: "Zulage Personenbeförd.",
|
|
Anzahl: stmt.PScheinZulage,
|
|
Betrag: 5.0,
|
|
})
|
|
}
|
|
if stmt.Wochenendzuschlag15 > 0 {
|
|
items = append(items, LineItem{
|
|
Lohnart: "1063",
|
|
Beschreibung: "Samstagszuschlag 15%",
|
|
Anzahl: stmt.Wochenendzuschlag15,
|
|
Betrag: stmt.VertragStundenlohn,
|
|
Faktor: 15,
|
|
})
|
|
}
|
|
if stmt.Sonntagszuschlag50 > 0 {
|
|
items = append(items, LineItem{
|
|
Lohnart: "0581",
|
|
Beschreibung: "Sonntagszuschlag 50%",
|
|
Anzahl: stmt.Sonntagszuschlag50,
|
|
Betrag: stmt.VertragStundenlohn,
|
|
Faktor: 50,
|
|
})
|
|
}
|
|
if stmt.Feiertagszuschlag125 > 0 {
|
|
items = append(items, LineItem{
|
|
Lohnart: "0551",
|
|
Beschreibung: "Feiertagszuschlag 125%",
|
|
Anzahl: stmt.Feiertagszuschlag125,
|
|
Betrag: stmt.VertragStundenlohn,
|
|
Faktor: 125,
|
|
})
|
|
}
|
|
if stmt.Nachtzuschlag25 > 0 {
|
|
items = append(items, LineItem{
|
|
Lohnart: "0505",
|
|
Beschreibung: "Nachtzuschl. 20:00-24:00h",
|
|
Anzahl: stmt.Nachtzuschlag25,
|
|
Betrag: stmt.VertragStundenlohn,
|
|
Faktor: 25,
|
|
})
|
|
}
|
|
if stmt.Nachtzuschlag40 > 0 {
|
|
items = append(items, LineItem{
|
|
Lohnart: "0501",
|
|
Beschreibung: "Nachtzuschl. 0:00-4:00h",
|
|
Anzahl: stmt.Nachtzuschlag40,
|
|
Betrag: stmt.VertragStundenlohn,
|
|
Faktor: 40,
|
|
})
|
|
}
|
|
|
|
slices.SortFunc(items, func(a, b LineItem) int {
|
|
return int(b.Total() - a.Total())
|
|
})
|
|
|
|
return items
|
|
}
|
|
|
|
func drawInfoBlock(pdf *fpdf.Fpdf, info StatementInfo) {
|
|
const padding = 3.0
|
|
const elementHeight = 4.0 + 6.0
|
|
|
|
x, y := pdf.GetXY()
|
|
drawLabelWithValue(pdf, x+padding, y, "Anw.-Std.", formatFloat(info.GearbeiteteStunden))
|
|
drawLabelWithValue(pdf, x+padding+18, y, "Anw.-Tage.", formatFloat(info.GearbeiteteTage))
|
|
pdf.Line(x+padding+38, y-padding, x+padding+38, y+elementHeight+padding)
|
|
drawLabelWithValue(pdf, x+padding+40, y, "Urlaub", formatFloat(float64(info.Urlaubsgeld)))
|
|
drawLabelWithValue(pdf, x+padding+55, y, "Krank.-Tage", formatFloat(float64(info.Krankheitstage)))
|
|
|
|
rectX := x
|
|
rectY := y - padding
|
|
rectWidth := 4*20 + 2*padding - 5
|
|
rectHeight := elementHeight + 2*padding
|
|
pdf.SetFillColor(180, 200, 220)
|
|
pdf.Rect(rectX, rectY, rectWidth, rectHeight, "D")
|
|
}
|
|
|
|
func drawLabelWithValue(pdf *fpdf.Fpdf, x, y float64, label, value string) {
|
|
const labelHeight = 4.0
|
|
const valueheight = 6.0
|
|
|
|
pdf.SetFont("DejaVu", "", 8)
|
|
pdf.SetTextColor(80, 100, 120)
|
|
pdf.SetXY(x, y)
|
|
pdf.Cell(20, labelHeight, label)
|
|
|
|
pdf.SetFont("DejaVu", "", 10)
|
|
pdf.SetTextColor(0, 0, 0)
|
|
pdf.SetXY(x, y+labelHeight)
|
|
pdf.Cell(20, valueheight, value)
|
|
}
|
|
|
|
func drawHeader(pdf *fpdf.Fpdf) {
|
|
pdf.SetFont("DejaVu", "B", 10)
|
|
pdf.SetFillColor(240, 240, 240)
|
|
pdf.SetX(10)
|
|
pdf.CellFormat(20, 7, "Lohnart", "1", 0, "C", true, 0, "")
|
|
pdf.SetX(30)
|
|
pdf.CellFormat(70, 7, "Bezeichnung", "1", 0, "L", true, 0, "")
|
|
pdf.SetX(100)
|
|
pdf.CellFormat(25, 7, "Menge", "1", 0, "R", true, 0, "")
|
|
pdf.SetX(125)
|
|
pdf.CellFormat(25, 7, "%-Zuschlag", "1", 0, "R", true, 0, "")
|
|
pdf.SetX(150)
|
|
pdf.CellFormat(25, 7, "Betrag", "1", 0, "R", true, 0, "")
|
|
pdf.SetX(175)
|
|
pdf.CellFormat(30, 7, "Gesamt", "1", 0, "R", true, 0, "")
|
|
pdf.Ln(7)
|
|
}
|
|
|
|
func drawRow(pdf *fpdf.Fpdf, item LineItem) {
|
|
pdf.SetFont("DejaVu", "", 10)
|
|
pdf.SetX(10)
|
|
pdf.CellFormat(20, 7, item.Lohnart, "1", 0, "C", false, 0, "")
|
|
pdf.SetX(30)
|
|
pdf.CellFormat(70, 7, item.Beschreibung, "1", 0, "L", false, 0, "")
|
|
pdf.SetX(100)
|
|
pdf.CellFormat(25, 7, formatFloat(item.Anzahl), "1", 0, "R", false, 0, "")
|
|
pdf.SetX(125)
|
|
pdf.CellFormat(25, 7, formatFloat(item.Faktor), "1", 0, "R", false, 0, "")
|
|
pdf.SetX(150)
|
|
pdf.CellFormat(25, 7, formatCurrency(item.Betrag), "1", 0, "R", false, 0, "")
|
|
pdf.SetX(175)
|
|
pdf.CellFormat(30, 7, formatCurrency(item.Total()), "1", 0, "R", false, 0, "")
|
|
pdf.Ln(7)
|
|
}
|
|
|
|
func drawSummary(pdf *fpdf.Fpdf, total float64) {
|
|
pdf.SetFont("DejaVu", "B", 11)
|
|
pdf.SetY(pdf.GetY() + 5) // Etwas Abstand
|
|
pdf.SetX(120)
|
|
pdf.CellFormat(50, 8, "Gesamtbetrag:", "T", 0, "L", false, 0, "")
|
|
pdf.SetX(170)
|
|
pdf.CellFormat(30, 8, formatCurrency(total), "T", 0, "R", false, 0, "")
|
|
}
|
|
|
|
func formatFloat(f float64) string {
|
|
p := message.NewPrinter(language.German)
|
|
str := p.Sprintf("%.2f", f)
|
|
return str //strings.Replace(str, ".", ",", 1)
|
|
}
|
|
|
|
func formatCurrency(val float64) string {
|
|
return formatFloat(val) + "€"
|
|
}
|