diff --git a/client/src/components/TableManagement.js b/client/src/components/TableManagement.js index 1dc288e..0d1d2ba 100644 --- a/client/src/components/TableManagement.js +++ b/client/src/components/TableManagement.js @@ -10,16 +10,19 @@ import { AccountBalance as KreditorIcon, AccountBalanceWallet as KontoIcon, Receipt as BUIcon, + CloudUpload as ImportIcon, } from '@mui/icons-material'; import KreditorTable from './admin/KreditorTable'; import KontoTable from './admin/KontoTable'; import BUTable from './admin/BUTable'; +import CSVImportDialog from './CSVImportDialog'; class TableManagement extends Component { constructor(props) { super(props); this.state = { activeTab: 0, + csvImportOpen: false, }; } @@ -27,6 +30,14 @@ class TableManagement extends Component { this.setState({ activeTab: newValue }); }; + handleOpenCSVImport = () => { + this.setState({ csvImportOpen: true }); + }; + + handleCloseCSVImport = () => { + this.setState({ csvImportOpen: false }); + }; + render() { const { activeTab } = this.state; const { user } = this.props; @@ -59,6 +70,11 @@ class TableManagement extends Component { label="Buchungsschlüssel" sx={{ minHeight: 64 }} /> + } + label="CSV Import" + sx={{ minHeight: 64 }} + /> @@ -66,6 +82,21 @@ class TableManagement extends Component { {activeTab === 0 && } {activeTab === 1 && } {activeTab === 2 && } + {activeTab === 3 && ( + + + CSV Transaktionen importieren + + + Hier können Sie CSV-Dateien von Ihrer Bank importieren. Die Daten werden in die Datenbank gespeichert und können dann Banking-Konten zugeordnet werden. + + {}} // Always open in this tab + user={user} + /> + + )} diff --git a/src/routes/data.js b/src/routes/data.js index 95d71df..74ed04a 100644 --- a/src/routes/data.js +++ b/src/routes/data.js @@ -1,85 +1,30 @@ 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 []; - } -}; +// Old CSV parsing removed - now using database-based CSV import -// Get available months -router.get('/months', authenticateToken, (req, res) => { +// Get available months from database +router.get('/months', authenticateToken, async (req, res) => { try { - const transactions = parseCSV(); - const months = [...new Set(transactions - .filter(t => t.monthYear) - .map(t => t.monthYear) - )].sort().reverse(); // Newest first + const { executeQuery } = require('../config/database'); + + // Get months from both AccountingItems and CSVTransactions + const query = ` + SELECT DISTINCT + FORMAT(COALESCE(ai.buchungstag, csv.parsed_date), 'yyyy-MM') as month_year + FROM ( + SELECT buchungstag FROM fibdash.AccountingItems + UNION ALL + SELECT parsed_date as buchungstag FROM fibdash.CSVTransactions WHERE parsed_date IS NOT NULL + ) ai + WHERE ai.buchungstag IS NOT NULL + ORDER BY month_year DESC + `; + + const result = await executeQuery(query); + const months = result.recordset.map(row => row.month_year); res.json({ months }); } catch (error) { @@ -154,8 +99,12 @@ const getJTLTransactions = async () => { // Get transactions for a specific time period (month, quarter, or year) router.get('/transactions/:timeRange', authenticateToken, async (req, res) => { try { + // TODO: Update to use database queries instead of CSV file + res.status(501).json({ error: 'Endpoint temporarily disabled - use database-based queries' }); + return; + const { timeRange } = req.params; - const transactions = parseCSV(); + const transactions = []; let filteredTransactions = []; let periodDescription = ''; @@ -428,10 +377,14 @@ const quote = (str, maxLen = 60) => { // DATEV export endpoint router.get('/datev/:timeRange', authenticateToken, async (req, res) => { try { + // TODO: Update to use database queries instead of CSV file + res.status(501).json({ error: 'DATEV export temporarily disabled - use database-based queries' }); + return; + const { timeRange } = req.params; // Get transactions for the time period - const transactions = parseCSV(); + const transactions = []; let filteredTransactions = []; let periodStart, periodEnd, filename; @@ -933,132 +886,7 @@ router.get('/assignable-kreditors', authenticateToken, async (req, res) => { // CSV Import endpoints -// Test CSV import endpoint (no auth for testing) - ACTUALLY IMPORTS TO DATABASE -router.post('/test-csv-import', async (req, res) => { - try { - const { executeQuery } = require('../config/database'); - const { transactions, filename, batchId, headers } = req.body; - - if (!transactions || !Array.isArray(transactions)) { - return res.status(400).json({ error: 'Transactions array is required' }); - } - - const importBatchId = batchId || `test_import_${Date.now()}`; - let successCount = 0; - let errorCount = 0; - const errors = []; - - for (let i = 0; i < transactions.length; i++) { - const transaction = transactions[i]; - - try { - // Validate required fields - const validationErrors = []; - - if (!transaction['Buchungstag'] || transaction['Buchungstag'].trim() === '') { - validationErrors.push('Buchungstag is required'); - } - - if (!transaction['Betrag'] || transaction['Betrag'].toString().trim() === '') { - validationErrors.push('Betrag is required'); - } - - if (validationErrors.length > 0) { - errors.push({ - row: i + 1, - error: `Validation failed: ${validationErrors.join(', ')}`, - transaction: transaction - }); - errorCount++; - continue; - } - - // Parse the date - let parsedDate = null; - if (transaction['Buchungstag']) { - const dateStr = transaction['Buchungstag'].trim(); - const dateParts = dateStr.split(/[.\/\-]/); - if (dateParts.length === 3) { - const day = parseInt(dateParts[0]); - const month = parseInt(dateParts[1]) - 1; - let year = parseInt(dateParts[2]); - - if (year < 100) { - year += (year < 50) ? 2000 : 1900; - } - - parsedDate = new Date(year, month, day); - - if (isNaN(parsedDate.getTime())) { - parsedDate = null; - validationErrors.push(`Invalid date format: ${dateStr}`); - } - } - } - - // Parse the amount - let numericAmount = 0; - if (transaction['Betrag']) { - const amountStr = transaction['Betrag'].toString().replace(/[^\d,.-]/g, ''); - const normalizedAmount = amountStr.replace(',', '.'); - numericAmount = parseFloat(normalizedAmount) || 0; - } - - const insertQuery = ` - INSERT INTO fibdash.CSVTransactions - (buchungstag, wertstellung, umsatzart, betrag, betrag_original, waehrung, - beguenstigter_zahlungspflichtiger, kontonummer_iban, bic, verwendungszweck, - parsed_date, numeric_amount, import_batch_id, source_filename, source_row_number) - VALUES - (@buchungstag, @wertstellung, @umsatzart, @betrag, @betrag_original, @waehrung, - @beguenstigter_zahlungspflichtiger, @kontonummer_iban, @bic, @verwendungszweck, - @parsed_date, @numeric_amount, @import_batch_id, @source_filename, @source_row_number) - `; - - await executeQuery(insertQuery, { - buchungstag: transaction['Buchungstag'] || null, - wertstellung: transaction['Valutadatum'] || null, - umsatzart: transaction['Buchungstext'] || null, - betrag: numericAmount, - betrag_original: transaction['Betrag'] || null, - waehrung: transaction['Waehrung'] || null, - beguenstigter_zahlungspflichtiger: transaction['Beguenstigter/Zahlungspflichtiger'] || null, - kontonummer_iban: transaction['Kontonummer/IBAN'] || null, - bic: transaction['BIC (SWIFT-Code)'] || null, - verwendungszweck: transaction['Verwendungszweck'] || null, - parsed_date: parsedDate, - numeric_amount: numericAmount, - import_batch_id: importBatchId, - source_filename: filename || 'test_import', - source_row_number: i + 1 - }); - - successCount++; - } catch (error) { - console.error(`Error importing transaction ${i + 1}:`, error); - errors.push({ - row: i + 1, - error: error.message, - transaction: transaction - }); - errorCount++; - } - } - - res.json({ - success: true, - batchId: importBatchId, - imported: successCount, - errors: errorCount, - details: errors.length > 0 ? errors : undefined, - paypalTransaction: transactions.find(t => t['Kontonummer/IBAN'] === 'LU89751000135104200E') - }); - - } catch (error) { - console.error('Test import error:', error); - res.status(500).json({ error: 'Test import failed' }); - } -}); +// Test endpoint removed - use the authenticated import-csv-transactions endpoint // Import CSV transactions to database router.post('/import-csv-transactions', authenticateToken, async (req, res) => {