Implement DATEV export functionality in DataViewer and enhance TransactionsTable with selection features and improved row styling. Update environment variables and add devServer configuration in webpack for better development experience.

This commit is contained in:
sebseb7
2025-07-20 07:47:18 +02:00
parent 2a43b7106d
commit 429fd70497
18 changed files with 1542 additions and 149 deletions

View File

@@ -103,10 +103,51 @@ const getJTLTransactions = async () => {
`;
const result = await executeQuery(query);
return result.recordset || [];
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);
return [];
throw error; // Re-throw the error so the caller knows the database is unavailable
}
};
@@ -156,10 +197,13 @@ router.get('/transactions/:timeRange', authenticateToken, async (req, res) => {
// 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
@@ -213,9 +257,13 @@ router.get('/transactions/:timeRange', authenticateToken, async (req, res) => {
return {
...transaction,
hasJTL: !!jtlMatch,
hasJTL: jtlDatabaseAvailable ? !!jtlMatch : undefined,
jtlId: jtlMatch ? jtlMatch.kZahlungsabgleichUmsatz : null,
isFromCSV: true
isFromCSV: true,
jtlDatabaseAvailable,
// Include document data from JTL match
pdfs: jtlMatch ? jtlMatch.pdfs || [] : [],
links: jtlMatch ? jtlMatch.links || [] : []
};
});
@@ -258,7 +306,10 @@ router.get('/transactions/:timeRange', authenticateToken, async (req, res) => {
hasJTL: true,
jtlId: jtl.kZahlungsabgleichUmsatz,
isFromCSV: false,
isJTLOnly: true
isJTLOnly: true,
// Include document data from JTL transaction
pdfs: jtl.pdfs || [],
links: jtl.links || []
}));
// Combine CSV and JTL-only transactions
@@ -275,10 +326,11 @@ router.get('/transactions/:timeRange', authenticateToken, async (req, res) => {
.filter(t => t.numericAmount < 0)
.reduce((sum, t) => sum + Math.abs(t.numericAmount), 0),
netAmount: allTransactions.reduce((sum, t) => sum + t.numericAmount, 0),
jtlMatches: allTransactions.filter(t => t.hasJTL && t.isFromCSV).length,
jtlMissing: allTransactions.filter(t => !t.hasJTL && t.isFromCSV).length,
jtlOnly: allTransactions.filter(t => t.isJTLOnly).length,
csvOnly: allTransactions.filter(t => !t.hasJTL && t.isFromCSV).length
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({
@@ -440,4 +492,70 @@ router.get('/datev/:timeRange', authenticateToken, async (req, res) => {
}
});
// 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;