diff --git a/src/utils/pdf.ts b/src/utils/pdf.ts index 03e8aa5..a231a40 100644 --- a/src/utils/pdf.ts +++ b/src/utils/pdf.ts @@ -878,11 +878,10 @@ export const createTimeSheetPDF = async (server: FastifyInstance, returnMode, da let pages = [] let pageCounter = 1 - - const backgroudPdf = await PDFDocument.load(backgroundSourceBuffer) const firstPageBackground = await pdfDoc.embedPage(backgroudPdf.getPages()[0]) + // Fallback für einseitige Hintergründe const secondPageBackground = await pdfDoc.embedPage(backgroudPdf.getPages()[backgroudPdf.getPages().length > 1 ? 1 : 0]) const page1 = pdfDoc.addPage() @@ -894,121 +893,145 @@ export const createTimeSheetPDF = async (server: FastifyInstance, returnMode, da pages.push(page1) + console.log("PDF Input Data:", input) - //Falzmarke 1 - /*pages[pageCounter - 1].drawLine({ - start: getCoordinatesForPDFLib(0,105,page1), - end: getCoordinatesForPDFLib(7,105,page1), - thickness: 0.25, - color: rgb(0,0,0), - opacity: 1 - })*/ + // --------------------------------------------------------- + // DATEN-EXTRAKTION MIT FALLBACKS (Calculated vs. Standard) + // --------------------------------------------------------- - //Lochmarke - /*pages[pageCounter - 1].drawLine({ - start: getCoordinatesForPDFLib(0,148.5,page1), - end: getCoordinatesForPDFLib(7,148.5,page1), - thickness: 0.25, - color: rgb(0,0,0), - opacity: 1 - })*/ + // Summen: Bevorzuge calculated..., falls vorhanden + const sumSubmitted = input.calculatedSumWorkingMinutesSubmitted ?? input.sumWorkingMinutesSubmitted ?? 0; + const sumApproved = input.calculatedSumWorkingMinutesApproved ?? input.sumWorkingMinutesApproved ?? 0; + + // Saldi: Bevorzuge calculated... + const saldoSubmitted = input.calculatedSaldoSubmitted ?? input.saldoSubmitted ?? input.saldoInOfficial ?? 0; + const saldoApproved = input.calculatedSaldoApproved ?? input.saldoApproved ?? input.saldo ?? 0; + + // Andere Summen (diese sind meist korrekt vom Backend) + const sumRecreation = input.sumWorkingMinutesRecreationDays ?? 0; + const sumVacation = input.sumWorkingMinutesVacationDays ?? 0; + const sumSick = input.sumWorkingMinutesSickDays ?? 0; + const sumTarget = input.timeSpanWorkingMinutes ?? 0; + + // Hilfsfunktion zur Formatierung von Minuten -> HH:MM + const fmtTime = (mins) => { + const m = Math.floor(Math.abs(mins)); + return `${Math.floor(m / 60)}:${String(m % 60).padStart(2, "0")}`; + }; + + const fmtSaldo = (mins) => { + const sign = Math.sign(mins) >= 0 ? "+" : "-"; + return `${sign} ${fmtTime(mins)}`; + } + + + // --------------------------------------------------------- + // HEADER TEXTE ZEICHNEN + // --------------------------------------------------------- - //Falzmarke 2 - /*pages[pageCounter - 1].drawLine({ - start: getCoordinatesForPDFLib(0,210,page1), - end: getCoordinatesForPDFLib(7,210,page1), - thickness: 0.25, - color: rgb(0,0,0), - opacity: 1 - })*/ - console.log(input) pages[pageCounter - 1].drawText(`Anwesenheitsauswertung`,{ - x: getCoordinatesForPDFLib(20,40,pages[pageCounter -1]).x, - y: getCoordinatesForPDFLib(20,40,pages[pageCounter -1]).y, + x: getCoordinatesForPDFLib(20,60,pages[pageCounter -1]).x, + y: getCoordinatesForPDFLib(20,60,pages[pageCounter -1]).y, size: 15, font: fontBold }) - pages[pageCounter - 1].drawText(`Mitarbeiter: ${input.full_name}`,{ - x: getCoordinatesForPDFLib(20,50,pages[pageCounter -1]).x, - y: getCoordinatesForPDFLib(20,50,pages[pageCounter -1]).y, - size: 10, - }) - pages[pageCounter - 1].drawText(`Nummer: ${input.employee_number}`,{ - x: getCoordinatesForPDFLib(20,55,pages[pageCounter -1]).x, - y: getCoordinatesForPDFLib(20,55,pages[pageCounter -1]).y, - size: 10, - }) - pages[pageCounter - 1].drawText(`Eingereicht: ${Math.floor(input.sumWorkingMinutesEingereicht/60)}:${String(input.sumWorkingMinutesEingereicht % 60).padStart(2,"0")} Std`,{ - x: getCoordinatesForPDFLib(20,60,pages[pageCounter -1]).x, - y: getCoordinatesForPDFLib(20,60,pages[pageCounter -1]).y, - size: 10, - }) - pages[pageCounter - 1].drawText(`Genehmigt: ${Math.floor(input.sumWorkingMinutesApproved/60)}:${String(input.sumWorkingMinutesApproved % 60).padStart(2,"0")} Std`,{ - x: getCoordinatesForPDFLib(20,65,pages[pageCounter -1]).x, - y: getCoordinatesForPDFLib(20,65,pages[pageCounter -1]).y, - size: 10, - }) - - pages[pageCounter - 1].drawText(`Feiertagsausgleich: ${Math.floor(input.sumWorkingMinutesRecreationDays/60)}:${String(input.sumWorkingMinutesRecreationDays % 60).padStart(2,"0")} Std`,{ + pages[pageCounter - 1].drawText(`Mitarbeiter: ${input.full_name || ''}`,{ x: getCoordinatesForPDFLib(20,70,pages[pageCounter -1]).x, y: getCoordinatesForPDFLib(20,70,pages[pageCounter -1]).y, size: 10, }) - pages[pageCounter - 1].drawText(`Urlaubsausgleich: ${Math.floor(input.sumWorkingMinutesVacationDays/60)}:${String(input.sumWorkingMinutesVacationDays % 60).padStart(2,"0")} Std`,{ + pages[pageCounter - 1].drawText(`Nummer: ${input.employee_number || '-'}`,{ x: getCoordinatesForPDFLib(20,75,pages[pageCounter -1]).x, y: getCoordinatesForPDFLib(20,75,pages[pageCounter -1]).y, size: 10, }) - pages[pageCounter - 1].drawText(`Krankheitsausgleich: ${Math.floor(input.sumWorkingMinutesSickDays/60)}:${String(input.sumWorkingMinutesSickDays % 60).padStart(2,"0")} Std`,{ + + // Zeile 1: Eingereicht & Genehmigt + pages[pageCounter - 1].drawText(`Eingereicht: ${fmtTime(sumSubmitted)} Std`,{ x: getCoordinatesForPDFLib(20,80,pages[pageCounter -1]).x, y: getCoordinatesForPDFLib(20,80,pages[pageCounter -1]).y, size: 10, }) - pages[pageCounter - 1].drawText(`Soll Stunden: ${Math.floor(input.timeSpanWorkingMinutes/60)}:${Math.floor(Number(String(input.timeSpanWorkingMinutes % 60).padStart(2,"0")))} Std`,{ + pages[pageCounter - 1].drawText(`Genehmigt: ${fmtTime(sumApproved)} Std`,{ x: getCoordinatesForPDFLib(20,85,pages[pageCounter -1]).x, y: getCoordinatesForPDFLib(20,85,pages[pageCounter -1]).y, size: 10, }) - pages[pageCounter - 1].drawText(`Inoffizielles Saldo: ${Math.sign(input.saldoInOfficial) === 1 ? "+" : "-"} ${Math.floor(Math.abs(input.saldoInOfficial/60))}:${Math.floor(Number(String(Math.abs(input.saldoInOfficial) % 60).padStart(2,"0")))} Std`,{ + + // Zeile 2: Ausgleichstage + pages[pageCounter - 1].drawText(`Feiertagsausgleich: ${fmtTime(sumRecreation)} Std`,{ x: getCoordinatesForPDFLib(20,90,pages[pageCounter -1]).x, y: getCoordinatesForPDFLib(20,90,pages[pageCounter -1]).y, size: 10, }) - pages[pageCounter - 1].drawText(`Saldo: ${Math.sign(input.saldo) === 1 ? "+" : "-"} ${Math.floor(Math.abs(input.saldo/60))}:${Math.floor(Number(String(Math.abs(input.saldo) % 60).padStart(2,"0")))} Std`,{ + pages[pageCounter - 1].drawText(`Urlaubsausgleich: ${fmtTime(sumVacation)} Std`,{ x: getCoordinatesForPDFLib(20,95,pages[pageCounter -1]).x, y: getCoordinatesForPDFLib(20,95,pages[pageCounter -1]).y, size: 10, }) - - pages[pageCounter - 1].drawText(`Start:`,{ + pages[pageCounter - 1].drawText(`Krankheitsausgleich: ${fmtTime(sumSick)} Std`,{ x: getCoordinatesForPDFLib(20,100,pages[pageCounter -1]).x, y: getCoordinatesForPDFLib(20,100,pages[pageCounter -1]).y, size: 10, }) + // Zeile 3: Soll & Saldo + pages[pageCounter - 1].drawText(`Soll Stunden: ${fmtTime(sumTarget)} Std`,{ + x: getCoordinatesForPDFLib(20,105,pages[pageCounter -1]).x, + y: getCoordinatesForPDFLib(20,105,pages[pageCounter -1]).y, + size: 10, + }) + + // Wir nutzen hier die Begriffe "Inoffiziell" (Submitted Saldo) und "Saldo" (Approved Saldo) + pages[pageCounter - 1].drawText(`Inoffizielles Saldo: ${fmtSaldo(saldoSubmitted)} Std`,{ + x: getCoordinatesForPDFLib(20,110,pages[pageCounter -1]).x, + y: getCoordinatesForPDFLib(20,110,pages[pageCounter -1]).y, + size: 10, + }) + pages[pageCounter - 1].drawText(`Saldo: ${fmtSaldo(saldoApproved)} Std`,{ + x: getCoordinatesForPDFLib(20,115,pages[pageCounter -1]).x, + y: getCoordinatesForPDFLib(20,115,pages[pageCounter -1]).y, + size: 10, + }) + + // Tabellen-Header + pages[pageCounter - 1].drawText(`Start:`,{ + x: getCoordinatesForPDFLib(20,125,pages[pageCounter -1]).x, + y: getCoordinatesForPDFLib(20,125,pages[pageCounter -1]).y, + size: 10, + }) + pages[pageCounter - 1].drawText(`Ende:`,{ - x: getCoordinatesForPDFLib(60,100,pages[pageCounter -1]).x, - y: getCoordinatesForPDFLib(60,100,pages[pageCounter -1]).y, + x: getCoordinatesForPDFLib(60,125,pages[pageCounter -1]).x, + y: getCoordinatesForPDFLib(60,125,pages[pageCounter -1]).y, size: 10, }) pages[pageCounter - 1].drawText(`Dauer:`,{ - x: getCoordinatesForPDFLib(100,100,pages[pageCounter -1]).x, - y: getCoordinatesForPDFLib(100,100,pages[pageCounter -1]).y, + x: getCoordinatesForPDFLib(100,125,pages[pageCounter -1]).x, + y: getCoordinatesForPDFLib(100,125,pages[pageCounter -1]).y, size: 10, }) + // --------------------------------------------------------- + // TABELLE GENERIEREN (Spans verarbeiten) + // --------------------------------------------------------- - let rowHeight = 115 + let rowHeight = 130 + // WICHTIG: input.spans verwenden, fallback auf input.times (altes Format) + // Wir filtern leere Einträge raus + const rawItems = (input.spans || input.times || []).filter(t => t); + + // Sortierung umkehren (neueste zuletzt für den Druck? Oder wie gewünscht) + // Im Original war es .reverse(). + let reversedInput = rawItems.slice().reverse(); let splitted = [] - - let reversedInput = input.times.slice().reverse() - const splittedLength = Math.floor((reversedInput.length - 25) / 40) + // Erste Seite hat weniger Platz wegen Header (25 Zeilen) splitted.push(reversedInput.slice(0,25)) let lastIndex = 25 @@ -1017,10 +1040,11 @@ export const createTimeSheetPDF = async (server: FastifyInstance, returnMode, da lastIndex = lastIndex + (i + 1) * 40 + 1 } - if(reversedInput.slice(lastIndex, reversedInput.length).length > 0) splitted.push(reversedInput.slice(lastIndex, reversedInput.length)) - - console.log(splitted ) + if(reversedInput.slice(lastIndex, reversedInput.length).length > 0) { + splitted.push(reversedInput.slice(lastIndex, reversedInput.length)) + } + console.log("PDF Pages Chunks:", splitted.length) splitted.forEach((chunk,index) => { if(index > 0) { @@ -1034,30 +1058,50 @@ export const createTimeSheetPDF = async (server: FastifyInstance, returnMode, da pages.push(page) pageCounter++ rowHeight = 20 - } - chunk.forEach(time => { - pages[pageCounter - 1].drawText(`${dayjs(time.started_at).format("HH:mm DD.MM.YY")}`,{ + chunk.forEach(span => { + // Mapping für Felder: spans nutzen 'startedAt', times nutzten 'started_at' + const startStr = span.startedAt || span.started_at; + const endStr = span.endedAt || span.stopped_at; // endedAt oder stopped_at + + // Dauer berechnen (da Spans keine duration_minutes haben) + let durationStr = ""; + if (startStr && endStr) { + const diffMins = dayjs(endStr).diff(dayjs(startStr), 'minute'); + durationStr = fmtTime(diffMins); + } else if (span.duration_minutes) { + durationStr = fmtTime(span.duration_minutes); + } else if (span.duration) { // Falls schon formatiert übergeben + durationStr = span.duration; + } + + pages[pageCounter - 1].drawText(`${dayjs(startStr).format("HH:mm DD.MM.YY")}`,{ x: getCoordinatesForPDFLib(20,rowHeight,pages[pageCounter -1]).x, y: getCoordinatesForPDFLib(20,rowHeight,pages[pageCounter -1]).y, size: 10, }) - pages[pageCounter - 1].drawText(`${dayjs(time.stopped_at).format("HH:mm DD.MM.YY")}`,{ + pages[pageCounter - 1].drawText(`${endStr ? dayjs(endStr).format("HH:mm DD.MM.YY") : 'läuft...'}`,{ x: getCoordinatesForPDFLib(60,rowHeight,pages[pageCounter -1]).x, y: getCoordinatesForPDFLib(60,rowHeight,pages[pageCounter -1]).y, size: 10, }) - pages[pageCounter - 1].drawText(`${getDuration(time).composed}`,{ + pages[pageCounter - 1].drawText(`${durationStr}`,{ x: getCoordinatesForPDFLib(100,rowHeight,pages[pageCounter -1]).x, y: getCoordinatesForPDFLib(100,rowHeight,pages[pageCounter -1]).y, size: 10, }) - rowHeight += 6 + // Optional: Status anzeigen? + /*pages[pageCounter - 1].drawText(`${span.status || span.state || ''}`,{ + x: getCoordinatesForPDFLib(130,rowHeight,pages[pageCounter -1]).x, + y: getCoordinatesForPDFLib(130,rowHeight,pages[pageCounter -1]).y, + size: 8, + })*/ + rowHeight += 6 }) }) @@ -1077,8 +1121,6 @@ export const createTimeSheetPDF = async (server: FastifyInstance, returnMode, da } } catch(error) { console.log(error) + throw error; // Fehler weiterwerfen, damit er oben ankommt } - - - } \ No newline at end of file