diff --git a/.cursor/rules/devdatabase.mdc b/.cursor/rules/devdatabase.mdc
index dc222a2..10772ae 100644
--- a/.cursor/rules/devdatabase.mdc
+++ b/.cursor/rules/devdatabase.mdc
@@ -1,4 +1,6 @@
---
alwaysApply: true
---
-sqlcmd -C -S tcp:192.168.56.1,1497 -U app -P 'readonly' -d eazybusiness -W
\ No newline at end of file
+sqlcmd -C -S tcp:192.168.56.1,1497 -U app -P 'readonly' -d eazybusiness -W
+
+sqlcmd -C -S tcp:192.168.56.1,1497 -U sa -P 'sa_tekno23' -d eazybusiness -W
\ No newline at end of file
diff --git a/client/src/components/KreditorSelector.js b/client/src/components/KreditorSelector.js
new file mode 100644
index 0000000..5f635b2
--- /dev/null
+++ b/client/src/components/KreditorSelector.js
@@ -0,0 +1,288 @@
+import React, { Component } from 'react';
+import {
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ TextField,
+ Box,
+ Typography,
+ Alert,
+ CircularProgress
+} from '@mui/material';
+import { Add as AddIcon } from '@mui/icons-material';
+import KreditorService from '../services/KreditorService';
+
+class KreditorSelector extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ kreditors: [],
+ selectedKreditorId: props.selectedKreditorId || '',
+ loading: false,
+ createDialogOpen: false,
+ newKreditor: {
+ iban: '',
+ name: '',
+ kreditorId: ''
+ },
+ validationErrors: [],
+ error: null,
+ creating: false
+ };
+
+ this.kreditorService = new KreditorService();
+ }
+
+ componentDidMount() {
+ this.loadKreditors();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (prevProps.selectedKreditorId !== this.props.selectedKreditorId) {
+ this.setState({ selectedKreditorId: this.props.selectedKreditorId || '' });
+ }
+ }
+
+ loadKreditors = async () => {
+ this.setState({ loading: true, error: null });
+
+ try {
+ const kreditors = await this.kreditorService.getAllKreditors();
+ this.setState({ kreditors, loading: false });
+ } catch (error) {
+ console.error('Error loading kreditors:', error);
+ this.setState({
+ error: error.message,
+ loading: false
+ });
+ }
+ };
+
+ handleKreditorChange = (event) => {
+ const selectedKreditorId = event.target.value;
+
+ if (selectedKreditorId === 'create_new') {
+ this.setState({ createDialogOpen: true });
+ return;
+ }
+
+ this.setState({ selectedKreditorId });
+
+ if (this.props.onKreditorChange) {
+ const selectedKreditor = this.state.kreditors.find(k => k.id === selectedKreditorId);
+ this.props.onKreditorChange(selectedKreditor);
+ }
+ };
+
+ handleCreateDialogClose = () => {
+ this.setState({
+ createDialogOpen: false,
+ newKreditor: { iban: '', name: '', kreditorId: '' },
+ validationErrors: [],
+ error: null
+ });
+ };
+
+ handleNewKreditorChange = (field, value) => {
+ this.setState({
+ newKreditor: {
+ ...this.state.newKreditor,
+ [field]: value
+ },
+ validationErrors: [] // Clear validation errors when user types
+ });
+ };
+
+ generateKreditorId = () => {
+ // Generate a kreditorId starting with 70 followed by random digits
+ const randomDigits = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
+ const kreditorId = `70${randomDigits}`;
+
+ this.setState({
+ newKreditor: {
+ ...this.state.newKreditor,
+ kreditorId
+ }
+ });
+ };
+
+ handleCreateKreditor = async () => {
+ const { newKreditor } = this.state;
+
+ // Validate the data
+ const validationErrors = this.kreditorService.validateKreditorData(newKreditor);
+ if (validationErrors.length > 0) {
+ this.setState({ validationErrors });
+ return;
+ }
+
+ this.setState({ creating: true, error: null });
+
+ try {
+ const createdKreditor = await this.kreditorService.createKreditor(newKreditor);
+
+ // Add the new kreditor to the list and select it
+ const updatedKreditors = [...this.state.kreditors, createdKreditor];
+ this.setState({
+ kreditors: updatedKreditors,
+ selectedKreditorId: createdKreditor.id,
+ creating: false
+ });
+
+ // Notify parent component
+ if (this.props.onKreditorChange) {
+ this.props.onKreditorChange(createdKreditor);
+ }
+
+ this.handleCreateDialogClose();
+ } catch (error) {
+ console.error('Error creating kreditor:', error);
+ this.setState({
+ error: error.message,
+ creating: false
+ });
+ }
+ };
+
+ render() {
+ const {
+ kreditors,
+ selectedKreditorId,
+ loading,
+ createDialogOpen,
+ newKreditor,
+ validationErrors,
+ error,
+ creating
+ } = this.state;
+
+ const { label = "Kreditor", disabled = false, fullWidth = true } = this.props;
+
+ return (
+ <>
+
+ {label}
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {/* Create Kreditor Dialog */}
+
+ >
+ );
+ }
+}
+
+export default KreditorSelector;
\ No newline at end of file
diff --git a/client/src/components/cellRenderers/DocumentRenderer.js b/client/src/components/cellRenderers/DocumentRenderer.js
index ef7ac3a..64e58ef 100644
--- a/client/src/components/cellRenderers/DocumentRenderer.js
+++ b/client/src/components/cellRenderers/DocumentRenderer.js
@@ -25,6 +25,7 @@ import {
ContentCopy as CopyIcon
} from '@mui/icons-material';
import { AgGridReact } from 'ag-grid-react';
+import KreditorSelector from '../KreditorSelector';
const DocumentRenderer = (params) => {
// Check for pdfs and links regardless of transaction source
@@ -145,7 +146,10 @@ const DocumentRenderer = (params) => {
currency: extractionData.currency || 'EUR',
invoiceNumber: extractionData.invoice_number || '',
date: extractionData.date || '',
- sender: extractionData.sender || ''
+ sender: extractionData.sender || '',
+ kreditorId: extractionData.kreditor_id || null,
+ kreditorName: extractionData.kreditor_name || '',
+ kreditorCode: extractionData.kreditor_code || ''
});
});
}
@@ -184,6 +188,34 @@ const DocumentRenderer = (params) => {
width: 150,
tooltipField: 'sender'
},
+ {
+ headerName: 'Kreditor',
+ field: 'kreditor',
+ width: 200,
+ cellRenderer: (params) => {
+ return (
+
+ {
+ // Update the line item with the selected kreditor
+ if (params.data && params.api) {
+ params.data.kreditorId = kreditor ? kreditor.id : null;
+ params.data.kreditorName = kreditor ? kreditor.name : '';
+ params.data.kreditorCode = kreditor ? kreditor.kreditorId : '';
+ params.api.refreshCells({ rowNodes: [params.node] });
+ }
+ }}
+ label=""
+ fullWidth={false}
+ />
+
+ );
+ },
+ editable: false,
+ sortable: false,
+ filter: false
+ },
{
headerName: 'Netto',
field: 'netAmount',
@@ -371,7 +403,7 @@ const DocumentRenderer = (params) => {
)}
{tabValue === 1 && (
-
+
{lineItems.length > 0 ? (
{
rowData={lineItems}
defaultColDef={defaultColDef}
suppressRowTransform={true}
- rowHeight={35}
+ rowHeight={50}
headerHeight={35}
domLayout="normal"
/>
diff --git a/client/src/services/KreditorService.js b/client/src/services/KreditorService.js
new file mode 100644
index 0000000..159d708
--- /dev/null
+++ b/client/src/services/KreditorService.js
@@ -0,0 +1,178 @@
+class KreditorService {
+ constructor() {
+ this.baseURL = '/api';
+ }
+
+ async getAuthHeaders() {
+ const token = localStorage.getItem('token');
+ return {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`,
+ };
+ }
+
+ async handleResponse(response) {
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}));
+ console.log('Server error response:', errorData);
+
+ // Handle different types of errors with clearer messages
+ if (response.status === 502 || response.status === 503) {
+ throw new Error('FibDash Service nicht verfügbar - Bitte versuchen Sie es später erneut');
+ } else if (response.status === 500) {
+ throw new Error('FibDash Server Fehler - Bitte kontaktieren Sie den Administrator');
+ } else if (response.status === 403) {
+ const message = errorData.message || 'Zugriff verweigert';
+ throw new Error(message);
+ } else if (response.status === 401) {
+ throw new Error('Authentifizierung fehlgeschlagen - Bitte melden Sie sich erneut an');
+ } else if (response.status === 404) {
+ throw new Error('Kreditor nicht gefunden');
+ } else if (response.status === 409) {
+ const message = errorData.error || 'Kreditor bereits vorhanden';
+ throw new Error(message);
+ } else if (response.status === 400) {
+ const message = errorData.error || 'Ungültige Daten';
+ throw new Error(message);
+ } else {
+ const errorMessage = errorData.error || errorData.message || `HTTP ${response.status}: Unbekannter Fehler`;
+ throw new Error(errorMessage);
+ }
+ }
+
+ return await response.json();
+ }
+
+ async getAllKreditors() {
+ try {
+ const response = await fetch(`${this.baseURL}/data/kreditors`, {
+ method: 'GET',
+ headers: await this.getAuthHeaders(),
+ });
+
+ return await this.handleResponse(response);
+ } catch (error) {
+ console.error('Error fetching kreditors:', error);
+
+ // Handle network errors
+ if (error instanceof TypeError && error.message.includes('fetch')) {
+ throw new Error('FibDash Service nicht erreichbar - Prüfen Sie Ihre Internetverbindung oder versuchen Sie es später erneut');
+ }
+
+ throw error;
+ }
+ }
+
+ async getKreditorById(id) {
+ try {
+ const response = await fetch(`${this.baseURL}/data/kreditors/${id}`, {
+ method: 'GET',
+ headers: await this.getAuthHeaders(),
+ });
+
+ return await this.handleResponse(response);
+ } catch (error) {
+ console.error('Error fetching kreditor:', error);
+
+ // Handle network errors
+ if (error instanceof TypeError && error.message.includes('fetch')) {
+ throw new Error('FibDash Service nicht erreichbar - Prüfen Sie Ihre Internetverbindung oder versuchen Sie es später erneut');
+ }
+
+ throw error;
+ }
+ }
+
+ async createKreditor(kreditorData) {
+ try {
+ const response = await fetch(`${this.baseURL}/data/kreditors`, {
+ method: 'POST',
+ headers: await this.getAuthHeaders(),
+ body: JSON.stringify(kreditorData),
+ });
+
+ return await this.handleResponse(response);
+ } catch (error) {
+ console.error('Error creating kreditor:', error);
+
+ // Handle network errors
+ if (error instanceof TypeError && error.message.includes('fetch')) {
+ throw new Error('FibDash Service nicht erreichbar - Prüfen Sie Ihre Internetverbindung oder versuchen Sie es später erneut');
+ }
+
+ throw error;
+ }
+ }
+
+ async updateKreditor(id, kreditorData) {
+ try {
+ const response = await fetch(`${this.baseURL}/data/kreditors/${id}`, {
+ method: 'PUT',
+ headers: await this.getAuthHeaders(),
+ body: JSON.stringify(kreditorData),
+ });
+
+ return await this.handleResponse(response);
+ } catch (error) {
+ console.error('Error updating kreditor:', error);
+
+ // Handle network errors
+ if (error instanceof TypeError && error.message.includes('fetch')) {
+ throw new Error('FibDash Service nicht erreichbar - Prüfen Sie Ihre Internetverbindung oder versuchen Sie es später erneut');
+ }
+
+ throw error;
+ }
+ }
+
+ async deleteKreditor(id) {
+ try {
+ const response = await fetch(`${this.baseURL}/data/kreditors/${id}`, {
+ method: 'DELETE',
+ headers: await this.getAuthHeaders(),
+ });
+
+ return await this.handleResponse(response);
+ } catch (error) {
+ console.error('Error deleting kreditor:', error);
+
+ // Handle network errors
+ if (error instanceof TypeError && error.message.includes('fetch')) {
+ throw new Error('FibDash Service nicht erreichbar - Prüfen Sie Ihre Internetverbindung oder versuchen Sie es später erneut');
+ }
+
+ throw error;
+ }
+ }
+
+ // Utility method to validate kreditor data
+ validateKreditorData(kreditorData) {
+ const errors = [];
+
+ if (!kreditorData.iban || kreditorData.iban.trim() === '') {
+ errors.push('IBAN ist erforderlich');
+ }
+
+ if (!kreditorData.name || kreditorData.name.trim() === '') {
+ errors.push('Name ist erforderlich');
+ }
+
+ if (!kreditorData.kreditorId || kreditorData.kreditorId.trim() === '') {
+ errors.push('Kreditor-ID ist erforderlich');
+ }
+
+ // Basic IBAN format validation (simplified)
+ if (kreditorData.iban && !/^[A-Z]{2}[0-9]{2}[A-Z0-9]{4}[0-9]{7}([A-Z0-9]?){0,16}$/i.test(kreditorData.iban.replace(/\s/g, ''))) {
+ errors.push('IBAN Format ist ungültig');
+ }
+
+ // Validate kreditorId format (should start with 70xxx)
+ if (kreditorData.kreditorId && !/^70\d{3,}$/.test(kreditorData.kreditorId)) {
+ errors.push('Kreditor-ID muss mit 70 beginnen gefolgt von mindestens 3 Ziffern');
+ }
+
+ return errors;
+ }
+}
+
+export default KreditorService;
\ No newline at end of file
diff --git a/src/database/schema.sql b/src/database/schema.sql
index 60eb0e1..8fba03a 100644
--- a/src/database/schema.sql
+++ b/src/database/schema.sql
@@ -1,34 +1,94 @@
--- FibDash Database Schema
--- Run these commands in your MSSQL database
--- Create Users table
-CREATE TABLE Users (
+-- Create Kreditor table
+CREATE TABLE Kreditor (
id INT IDENTITY(1,1) PRIMARY KEY,
- google_id NVARCHAR(255) UNIQUE NOT NULL,
- email NVARCHAR(255) UNIQUE NOT NULL,
+ iban NVARCHAR(34) NOT NULL,
name NVARCHAR(255) NOT NULL,
- picture NVARCHAR(500),
- created_at DATETIME2 DEFAULT GETDATE(),
- last_login DATETIME2,
- is_active BIT DEFAULT 1
+ kreditorId NVARCHAR(50) NOT NULL
);
--- Create UserPreferences table
-CREATE TABLE UserPreferences (
+-- Ensure kreditorId is unique to support FK references
+ALTER TABLE Kreditor
+ADD CONSTRAINT UQ_Kreditor_kreditorId UNIQUE (kreditorId);
+
+-- Create AccountingItems table
+-- Based on CSV structure: umsatz brutto, soll/haben kz, konto, gegenkonto, bu, buchungsdatum, rechnungsnummer, buchungstext, beleglink
+CREATE TABLE AccountingItems (
id INT IDENTITY(1,1) PRIMARY KEY,
- user_id INT NOT NULL,
- theme NVARCHAR(50) DEFAULT 'light',
- language NVARCHAR(10) DEFAULT 'en',
- notifications_enabled BIT DEFAULT 1,
- created_at DATETIME2 DEFAULT GETDATE(),
- updated_at DATETIME2 DEFAULT GETDATE(),
- FOREIGN KEY (user_id) REFERENCES Users(id) ON DELETE CASCADE
+ umsatz_brutto DECIMAL(15,2) NOT NULL, -- gross turnover amount
+ soll_haben_kz CHAR(1) NOT NULL CHECK (soll_haben_kz IN ('S', 'H')), -- S = eingang (debit), H = ausgang (credit)
+ konto NVARCHAR(10) NOT NULL, -- account (e.g. 5400 = wareneingang 19%)
+ gegenkonto NVARCHAR(50) NOT NULL, -- counter account references Kreditor(kreditorId)
+ bu NVARCHAR(10), -- tax code (9 = 19%vst, 8 = 7%vst, 506 = dienstleistung aus EU, 511 = dienstleistung ausserhalb EU)
+ buchungsdatum DATE NOT NULL, -- booking date
+ rechnungsnummer NVARCHAR(100), -- invoice number (belegfeld 1)
+ buchungstext NVARCHAR(500), -- booking text (supplier/customer name + purpose)
+ beleglink NVARCHAR(500) -- document link
);
+-- Create Konto table
+CREATE TABLE Konto (
+ id INT IDENTITY(1,1) PRIMARY KEY,
+ konto NVARCHAR(10) NOT NULL,
+ name NVARCHAR(255) NOT NULL
+);
+
+-- Create BU table
+CREATE TABLE BU (
+ id INT IDENTITY(1,1) PRIMARY KEY,
+ bu NVARCHAR(10) NOT NULL
+ name NVARCHAR(255) NOT NULL
+);
+
+/*
+CSV
+ umsatz brutto ,
+ soll / haben kz ( S = eingang, H = ausgang),
+ ,,,
+ konto (XXXX , z.b. 5400 = wareneingang 19%),
+ gegenkonto (70XXX),
+ bu (9 = 19%vst , 8 = 7%vst, 506 = dienstleistung aus EU, 511 = dienstleistung ausserhalb EU),
+ buchungsdatum, (MDD)
+ rechnungsnummer (belegfeld 1),
+ ,,
+ buchungstext (lierferantenname / kundenname , + verwendungszweck)
+ ,,,,,
+ beleglink
+
+
+ --
+ nicht abziehbare vorstreuer buchen auf 5600
+
+
+
+
+*/
+
-- Create indexes for better performance
CREATE INDEX IX_Users_Email ON Users(email);
CREATE INDEX IX_Users_GoogleId ON Users(google_id);
CREATE INDEX IX_UserPreferences_UserId ON UserPreferences(user_id);
+CREATE INDEX IX_Kreditor_IBAN ON Kreditor(iban);
+CREATE INDEX IX_Kreditor_KreditorId ON Kreditor(kreditorId);
+CREATE INDEX IX_AccountingItems_Buchungsdatum ON AccountingItems(buchungsdatum);
+CREATE INDEX IX_AccountingItems_Konto ON AccountingItems(konto);
+CREATE INDEX IX_AccountingItems_Rechnungsnummer ON AccountingItems(rechnungsnummer);
+CREATE INDEX IX_AccountingItems_SollHabenKz ON AccountingItems(soll_haben_kz);
+
+-- Add FK from AccountingItems.bu -> BU(bu)
+ALTER TABLE AccountingItems
+ADD CONSTRAINT FK_AccountingItems_BU_BU
+FOREIGN KEY (bu) REFERENCES BU(bu);
+
+-- Add FK from AccountingItems.gegenkonto -> Kreditor(kreditorId)
+ALTER TABLE AccountingItems
+ADD CONSTRAINT FK_AccountingItems_Gegenkonto_Kreditor
+FOREIGN KEY (gegenkonto) REFERENCES Kreditor(kreditorId);
+
+-- Add FK from AccountingItems.konto -> Konto(konto)
+ALTER TABLE AccountingItems
+ADD CONSTRAINT FK_AccountingItems_Konto_Konto
+FOREIGN KEY (konto) REFERENCES Konto(konto);
-- Insert sample data (optional)
-- Note: This will only work after you have real Google user data
diff --git a/src/routes/data.js b/src/routes/data.js
index e6ed0c8..e5e7100 100644
--- a/src/routes/data.js
+++ b/src/routes/data.js
@@ -558,4 +558,183 @@ router.get('/pdf/pdfobject/:kPdfObjekt', authenticateToken, async (req, res) =>
}
});
+// Kreditor API endpoints
+
+// Get all kreditors
+router.get('/kreditors', authenticateToken, async (req, res) => {
+ try {
+ const { executeQuery } = require('../config/database');
+ const query = `
+ SELECT id, iban, name, kreditorId, created_at, updated_at
+ FROM Kreditor
+ WHERE is_active = 1
+ ORDER BY name ASC
+ `;
+
+ const result = await executeQuery(query);
+ res.json(result.recordset || []);
+ } catch (error) {
+ console.error('Error fetching kreditors:', error);
+ res.status(500).json({ error: 'Failed to fetch kreditors' });
+ }
+});
+
+// Get kreditor by ID
+router.get('/kreditors/:id', authenticateToken, async (req, res) => {
+ try {
+ const { executeQuery } = require('../config/database');
+ const { id } = req.params;
+
+ const query = `
+ SELECT id, iban, name, kreditorId, created_at, updated_at
+ FROM Kreditor
+ WHERE id = @id AND is_active = 1
+ `;
+
+ const result = await executeQuery(query, [
+ { name: 'id', type: 'int', value: parseInt(id) }
+ ]);
+
+ if (result.recordset.length === 0) {
+ return res.status(404).json({ error: 'Kreditor not found' });
+ }
+
+ res.json(result.recordset[0]);
+ } catch (error) {
+ console.error('Error fetching kreditor:', error);
+ res.status(500).json({ error: 'Failed to fetch kreditor' });
+ }
+});
+
+// Create new kreditor
+router.post('/kreditors', authenticateToken, async (req, res) => {
+ try {
+ const { executeQuery } = require('../config/database');
+ const { iban, name, kreditorId } = req.body;
+
+ // Validate required fields
+ if (!iban || !name || !kreditorId) {
+ return res.status(400).json({ error: 'IBAN, name, and kreditorId are required' });
+ }
+
+ // Check if kreditor with same IBAN or kreditorId already exists
+ const checkQuery = `
+ SELECT id FROM Kreditor
+ WHERE (iban = @iban OR kreditorId = @kreditorId) AND is_active = 1
+ `;
+
+ const checkResult = await executeQuery(checkQuery, [
+ { name: 'iban', type: 'nvarchar', value: iban },
+ { name: 'kreditorId', type: 'nvarchar', value: kreditorId }
+ ]);
+
+ if (checkResult.recordset.length > 0) {
+ return res.status(409).json({ error: 'Kreditor with this IBAN or kreditorId already exists' });
+ }
+
+ const insertQuery = `
+ INSERT INTO Kreditor (iban, name, kreditorId, created_at, updated_at)
+ OUTPUT INSERTED.id, INSERTED.iban, INSERTED.name, INSERTED.kreditorId, INSERTED.created_at, INSERTED.updated_at
+ VALUES (@iban, @name, @kreditorId, GETDATE(), GETDATE())
+ `;
+
+ const result = await executeQuery(insertQuery, [
+ { name: 'iban', type: 'nvarchar', value: iban },
+ { name: 'name', type: 'nvarchar', value: name },
+ { name: 'kreditorId', type: 'nvarchar', value: kreditorId }
+ ]);
+
+ res.status(201).json(result.recordset[0]);
+ } catch (error) {
+ console.error('Error creating kreditor:', error);
+ res.status(500).json({ error: 'Failed to create kreditor' });
+ }
+});
+
+// Update kreditor
+router.put('/kreditors/:id', authenticateToken, async (req, res) => {
+ try {
+ const { executeQuery } = require('../config/database');
+ const { id } = req.params;
+ const { iban, name, kreditorId } = req.body;
+
+ // Validate required fields
+ if (!iban || !name || !kreditorId) {
+ return res.status(400).json({ error: 'IBAN, name, and kreditorId are required' });
+ }
+
+ // Check if kreditor exists
+ const checkQuery = `SELECT id FROM Kreditor WHERE id = @id AND is_active = 1`;
+ const checkResult = await executeQuery(checkQuery, [
+ { name: 'id', type: 'int', value: parseInt(id) }
+ ]);
+
+ if (checkResult.recordset.length === 0) {
+ return res.status(404).json({ error: 'Kreditor not found' });
+ }
+
+ // Check for conflicts with other kreditors
+ const conflictQuery = `
+ SELECT id FROM Kreditor
+ WHERE (iban = @iban OR kreditorId = @kreditorId) AND id != @id AND is_active = 1
+ `;
+
+ const conflictResult = await executeQuery(conflictQuery, [
+ { name: 'iban', type: 'nvarchar', value: iban },
+ { name: 'kreditorId', type: 'nvarchar', value: kreditorId },
+ { name: 'id', type: 'int', value: parseInt(id) }
+ ]);
+
+ if (conflictResult.recordset.length > 0) {
+ return res.status(409).json({ error: 'Another kreditor with this IBAN or kreditorId already exists' });
+ }
+
+ const updateQuery = `
+ UPDATE Kreditor
+ SET iban = @iban, name = @name, kreditorId = @kreditorId, updated_at = GETDATE()
+ OUTPUT INSERTED.id, INSERTED.iban, INSERTED.name, INSERTED.kreditorId, INSERTED.created_at, INSERTED.updated_at
+ WHERE id = @id
+ `;
+
+ const result = await executeQuery(updateQuery, [
+ { name: 'iban', type: 'nvarchar', value: iban },
+ { name: 'name', type: 'nvarchar', value: name },
+ { name: 'kreditorId', type: 'nvarchar', value: kreditorId },
+ { name: 'id', type: 'int', value: parseInt(id) }
+ ]);
+
+ res.json(result.recordset[0]);
+ } catch (error) {
+ console.error('Error updating kreditor:', error);
+ res.status(500).json({ error: 'Failed to update kreditor' });
+ }
+});
+
+// Delete kreditor (soft delete)
+router.delete('/kreditors/:id', authenticateToken, async (req, res) => {
+ try {
+ const { executeQuery } = require('../config/database');
+ const { id } = req.params;
+
+ const query = `
+ UPDATE Kreditor
+ SET is_active = 0, updated_at = GETDATE()
+ WHERE id = @id AND is_active = 1
+ `;
+
+ const result = await executeQuery(query, [
+ { name: 'id', type: 'int', value: parseInt(id) }
+ ]);
+
+ if (result.rowsAffected[0] === 0) {
+ return res.status(404).json({ error: 'Kreditor not found' });
+ }
+
+ res.json({ message: 'Kreditor deleted successfully' });
+ } catch (error) {
+ console.error('Error deleting kreditor:', error);
+ res.status(500).json({ error: 'Failed to delete kreditor' });
+ }
+});
+
module.exports = router;
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
index e2de45e..28dba89 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -45,7 +45,7 @@ module.exports = {
REACT_APP_GOOGLE_CLIENT_ID: JSON.stringify(process.env.GOOGLE_CLIENT_ID),
},
}),
- new ReactRefreshWebpackPlugin(),
+ ...(process.env.NODE_ENV === 'development' ? [new ReactRefreshWebpackPlugin()] : []),
],
devServer: {
static: {