561 lines
20 KiB
JavaScript
561 lines
20 KiB
JavaScript
const express = require('express');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const { authenticateToken } = require('../middleware/auth');
|
||
|
||
const router = express.Router();
|
||
|
||
// Parse CSV data
|
||
const parseCSV = () => {
|
||
try {
|
||
const csvPath = path.join(__dirname, '../../data.csv');
|
||
const csvData = fs.readFileSync(csvPath, 'utf8');
|
||
const lines = csvData.split('\n');
|
||
const headers = lines[0].split(';').map(h => h.replace(/"/g, ''));
|
||
|
||
const transactions = [];
|
||
for (let i = 1; i < lines.length; i++) {
|
||
const line = lines[i];
|
||
if (!line.trim()) continue;
|
||
|
||
// Parse CSV line (handle semicolon-separated values with quotes)
|
||
const values = [];
|
||
let current = '';
|
||
let inQuotes = false;
|
||
|
||
for (let j = 0; j < line.length; j++) {
|
||
const char = line[j];
|
||
if (char === '"') {
|
||
inQuotes = !inQuotes;
|
||
} else if (char === ';' && !inQuotes) {
|
||
values.push(current);
|
||
current = '';
|
||
} else {
|
||
current += char;
|
||
}
|
||
}
|
||
values.push(current); // Add last value
|
||
|
||
if (values.length >= headers.length) {
|
||
const transaction = {};
|
||
headers.forEach((header, index) => {
|
||
transaction[header] = values[index] || '';
|
||
});
|
||
|
||
// Parse date and amount
|
||
if (transaction['Buchungstag']) {
|
||
const dateParts = transaction['Buchungstag'].split('.');
|
||
if (dateParts.length === 3) {
|
||
// Convert DD.MM.YY to proper date
|
||
const day = dateParts[0];
|
||
const month = dateParts[1];
|
||
const year = '20' + dateParts[2]; // Assuming 20xx
|
||
transaction.parsedDate = new Date(year, month - 1, day);
|
||
transaction.monthYear = `${year}-${month.padStart(2, '0')}`;
|
||
}
|
||
}
|
||
|
||
// Parse amount
|
||
if (transaction['Betrag']) {
|
||
const amount = transaction['Betrag'].replace(',', '.').replace(/[^-0-9.]/g, '');
|
||
transaction.numericAmount = parseFloat(amount) || 0;
|
||
}
|
||
|
||
transactions.push(transaction);
|
||
}
|
||
}
|
||
|
||
return transactions;
|
||
} catch (error) {
|
||
console.error('Error parsing CSV:', error);
|
||
return [];
|
||
}
|
||
};
|
||
|
||
// Get available months
|
||
router.get('/months', authenticateToken, (req, res) => {
|
||
try {
|
||
const transactions = parseCSV();
|
||
const months = [...new Set(transactions
|
||
.filter(t => t.monthYear)
|
||
.map(t => t.monthYear)
|
||
)].sort().reverse(); // Newest first
|
||
|
||
res.json({ months });
|
||
} catch (error) {
|
||
console.error('Error getting months:', error);
|
||
res.status(500).json({ error: 'Failed to load months' });
|
||
}
|
||
});
|
||
|
||
// Get database transactions for JTL comparison
|
||
const getJTLTransactions = async () => {
|
||
try {
|
||
const { executeQuery } = require('../config/database');
|
||
const query = `
|
||
SELECT
|
||
cKonto, cKontozusatz, cName, dBuchungsdatum,
|
||
tZahlungsabgleichUmsatz.kZahlungsabgleichUmsatz,
|
||
cVerwendungszweck, fBetrag, tUmsatzKontierung.data
|
||
FROM [eazybusiness].[dbo].[tZahlungsabgleichUmsatz]
|
||
LEFT JOIN tUmsatzKontierung ON (tUmsatzKontierung.kZahlungsabgleichUmsatz = tZahlungsabgleichUmsatz.kZahlungsabgleichUmsatz)
|
||
ORDER BY dBuchungsdatum desc, tZahlungsabgleichUmsatz.kZahlungsabgleichUmsatz desc
|
||
`;
|
||
|
||
const result = await executeQuery(query);
|
||
const transactions = result.recordset || [];
|
||
|
||
// Get PDF documents for each transaction
|
||
const pdfQuery = `SELECT kUmsatzBeleg, kZahlungsabgleichUmsatz, textContent, markDown, extraction, datevlink FROM tUmsatzBeleg`;
|
||
const pdfResult = await executeQuery(pdfQuery);
|
||
|
||
for(const item of pdfResult.recordset){
|
||
for(const transaction of transactions){
|
||
if(item.kZahlungsabgleichUmsatz == transaction.kZahlungsabgleichUmsatz){
|
||
if(!transaction.pdfs) transaction.pdfs = [];
|
||
transaction.pdfs.push({
|
||
kUmsatzBeleg: item.kUmsatzBeleg,
|
||
content: item.textContent,
|
||
markDown: item.markDown,
|
||
extraction: item.extraction,
|
||
datevlink: item.datevlink
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// Get links for each transaction
|
||
const linksQuery = `
|
||
SELECT kZahlungsabgleichUmsatzLink, kZahlungsabgleichUmsatz, linktarget, linktype, note,
|
||
tPdfObjekt.kPdfObjekt, tPdfObjekt.textContent, tPdfObjekt.markDown,
|
||
tPdfObjekt.extraction
|
||
FROM tZahlungsabgleichUmsatzLink
|
||
LEFT JOIN tPdfObjekt ON (tZahlungsabgleichUmsatzLink.linktarget = tPdfObjekt.kLieferantenbestellung)
|
||
WHERE linktype = 'kLieferantenBestellung'
|
||
`;
|
||
const linksResult = await executeQuery(linksQuery);
|
||
|
||
for(const item of linksResult.recordset){
|
||
for(const transaction of transactions){
|
||
if(item.kZahlungsabgleichUmsatz == transaction.kZahlungsabgleichUmsatz){
|
||
if(!transaction.links) transaction.links = [];
|
||
transaction.links.push(item);
|
||
}
|
||
}
|
||
}
|
||
|
||
return transactions;
|
||
} catch (error) {
|
||
console.error('Error fetching JTL transactions:', error);
|
||
throw error; // Re-throw the error so the caller knows the database is unavailable
|
||
}
|
||
};
|
||
|
||
// Get transactions for a specific time period (month, quarter, or year)
|
||
router.get('/transactions/:timeRange', authenticateToken, async (req, res) => {
|
||
try {
|
||
const { timeRange } = req.params;
|
||
const transactions = parseCSV();
|
||
|
||
let filteredTransactions = [];
|
||
let periodDescription = '';
|
||
|
||
if (timeRange.includes('-Q')) {
|
||
// Quarter format: YYYY-Q1, YYYY-Q2, etc.
|
||
const [year, quarterPart] = timeRange.split('-Q');
|
||
const quarter = parseInt(quarterPart);
|
||
const startMonth = (quarter - 1) * 3 + 1;
|
||
const endMonth = startMonth + 2;
|
||
|
||
filteredTransactions = transactions.filter(t => {
|
||
if (!t.monthYear) return false;
|
||
const [tYear, tMonth] = t.monthYear.split('-');
|
||
const monthNum = parseInt(tMonth);
|
||
return tYear === year && monthNum >= startMonth && monthNum <= endMonth;
|
||
});
|
||
|
||
periodDescription = `Q${quarter} ${year}`;
|
||
} else if (timeRange.length === 4) {
|
||
// Year format: YYYY
|
||
filteredTransactions = transactions.filter(t => {
|
||
if (!t.monthYear) return false;
|
||
const [tYear] = t.monthYear.split('-');
|
||
return tYear === timeRange;
|
||
});
|
||
|
||
periodDescription = `Jahr ${timeRange}`;
|
||
} else {
|
||
// Month format: YYYY-MM
|
||
filteredTransactions = transactions.filter(t => t.monthYear === timeRange);
|
||
const [year, month] = timeRange.split('-');
|
||
const date = new Date(year, month - 1);
|
||
periodDescription = date.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' });
|
||
}
|
||
|
||
const monthTransactions = filteredTransactions
|
||
.sort((a, b) => b.parsedDate - a.parsedDate); // Newest first
|
||
|
||
// Get JTL transactions for comparison
|
||
let jtlTransactions = [];
|
||
let jtlDatabaseAvailable = false;
|
||
try {
|
||
jtlTransactions = await getJTLTransactions();
|
||
jtlDatabaseAvailable = true;
|
||
} catch (error) {
|
||
console.log('JTL database not available, continuing without JTL data');
|
||
jtlDatabaseAvailable = false;
|
||
}
|
||
|
||
// Filter JTL transactions for the selected time period
|
||
let jtlMonthTransactions = [];
|
||
|
||
if (timeRange.includes('-Q')) {
|
||
const [year, quarterPart] = timeRange.split('-Q');
|
||
const quarter = parseInt(quarterPart);
|
||
const startMonth = (quarter - 1) * 3 + 1;
|
||
const endMonth = startMonth + 2;
|
||
|
||
jtlMonthTransactions = jtlTransactions.filter(jtl => {
|
||
const jtlDate = new Date(jtl.dBuchungsdatum);
|
||
const jtlMonth = jtlDate.getMonth() + 1; // 0-based to 1-based
|
||
return jtlDate.getFullYear() === parseInt(year) &&
|
||
jtlMonth >= startMonth && jtlMonth <= endMonth;
|
||
});
|
||
} else if (timeRange.length === 4) {
|
||
jtlMonthTransactions = jtlTransactions.filter(jtl => {
|
||
const jtlDate = new Date(jtl.dBuchungsdatum);
|
||
return jtlDate.getFullYear() === parseInt(timeRange);
|
||
});
|
||
} else {
|
||
const [year, month] = timeRange.split('-');
|
||
jtlMonthTransactions = jtlTransactions.filter(jtl => {
|
||
const jtlDate = new Date(jtl.dBuchungsdatum);
|
||
return jtlDate.getFullYear() === parseInt(year) &&
|
||
jtlDate.getMonth() === parseInt(month) - 1;
|
||
});
|
||
}
|
||
|
||
// Add JTL status to each CSV transaction
|
||
const transactionsWithJTL = monthTransactions.map(transaction => {
|
||
// Try to match by amount and date (approximate matching)
|
||
const amount = transaction.numericAmount;
|
||
const transactionDate = transaction.parsedDate;
|
||
|
||
const jtlMatch = jtlMonthTransactions.find(jtl => {
|
||
const jtlAmount = parseFloat(jtl.fBetrag) || 0;
|
||
const jtlDate = new Date(jtl.dBuchungsdatum);
|
||
|
||
// Match by amount (exact) and date (same day)
|
||
const amountMatch = Math.abs(amount - jtlAmount) < 0.01;
|
||
const dateMatch = transactionDate && jtlDate &&
|
||
transactionDate.getFullYear() === jtlDate.getFullYear() &&
|
||
transactionDate.getMonth() === jtlDate.getMonth() &&
|
||
transactionDate.getDate() === jtlDate.getDate();
|
||
|
||
return amountMatch && dateMatch;
|
||
});
|
||
|
||
return {
|
||
...transaction,
|
||
hasJTL: jtlDatabaseAvailable ? !!jtlMatch : undefined,
|
||
jtlId: jtlMatch ? jtlMatch.kZahlungsabgleichUmsatz : null,
|
||
isFromCSV: true,
|
||
jtlDatabaseAvailable,
|
||
// Include document data from JTL match
|
||
pdfs: jtlMatch ? jtlMatch.pdfs || [] : [],
|
||
links: jtlMatch ? jtlMatch.links || [] : []
|
||
};
|
||
});
|
||
|
||
// Find JTL transactions that don't have CSV matches (red rows)
|
||
const unmatchedJTLTransactions = jtlMonthTransactions
|
||
.filter(jtl => {
|
||
const jtlAmount = parseFloat(jtl.fBetrag) || 0;
|
||
const jtlDate = new Date(jtl.dBuchungsdatum);
|
||
|
||
// Check if this JTL transaction has a CSV match
|
||
const hasCSVMatch = monthTransactions.some(transaction => {
|
||
const amount = transaction.numericAmount;
|
||
const transactionDate = transaction.parsedDate;
|
||
|
||
const amountMatch = Math.abs(amount - jtlAmount) < 0.01;
|
||
const dateMatch = transactionDate && jtlDate &&
|
||
transactionDate.getFullYear() === jtlDate.getFullYear() &&
|
||
transactionDate.getMonth() === jtlDate.getMonth() &&
|
||
transactionDate.getDate() === jtlDate.getDate();
|
||
|
||
return amountMatch && dateMatch;
|
||
});
|
||
|
||
return !hasCSVMatch;
|
||
})
|
||
.map(jtl => ({
|
||
// Convert JTL format to CSV-like format for display
|
||
'Buchungstag': new Date(jtl.dBuchungsdatum).toLocaleDateString('de-DE', {
|
||
day: '2-digit',
|
||
month: '2-digit',
|
||
year: '2-digit'
|
||
}),
|
||
'Verwendungszweck': jtl.cVerwendungszweck || '',
|
||
'Buchungstext': 'JTL Transaction',
|
||
'Beguenstigter/Zahlungspflichtiger': jtl.cName || '',
|
||
'Betrag': jtl.fBetrag ? jtl.fBetrag.toString().replace('.', ',') : '0,00',
|
||
numericAmount: parseFloat(jtl.fBetrag) || 0,
|
||
parsedDate: new Date(jtl.dBuchungsdatum),
|
||
monthYear: timeRange,
|
||
hasJTL: true,
|
||
jtlId: jtl.kZahlungsabgleichUmsatz,
|
||
isFromCSV: false,
|
||
isJTLOnly: true,
|
||
// Include document data from JTL transaction
|
||
pdfs: jtl.pdfs || [],
|
||
links: jtl.links || []
|
||
}));
|
||
|
||
// Combine CSV and JTL-only transactions
|
||
const allTransactions = [...transactionsWithJTL, ...unmatchedJTLTransactions]
|
||
.sort((a, b) => b.parsedDate - a.parsedDate);
|
||
|
||
// Calculate summary
|
||
const summary = {
|
||
totalTransactions: allTransactions.length,
|
||
totalIncome: allTransactions
|
||
.filter(t => t.numericAmount > 0)
|
||
.reduce((sum, t) => sum + t.numericAmount, 0),
|
||
totalExpenses: allTransactions
|
||
.filter(t => t.numericAmount < 0)
|
||
.reduce((sum, t) => sum + Math.abs(t.numericAmount), 0),
|
||
netAmount: allTransactions.reduce((sum, t) => sum + t.numericAmount, 0),
|
||
jtlMatches: jtlDatabaseAvailable ? allTransactions.filter(t => t.hasJTL === true && t.isFromCSV).length : undefined,
|
||
jtlMissing: jtlDatabaseAvailable ? allTransactions.filter(t => t.hasJTL === false && t.isFromCSV).length : undefined,
|
||
jtlOnly: jtlDatabaseAvailable ? allTransactions.filter(t => t.isJTLOnly).length : undefined,
|
||
csvOnly: jtlDatabaseAvailable ? allTransactions.filter(t => t.hasJTL === false && t.isFromCSV).length : undefined,
|
||
jtlDatabaseAvailable
|
||
};
|
||
|
||
res.json({
|
||
transactions: allTransactions,
|
||
summary,
|
||
timeRange,
|
||
periodDescription
|
||
});
|
||
} catch (error) {
|
||
console.error('Error getting transactions:', error);
|
||
res.status(500).json({ error: 'Failed to load transactions' });
|
||
}
|
||
});
|
||
|
||
// DATEV export functionality
|
||
const buildDatevHeader = (periodStart, periodEnd) => {
|
||
const ts = new Date().toISOString().replace(/[-T:\.Z]/g, '').slice(0, 17); // yyyymmddHHMMSSfff
|
||
const meta = {
|
||
consultant: 1001,
|
||
client: 10001,
|
||
fyStart: periodStart.slice(0, 4) + '0101', // fiscal year start
|
||
accLength: 4,
|
||
description: 'Bank Statement Export',
|
||
currency: 'EUR'
|
||
};
|
||
|
||
return [
|
||
'"EXTF"', 700, 21, '"Buchungsstapel"', 12, ts,
|
||
'', '', '', '', // 7‑10 spare
|
||
meta.consultant, meta.client, // 11, 12
|
||
meta.fyStart, meta.accLength, // 13, 14
|
||
periodStart, periodEnd, // 15, 16
|
||
'"' + meta.description + '"',
|
||
'AM', 1, 0, 1, meta.currency
|
||
].join(';');
|
||
};
|
||
|
||
const DATEV_COLS = [
|
||
'Umsatz (ohne Soll/Haben-Kz)', 'Soll/Haben-Kennzeichen', 'WKZ Umsatz',
|
||
'Kurs', 'Basis-Umsatz', 'WKZ Basis-Umsatz', 'Konto',
|
||
'Gegenkonto (ohne BU-Schlüssel)', 'BU-Schlüssel', 'Belegdatum',
|
||
'Belegfeld 1', 'Belegfeld 2', 'Skonto', 'Buchungstext',
|
||
'Postensperre', 'Diverse Adressnummer', 'Geschäftspartnerbank',
|
||
'Sachverhalt', 'Zinssperre', 'Beleglink'
|
||
].join(';');
|
||
|
||
const formatDatevAmount = (amount) => {
|
||
return Math.abs(amount).toFixed(2).replace('.', ',');
|
||
};
|
||
|
||
const formatDatevDate = (dateString) => {
|
||
if (!dateString) return '';
|
||
const parts = dateString.split('.');
|
||
if (parts.length === 3) {
|
||
const day = parts[0].padStart(2, '0');
|
||
const month = parts[1].padStart(2, '0');
|
||
return day + month;
|
||
}
|
||
return '';
|
||
};
|
||
|
||
const quote = (str, maxLen = 60) => {
|
||
if (!str) return '""';
|
||
return '"' + str.slice(0, maxLen).replace(/"/g, '""') + '"';
|
||
};
|
||
|
||
// DATEV export endpoint
|
||
router.get('/datev/:timeRange', authenticateToken, async (req, res) => {
|
||
try {
|
||
const { timeRange } = req.params;
|
||
|
||
// Get transactions for the time period
|
||
const transactions = parseCSV();
|
||
let filteredTransactions = [];
|
||
let periodStart, periodEnd, filename;
|
||
|
||
if (timeRange.includes('-Q')) {
|
||
// Quarter format: YYYY-Q1, YYYY-Q2, etc.
|
||
const [year, quarterPart] = timeRange.split('-Q');
|
||
const quarter = parseInt(quarterPart);
|
||
const startMonth = (quarter - 1) * 3 + 1;
|
||
const endMonth = startMonth + 2;
|
||
|
||
filteredTransactions = transactions.filter(t => {
|
||
if (!t.monthYear) return false;
|
||
const [tYear, tMonth] = t.monthYear.split('-');
|
||
const monthNum = parseInt(tMonth);
|
||
return tYear === year && monthNum >= startMonth && monthNum <= endMonth;
|
||
});
|
||
|
||
periodStart = `${year}${startMonth.toString().padStart(2, '0')}01`;
|
||
periodEnd = new Date(year, endMonth, 0).toISOString().slice(0, 10).replace(/-/g, '');
|
||
filename = `DATEV_${year}_Q${quarter}.csv`;
|
||
} else if (timeRange.length === 4) {
|
||
// Year format: YYYY
|
||
filteredTransactions = transactions.filter(t => {
|
||
if (!t.monthYear) return false;
|
||
const [tYear] = t.monthYear.split('-');
|
||
return tYear === timeRange;
|
||
});
|
||
|
||
periodStart = `${timeRange}0101`;
|
||
periodEnd = `${timeRange}1231`;
|
||
filename = `DATEV_${timeRange}.csv`;
|
||
} else {
|
||
// Month format: YYYY-MM
|
||
const [year, month] = timeRange.split('-');
|
||
filteredTransactions = transactions.filter(t => t.monthYear === timeRange);
|
||
|
||
periodStart = `${year}${month.padStart(2, '0')}01`;
|
||
periodEnd = new Date(year, month, 0).toISOString().slice(0, 10).replace(/-/g, '');
|
||
filename = `DATEV_${year}_${month.padStart(2, '0')}.csv`;
|
||
}
|
||
|
||
const monthTransactions = filteredTransactions
|
||
.sort((a, b) => a.parsedDate - b.parsedDate); // Oldest first for DATEV
|
||
|
||
if (!monthTransactions.length) {
|
||
return res.status(404).json({ error: 'No transactions found for this time period' });
|
||
}
|
||
|
||
// Build DATEV format
|
||
const header = buildDatevHeader(periodStart, periodEnd);
|
||
|
||
const rows = monthTransactions.map((transaction, index) => {
|
||
const amount = Math.abs(transaction.numericAmount);
|
||
const isDebit = transaction.numericAmount < 0 ? 'S' : 'H'; // S = Soll (debit), H = Haben (credit)
|
||
|
||
return [
|
||
formatDatevAmount(amount), // #1 Umsatz
|
||
isDebit, // #2 Soll/Haben
|
||
quote('EUR', 3), // #3 WKZ Umsatz
|
||
'', '', '', // #4-6 (no FX)
|
||
'1200', // #7 Konto (Bank account)
|
||
transaction.numericAmount < 0 ? '4000' : '8400', // #8 Gegenkonto (expense/income)
|
||
'', // #9 BU-Schlüssel
|
||
formatDatevDate(transaction['Buchungstag']), // #10 Belegdatum
|
||
quote((index + 1).toString(), 36), // #11 Belegfeld 1 (sequential number)
|
||
'', '', // #12, #13
|
||
quote(transaction['Verwendungszweck'] || transaction['Buchungstext'], 60), // #14 Buchungstext
|
||
'', '', '', '', '', // #15-19 unused
|
||
'' // #20 Beleglink
|
||
].join(';');
|
||
});
|
||
|
||
const csv = [header, DATEV_COLS, ...rows].join('\r\n');
|
||
|
||
// Set headers for file download
|
||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||
res.setHeader('Content-Type', 'text/csv; charset=latin1');
|
||
|
||
// Convert to latin1 encoding for DATEV compatibility
|
||
const buffer = Buffer.from(csv, 'utf8');
|
||
res.send(buffer);
|
||
|
||
} catch (error) {
|
||
console.error('Error generating DATEV export:', error);
|
||
res.status(500).json({ error: 'Failed to generate DATEV export' });
|
||
}
|
||
});
|
||
|
||
// Get PDF from tUmsatzBeleg
|
||
router.get('/pdf/umsatzbeleg/:kUmsatzBeleg', authenticateToken, async (req, res) => {
|
||
try {
|
||
const { kUmsatzBeleg } = req.params;
|
||
const { executeQuery } = require('../config/database');
|
||
|
||
const query = `
|
||
SELECT content, datevlink
|
||
FROM dbo.tUmsatzBeleg
|
||
WHERE kUmsatzBeleg = @kUmsatzBeleg AND content IS NOT NULL
|
||
`;
|
||
|
||
const result = await executeQuery(query, {
|
||
kUmsatzBeleg: parseInt(kUmsatzBeleg)
|
||
});
|
||
|
||
if (!result.recordset || result.recordset.length === 0) {
|
||
return res.status(404).json({ error: 'PDF not found' });
|
||
}
|
||
|
||
const pdfData = result.recordset[0];
|
||
const filename = `Umsatzbeleg_${kUmsatzBeleg}_${pdfData.datevlink || 'document'}.pdf`;
|
||
|
||
res.setHeader('Content-Type', 'application/pdf');
|
||
res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
|
||
res.send(pdfData.content);
|
||
|
||
} catch (error) {
|
||
console.error('Error fetching PDF from tUmsatzBeleg:', error);
|
||
res.status(500).json({ error: 'Failed to fetch PDF' });
|
||
}
|
||
});
|
||
|
||
// Get PDF from tPdfObjekt
|
||
router.get('/pdf/pdfobject/:kPdfObjekt', authenticateToken, async (req, res) => {
|
||
try {
|
||
const { kPdfObjekt } = req.params;
|
||
const { executeQuery } = require('../config/database');
|
||
|
||
const query = `
|
||
SELECT content, datevlink, kLieferantenbestellung
|
||
FROM dbo.tPdfObjekt
|
||
WHERE kPdfObjekt = @kPdfObjekt AND content IS NOT NULL
|
||
`;
|
||
|
||
const result = await executeQuery(query, {
|
||
kPdfObjekt: parseInt(kPdfObjekt)
|
||
});
|
||
|
||
if (!result.recordset || result.recordset.length === 0) {
|
||
return res.status(404).json({ error: 'PDF not found' });
|
||
}
|
||
|
||
const pdfData = result.recordset[0];
|
||
const filename = `PdfObjekt_${kPdfObjekt}_LB${pdfData.kLieferantenbestellung}_${pdfData.datevlink || 'document'}.pdf`;
|
||
|
||
res.setHeader('Content-Type', 'application/pdf');
|
||
res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
|
||
res.send(pdfData.content);
|
||
|
||
} catch (error) {
|
||
console.error('Error fetching PDF from tPdfObjekt:', error);
|
||
res.status(500).json({ error: 'Failed to fetch PDF' });
|
||
}
|
||
});
|
||
|
||
module.exports = router;
|