diff --git a/client/src/components/BankingKreditorSelector.js b/client/src/components/BankingKreditorSelector.js index c36258c..b35ec5a 100644 --- a/client/src/components/BankingKreditorSelector.js +++ b/client/src/components/BankingKreditorSelector.js @@ -225,7 +225,6 @@ class BankingKreditorSelector extends Component { method: 'PUT', body: JSON.stringify({ assigned_kreditor_id: parseInt(selectedKreditorId), - assigned_by: user?.username || 'Unknown', }) } ); @@ -240,7 +239,6 @@ class BankingKreditorSelector extends Component { csv_transaction_id: transaction.isFromCSV ? transaction.id : null, banking_iban: transaction['Kontonummer/IBAN'] || transaction.kontonummer_iban, assigned_kreditor_id: parseInt(selectedKreditorId), - assigned_by: user?.username || 'Unknown', }) } ); @@ -257,7 +255,11 @@ class BankingKreditorSelector extends Component { if (response && response.ok) { this.setState({ saving: false }); if (onSave) { - onSave(); + // Find the assigned kreditor from the list to pass to callback + const assignedKreditor = this.state.assignableKreditors.find( + k => k.id === parseInt(selectedKreditorId) + ); + onSave(assignedKreditor); } } else { const errorData = await response.json(); diff --git a/client/src/components/cellRenderers/DocumentRenderer.js b/client/src/components/cellRenderers/DocumentRenderer.js index 4a2885c..25cb542 100644 --- a/client/src/components/cellRenderers/DocumentRenderer.js +++ b/client/src/components/cellRenderers/DocumentRenderer.js @@ -386,13 +386,13 @@ const DocumentRenderer = (params) => { sx={{ color: !params.data['Kontonummer/IBAN'] ? 'text.secondary' - : (params.data.hasKreditor && params.data.kreditor && !params.data.kreditor.is_banking) + : (params.data.hasKreditor && params.data.kreditor && !params.data.kreditor.is_banking) || params.data.assignedKreditor ? 'success.main' : 'warning.main', '&.Mui-selected': { color: !params.data['Kontonummer/IBAN'] ? 'text.secondary' - : (params.data.hasKreditor && params.data.kreditor && !params.data.kreditor.is_banking) + : (params.data.hasKreditor && params.data.kreditor && !params.data.kreditor.is_banking) || params.data.assignedKreditor ? 'success.main' : 'warning.main', } @@ -543,14 +543,22 @@ const DocumentRenderer = (params) => { { - // Refresh the grid to show updated assignment + onSave={async (assignedKreditor) => { + // Update the transaction data with the new assignment + if (assignedKreditor) { + params.data.assignedKreditor = assignedKreditor; + } + + // Refresh the entire row to update colors and content if (params.api) { params.api.refreshCells({ - columns: ['Kontonummer/IBAN'], + rowNodes: [params.node], force: true }); } + + // Force dialog re-render by updating state + this.forceUpdate(); }} /> @@ -620,14 +628,22 @@ const DocumentRenderer = (params) => { { - // Refresh the grid to show updated assignment + onSave={async (assignedKreditor) => { + // Update the transaction data with the new assignment + if (assignedKreditor) { + params.data.assignedKreditor = assignedKreditor; + } + + // Refresh the entire row to update colors and content if (params.api) { params.api.refreshCells({ - columns: ['Kontonummer/IBAN'], + rowNodes: [params.node], force: true }); } + + // Force dialog re-render by updating state + this.forceUpdate(); }} /> diff --git a/src/routes/data/bankingTransactions.js b/src/routes/data/bankingTransactions.js index 6344277..491d631 100644 --- a/src/routes/data/bankingTransactions.js +++ b/src/routes/data/bankingTransactions.js @@ -9,17 +9,37 @@ router.get('/banking-transactions/:transactionId', authenticateToken, async (req const { executeQuery } = require('../../config/database'); const { transactionId } = req.params; - const query = ` - SELECT - bat.*, - k.name as assigned_kreditor_name, - k.kreditorId as assigned_kreditor_id_code - FROM fibdash.BankingAccountTransactions bat - LEFT JOIN fibdash.Kreditor k ON bat.assigned_kreditor_id = k.id - WHERE bat.transaction_id = @transactionId OR bat.csv_transaction_id = @transactionId - `; + // Try both numeric and string format + let query, params; + const numericId = parseInt(transactionId, 10); - const result = await executeQuery(query, { transactionId: parseInt(transactionId, 10) }); + if (!isNaN(numericId) && numericId.toString() === transactionId) { + // It's a numeric ID - check transaction_id column + query = ` + SELECT + bat.*, + k.name as assigned_kreditor_name, + k.kreditorId as assigned_kreditor_id_code + FROM fibdash.BankingAccountTransactions bat + LEFT JOIN fibdash.Kreditor k ON bat.assigned_kreditor_id = k.id + WHERE bat.transaction_id = @transactionId + `; + params = { transactionId: numericId }; + } else { + // It's a string ID - check csv_transaction_id column + query = ` + SELECT + bat.*, + k.name as assigned_kreditor_name, + k.kreditorId as assigned_kreditor_id_code + FROM fibdash.BankingAccountTransactions bat + LEFT JOIN fibdash.Kreditor k ON bat.assigned_kreditor_id = k.id + WHERE bat.csv_transaction_id = @transactionId + `; + params = { transactionId }; + } + + const result = await executeQuery(query, params); res.json(result.recordset); } catch (error) { @@ -32,7 +52,7 @@ router.get('/banking-transactions/:transactionId', authenticateToken, async (req router.post('/banking-transactions', authenticateToken, async (req, res) => { try { const { executeQuery } = require('../../config/database'); - const { transaction_id, csv_transaction_id, banking_iban, assigned_kreditor_id, notes, assigned_by } = req.body; + const { transaction_id, csv_transaction_id, banking_iban, assigned_kreditor_id, notes } = req.body; if ((!transaction_id && !csv_transaction_id) || !banking_iban || !assigned_kreditor_id) { return res.status(400).json({ @@ -40,35 +60,55 @@ router.post('/banking-transactions', authenticateToken, async (req, res) => { }); } - const checkQuery = ` - SELECT id FROM fibdash.BankingAccountTransactions - WHERE transaction_id = @transaction_id OR csv_transaction_id = @csv_transaction_id - `; + let checkQuery, checkParams; - const checkResult = await executeQuery(checkQuery, { - transaction_id: transaction_id || null, - csv_transaction_id: csv_transaction_id || null - }); + if (csv_transaction_id) { + checkQuery = `SELECT id FROM fibdash.BankingAccountTransactions WHERE csv_transaction_id = @csv_transaction_id`; + checkParams = { csv_transaction_id }; + } else { + checkQuery = `SELECT id FROM fibdash.BankingAccountTransactions WHERE transaction_id = @transaction_id`; + checkParams = { transaction_id }; + } + + const checkResult = await executeQuery(checkQuery, checkParams); if (checkResult.recordset.length > 0) { return res.status(409).json({ error: 'Banking transaction assignment already exists' }); } - const insertQuery = ` - INSERT INTO fibdash.BankingAccountTransactions - (transaction_id, csv_transaction_id, banking_iban, assigned_kreditor_id, notes, assigned_by) - OUTPUT INSERTED.* - VALUES (@transaction_id, @csv_transaction_id, @banking_iban, @assigned_kreditor_id, @notes, @assigned_by) - `; + let insertQuery, queryParams; - const result = await executeQuery(insertQuery, { - transaction_id: transaction_id || null, - csv_transaction_id: csv_transaction_id || null, - banking_iban, - assigned_kreditor_id, - notes: notes || null, - assigned_by: assigned_by || null - }); + if (csv_transaction_id) { + // For CSV transactions, use a placeholder transaction_id since it's NOT NULL + insertQuery = ` + INSERT INTO fibdash.BankingAccountTransactions + (transaction_id, csv_transaction_id, banking_iban, assigned_kreditor_id, notes) + OUTPUT INSERTED.* + VALUES (-1, @csv_transaction_id, @banking_iban, @assigned_kreditor_id, @notes) + `; + queryParams = { + csv_transaction_id, + banking_iban, + assigned_kreditor_id, + notes: notes || null + }; + } else { + // For regular transactions + insertQuery = ` + INSERT INTO fibdash.BankingAccountTransactions + (transaction_id, csv_transaction_id, banking_iban, assigned_kreditor_id, notes) + OUTPUT INSERTED.* + VALUES (@transaction_id, NULL, @banking_iban, @assigned_kreditor_id, @notes) + `; + queryParams = { + transaction_id, + banking_iban, + assigned_kreditor_id, + notes: notes || null + }; + } + + const result = await executeQuery(insertQuery, queryParams); res.status(201).json(result.recordset[0]); } catch (error) { @@ -82,7 +122,7 @@ router.put('/banking-transactions/:id', authenticateToken, async (req, res) => { try { const { executeQuery } = require('../../config/database'); const { id } = req.params; - const { assigned_kreditor_id, notes, assigned_by } = req.body; + const { assigned_kreditor_id, notes } = req.body; if (!assigned_kreditor_id) { return res.status(400).json({ error: 'Assigned kreditor ID is required' }); @@ -92,7 +132,6 @@ router.put('/banking-transactions/:id', authenticateToken, async (req, res) => { UPDATE fibdash.BankingAccountTransactions SET assigned_kreditor_id = @assigned_kreditor_id, notes = @notes, - assigned_by = @assigned_by, assigned_date = GETDATE() OUTPUT INSERTED.* WHERE id = @id @@ -101,7 +140,6 @@ router.put('/banking-transactions/:id', authenticateToken, async (req, res) => { const result = await executeQuery(updateQuery, { assigned_kreditor_id, notes: notes || null, - assigned_by: assigned_by || null, id: parseInt(id, 10) }); diff --git a/src/routes/data/transactions.js b/src/routes/data/transactions.js index 914bc32..e267c14 100644 --- a/src/routes/data/transactions.js +++ b/src/routes/data/transactions.js @@ -12,6 +12,7 @@ router.get('/transactions/:timeRange', authenticateToken, async (req, res) => { const { executeQuery } = require('../../config/database'); const query = ` SELECT + csv.id as id, csv.buchungstag as 'Buchungstag', csv.wertstellung as 'Valutadatum', csv.umsatzart as 'Buchungstext', @@ -31,6 +32,7 @@ router.get('/transactions/:timeRange', authenticateToken, async (req, res) => { k.is_banking as kreditor_is_banking, bat.assigned_kreditor_id, ak.name as assigned_kreditor_name, + ak.kreditorId as assigned_kreditor_kreditorId, 0 as isJTLOnly, 1 as isFromCSV, ub.textContent as jtl_document_data, @@ -49,6 +51,7 @@ router.get('/transactions/:timeRange', authenticateToken, async (req, res) => { UNION ALL SELECT + jtl.kZahlungsabgleichUmsatz as id, FORMAT(jtl.dBuchungsdatum, 'dd.MM.yy') as 'Buchungstag', FORMAT(jtl.dBuchungsdatum, 'dd.MM.yy') as 'Valutadatum', 'JTL Transaction' as 'Buchungstext', @@ -68,6 +71,7 @@ router.get('/transactions/:timeRange', authenticateToken, async (req, res) => { NULL as kreditor_is_banking, NULL as assigned_kreditor_id, NULL as assigned_kreditor_name, + NULL as assigned_kreditor_kreditorId, 1 as isJTLOnly, 0 as isFromCSV, ub.textContent as jtl_document_data, @@ -108,7 +112,7 @@ router.get('/transactions/:timeRange', authenticateToken, async (req, res) => { ...transaction, parsedDate: new Date(transaction.parsed_date), hasJTL: Boolean(transaction.hasJTL), - isFromCSV: true, + isFromCSV: Boolean(transaction.isFromCSV), jtlDatabaseAvailable: true, hasKreditor: !!transaction.kreditor_name, kreditor: transaction.kreditor_name ? { @@ -116,9 +120,10 @@ router.get('/transactions/:timeRange', authenticateToken, async (req, res) => { kreditorId: transaction.kreditor_id, is_banking: Boolean(transaction.kreditor_is_banking) } : null, - assigned_kreditor: transaction.assigned_kreditor_name ? { + assignedKreditor: transaction.assigned_kreditor_name ? { name: transaction.assigned_kreditor_name, - id: transaction.assigned_kreditor_id + id: transaction.assigned_kreditor_id, + kreditorId: transaction.assigned_kreditor_kreditorId } : null, pdfs: transaction.jtl_document_data ? [{ content: transaction.jtl_document_data,