Massdownload
This commit is contained in:
@@ -268,4 +268,211 @@ function generatePDF(timesheetId, req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { generatePDF };
|
||||
// PDF als Buffer generieren (für ZIP-Downloads)
|
||||
function generatePDFToBuffer(timesheetId, req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(`SELECT wt.*, u.firstname, u.lastname, u.username, u.wochenstunden
|
||||
FROM weekly_timesheets wt
|
||||
JOIN users u ON wt.user_id = u.id
|
||||
WHERE wt.id = ?`, [timesheetId], (err, timesheet) => {
|
||||
|
||||
if (err || !timesheet) {
|
||||
return reject(new Error('Stundenzettel nicht gefunden'));
|
||||
}
|
||||
|
||||
// Hole Einträge die zum Zeitpunkt der Einreichung existierten
|
||||
db.all(`SELECT * FROM timesheet_entries
|
||||
WHERE user_id = ? AND date >= ? AND date <= ?
|
||||
AND (
|
||||
(updated_at IS NOT NULL AND updated_at <= ?) OR
|
||||
(updated_at IS NULL AND created_at IS NOT NULL AND created_at <= ?) OR
|
||||
(updated_at IS NULL AND created_at IS NULL)
|
||||
)
|
||||
ORDER BY date, updated_at DESC, id DESC`,
|
||||
[timesheet.user_id, timesheet.week_start, timesheet.week_end,
|
||||
timesheet.submitted_at, timesheet.submitted_at],
|
||||
(err, allEntries) => {
|
||||
if (err) {
|
||||
return reject(new Error('Fehler beim Abrufen der Einträge'));
|
||||
}
|
||||
|
||||
// Filtere auf neuesten Eintrag pro Tag
|
||||
const entriesByDate = {};
|
||||
(allEntries || []).forEach(entry => {
|
||||
const existing = entriesByDate[entry.date];
|
||||
if (!existing) {
|
||||
entriesByDate[entry.date] = entry;
|
||||
} else {
|
||||
const existingTime = existing.updated_at ? new Date(existing.updated_at).getTime() : 0;
|
||||
const currentTime = entry.updated_at ? new Date(entry.updated_at).getTime() : 0;
|
||||
if (currentTime > existingTime || (currentTime === existingTime && entry.id > existing.id)) {
|
||||
entriesByDate[entry.date] = entry;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const entries = Object.values(entriesByDate).sort((a, b) => {
|
||||
return new Date(a.date) - new Date(b.date);
|
||||
});
|
||||
|
||||
const doc = new PDFDocument({ margin: 50 });
|
||||
const buffers = [];
|
||||
|
||||
doc.on('data', buffers.push.bind(buffers));
|
||||
doc.on('end', () => {
|
||||
const pdfBuffer = Buffer.concat(buffers);
|
||||
resolve(pdfBuffer);
|
||||
});
|
||||
doc.on('error', reject);
|
||||
|
||||
// Header
|
||||
const calendarWeek = getCalendarWeek(timesheet.week_start);
|
||||
doc.fontSize(20).text(`Stundenzettel für KW ${calendarWeek}`, { align: 'center' });
|
||||
doc.moveDown();
|
||||
|
||||
// Mitarbeiter-Info
|
||||
doc.fontSize(12);
|
||||
doc.text(`Mitarbeiter: ${timesheet.firstname} ${timesheet.lastname}`);
|
||||
doc.text(`Zeitraum: ${formatDate(timesheet.week_start)} - ${formatDate(timesheet.week_end)}`);
|
||||
doc.text(`Eingereicht am: ${formatDateTime(timesheet.submitted_at)}`);
|
||||
doc.moveDown();
|
||||
|
||||
// Tabelle - Basis-Informationen
|
||||
const tableTop = doc.y;
|
||||
const colWidths = [80, 80, 80, 60, 80];
|
||||
const headers = ['Datum', 'Start', 'Ende', 'Pause', 'Stunden'];
|
||||
|
||||
// Tabellen-Header
|
||||
doc.fontSize(10).font('Helvetica-Bold');
|
||||
let x = 50;
|
||||
headers.forEach((header, i) => {
|
||||
doc.text(header, x, tableTop, { width: colWidths[i], align: 'left' });
|
||||
x += colWidths[i];
|
||||
});
|
||||
|
||||
doc.moveDown();
|
||||
let y = doc.y;
|
||||
doc.moveTo(50, y).lineTo(430, y).stroke();
|
||||
doc.moveDown(0.5);
|
||||
|
||||
// Tabellen-Daten
|
||||
doc.font('Helvetica');
|
||||
let totalHours = 0;
|
||||
let vacationHours = 0;
|
||||
|
||||
entries.forEach((entry) => {
|
||||
y = doc.y;
|
||||
x = 50;
|
||||
|
||||
const rowData = [
|
||||
formatDate(entry.date),
|
||||
entry.start_time || '-',
|
||||
entry.end_time || '-',
|
||||
entry.break_minutes ? `${entry.break_minutes} min` : '-',
|
||||
entry.total_hours ? entry.total_hours.toFixed(2) + ' h' : '-'
|
||||
];
|
||||
|
||||
rowData.forEach((data, i) => {
|
||||
doc.text(data, x, y, { width: colWidths[i], align: 'left' });
|
||||
x += colWidths[i];
|
||||
});
|
||||
|
||||
// Tätigkeiten sammeln
|
||||
const activities = [];
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const desc = entry[`activity${i}_desc`];
|
||||
const hours = entry[`activity${i}_hours`];
|
||||
const projectNumber = entry[`activity${i}_project_number`];
|
||||
if (desc && desc.trim() && hours > 0) {
|
||||
activities.push({
|
||||
desc: desc.trim(),
|
||||
hours: parseFloat(hours),
|
||||
projectNumber: projectNumber ? projectNumber.trim() : null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Tätigkeiten anzeigen
|
||||
if (activities.length > 0) {
|
||||
doc.moveDown(0.3);
|
||||
doc.fontSize(9).font('Helvetica-Oblique');
|
||||
doc.text('Tätigkeiten:', 60, doc.y, { width: 380 });
|
||||
doc.moveDown(0.2);
|
||||
|
||||
activities.forEach((activity, idx) => {
|
||||
let activityText = `${idx + 1}. ${activity.desc}`;
|
||||
if (activity.projectNumber) {
|
||||
activityText += ` (Projekt: ${activity.projectNumber})`;
|
||||
}
|
||||
activityText += ` - ${activity.hours.toFixed(2)} h`;
|
||||
doc.fontSize(9).font('Helvetica');
|
||||
doc.text(activityText, 70, doc.y, { width: 360 });
|
||||
doc.moveDown(0.2);
|
||||
});
|
||||
doc.fontSize(10);
|
||||
}
|
||||
|
||||
// Überstunden und Urlaub anzeigen
|
||||
const overtimeInfo = [];
|
||||
if (entry.overtime_taken_hours && parseFloat(entry.overtime_taken_hours) > 0) {
|
||||
overtimeInfo.push(`Überstunden genommen: ${parseFloat(entry.overtime_taken_hours).toFixed(2)} h`);
|
||||
}
|
||||
if (entry.vacation_type) {
|
||||
const vacationText = entry.vacation_type === 'full' ? 'Ganzer Tag' : 'Halber Tag';
|
||||
overtimeInfo.push(`Urlaub: ${vacationText}`);
|
||||
}
|
||||
|
||||
if (overtimeInfo.length > 0) {
|
||||
doc.moveDown(0.2);
|
||||
doc.fontSize(9).font('Helvetica-Oblique');
|
||||
overtimeInfo.forEach((info, idx) => {
|
||||
doc.text(info, 70, doc.y, { width: 360 });
|
||||
doc.moveDown(0.15);
|
||||
});
|
||||
doc.fontSize(10);
|
||||
}
|
||||
|
||||
if (entry.total_hours) {
|
||||
totalHours += entry.total_hours;
|
||||
}
|
||||
|
||||
if (entry.vacation_type === 'full') {
|
||||
vacationHours += 8;
|
||||
} else if (entry.vacation_type === 'half') {
|
||||
vacationHours += 4;
|
||||
}
|
||||
|
||||
doc.moveDown(0.5);
|
||||
|
||||
y = doc.y;
|
||||
doc.moveTo(50, y).lineTo(430, y).stroke();
|
||||
doc.moveDown(0.3);
|
||||
});
|
||||
|
||||
// Summe
|
||||
y = doc.y;
|
||||
doc.moveTo(50, y).lineTo(550, y).stroke();
|
||||
doc.moveDown(0.5);
|
||||
doc.font('Helvetica-Bold');
|
||||
doc.text(`Gesamtstunden: ${totalHours.toFixed(2)} h`, 50, doc.y);
|
||||
|
||||
const wochenstunden = timesheet.wochenstunden || 0;
|
||||
const overtimeHours = totalHours - wochenstunden;
|
||||
|
||||
doc.moveDown(0.3);
|
||||
doc.font('Helvetica-Bold');
|
||||
if (overtimeHours > 0) {
|
||||
doc.text(`Überstunden: +${overtimeHours.toFixed(2)} h`, 50, doc.y);
|
||||
} else if (overtimeHours < 0) {
|
||||
doc.text(`Überstunden: ${overtimeHours.toFixed(2)} h`, 50, doc.y);
|
||||
} else {
|
||||
doc.text(`Überstunden: 0.00 h`, 50, doc.y);
|
||||
}
|
||||
|
||||
doc.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { generatePDF, generatePDFToBuffer };
|
||||
|
||||
Reference in New Issue
Block a user