From 20cd0b34bcd345ab9b8714e95bcdbee9db870628 Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Sat, 2 Aug 2025 09:14:49 +0200 Subject: [PATCH] Implement new Kreditor creation functionality in BankingKreditorSelector component. Add state management for new Kreditor details, validation, and error handling. Enhance transaction handling to support banking transactions without IBAN. Update UI to allow users to create new Kreditors directly from the selector, including ID generation and validation feedback. --- .../src/components/BankingKreditorSelector.js | 287 ++++++++++++++---- client/src/components/KreditorSelector.js | 25 +- .../cellRenderers/DocumentRenderer.js | 79 +++-- client/src/components/utils/dataUtils.js | 2 +- client/src/services/KreditorService.js | 3 +- 5 files changed, 316 insertions(+), 80 deletions(-) diff --git a/client/src/components/BankingKreditorSelector.js b/client/src/components/BankingKreditorSelector.js index 3d1bb3c..c36258c 100644 --- a/client/src/components/BankingKreditorSelector.js +++ b/client/src/components/BankingKreditorSelector.js @@ -11,7 +11,9 @@ import { CircularProgress, Typography, } from '@mui/material'; +import { Add as AddIcon } from '@mui/icons-material'; import AuthService from '../services/AuthService'; +import KreditorService from '../services/KreditorService'; class BankingKreditorSelector extends Component { constructor(props) { @@ -19,22 +21,46 @@ class BankingKreditorSelector extends Component { this.state = { assignableKreditors: [], selectedKreditorId: '', - notes: '', loading: false, error: null, saving: false, + showCreateKreditor: false, + newKreditor: { + name: '', + kreditorId: '' + }, + creating: false, + validationErrors: [] }; this.authService = new AuthService(); + this.kreditorService = new KreditorService(); } componentDidMount() { this.loadAssignableKreditors(); this.loadExistingAssignment(); + + // Pre-fill new kreditor data with description (actual company name) instead of Beguenstigter (banking service name) + const prefilledName = this.props.transaction?.description || ''; + if (prefilledName) { + this.setState({ + newKreditor: { + ...this.state.newKreditor, + name: prefilledName + } + }); + } } componentDidUpdate(prevProps) { - // Reload data when transaction changes - if (this.props.transaction?.id !== prevProps.transaction?.id) { + // Reload data when transaction changes (only for database transactions) + const currentTransactionId = this.props.transaction?.id; + const prevTransactionId = prevProps.transaction?.id; + + console.log('componentDidUpdate - current:', currentTransactionId, 'prev:', prevTransactionId); + + if (currentTransactionId !== prevTransactionId) { + console.log('Transaction changed, reloading assignment'); this.loadExistingAssignment(); } } @@ -63,9 +89,18 @@ class BankingKreditorSelector extends Component { }; loadExistingAssignment = async () => { - // For CSV transactions, we need to use csv_transaction_id instead of transaction_id - const transactionId = this.props.transaction?.id || this.props.transaction?.csv_id; - if (!transactionId) return; + // Only load assignments for regular database transactions, not CSV transactions + const transactionId = this.props.transaction?.id; + console.log('loadExistingAssignment called with:', { + transactionId, + csv_id: this.props.transaction?.csv_id, + fullTransaction: this.props.transaction + }); + + if (!transactionId) { + console.log('Skipping loadExistingAssignment - no transaction ID'); + return; + } try { const response = await this.authService.apiCall( @@ -78,7 +113,6 @@ class BankingKreditorSelector extends Component { const assignment = assignments[0]; this.setState({ selectedKreditorId: assignment.assigned_kreditor_id || '', - notes: assignment.notes || '', }); } } @@ -89,16 +123,80 @@ class BankingKreditorSelector extends Component { }; handleKreditorChange = (event) => { - this.setState({ selectedKreditorId: event.target.value }); + const value = event.target.value; + if (value === 'create_new') { + this.setState({ showCreateKreditor: true, selectedKreditorId: '' }); + } else { + this.setState({ selectedKreditorId: value, showCreateKreditor: false }); + } }; - handleNotesChange = (event) => { - this.setState({ notes: event.target.value }); + handleNewKreditorChange = (field, value) => { + this.setState({ + newKreditor: { + ...this.state.newKreditor, + [field]: value + }, + validationErrors: [] + }); + }; + + generateKreditorId = () => { + 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; + + // Create regular kreditor data (no IBAN because transaction was processed through banking account) + const kreditorDataToValidate = { + ...newKreditor, + iban: null, + is_banking: false, // This is a regular kreditor (actual company) that will be assigned to banking transactions + is_manual_assignment: true // This is a manual assignment for a banking transaction, so no IBAN is required + }; + + // Validate the data + const validationErrors = this.kreditorService.validateKreditorData(kreditorDataToValidate); + if (validationErrors.length > 0) { + this.setState({ validationErrors }); + return; + } + + this.setState({ creating: true, error: null }); + + try { + const createdKreditor = await this.kreditorService.createKreditor(kreditorDataToValidate); + + // Add the new kreditor to the list and select it + this.setState({ + assignableKreditors: [...this.state.assignableKreditors, createdKreditor], + selectedKreditorId: createdKreditor.id, + showCreateKreditor: false, + creating: false, + newKreditor: { name: '', kreditorId: '' }, + validationErrors: [] + }); + } catch (error) { + console.error('Error creating kreditor:', error); + this.setState({ + error: error.message, + creating: false + }); + } }; handleSave = async () => { const { transaction, user, onSave } = this.props; - const { selectedKreditorId, notes } = this.state; + const { selectedKreditorId } = this.state; if (!selectedKreditorId) { this.setState({ error: 'Bitte wählen Sie einen Kreditor aus' }); @@ -108,41 +206,52 @@ class BankingKreditorSelector extends Component { this.setState({ saving: true, error: null }); try { - // Check if assignment already exists - const checkResponse = await this.authService.apiCall( - `/data/banking-transactions/${transaction.id}` - ); - let response; - if (checkResponse && checkResponse.ok) { - const existingAssignments = await checkResponse.json(); + + if (transaction.id) { + // Check for existing assignment first + const checkResponse = await this.authService.apiCall( + `/data/banking-transactions/${transaction.id}` + ); - if (existingAssignments.length > 0) { - // Update existing assignment - response = await this.authService.apiCall( - `/data/banking-transactions/${existingAssignments[0].id}`, - 'PUT', - { - assigned_kreditor_id: parseInt(selectedKreditorId), - notes: notes.trim() || null, - assigned_by: user?.username || 'Unknown', - } - ); - } else { - // Create new assignment - response = await this.authService.apiCall( - '/data/banking-transactions', - 'POST', - { - transaction_id: transaction.id || null, - csv_transaction_id: transaction.csv_id || transaction.id || null, - banking_iban: transaction['Kontonummer/IBAN'] || transaction.kontonummer_iban, - assigned_kreditor_id: parseInt(selectedKreditorId), - notes: notes.trim() || null, - assigned_by: user?.username || 'Unknown', - } - ); + if (checkResponse && checkResponse.ok) { + const existingAssignments = await checkResponse.json(); + + if (existingAssignments.length > 0) { + // Update existing assignment + response = await this.authService.apiCall( + `/data/banking-transactions/${existingAssignments[0].id}`, + { + method: 'PUT', + body: JSON.stringify({ + assigned_kreditor_id: parseInt(selectedKreditorId), + assigned_by: user?.username || 'Unknown', + }) + } + ); + } else { + // Create new assignment + response = await this.authService.apiCall( + '/data/banking-transactions', + { + method: 'POST', + body: JSON.stringify({ + transaction_id: transaction.isFromCSV ? null : transaction.id, + 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', + }) + } + ); + } } + } else { + this.setState({ + error: 'Transaktion hat keine gültige ID', + saving: false + }); + return; } if (response && response.ok) { @@ -170,10 +279,13 @@ class BankingKreditorSelector extends Component { const { assignableKreditors, selectedKreditorId, - notes, loading, error, - saving + saving, + showCreateKreditor, + newKreditor, + creating, + validationErrors } = this.state; if (loading) { @@ -210,20 +322,83 @@ class BankingKreditorSelector extends Component { {kreditor.name} ({kreditor.kreditorId}) ))} + + + Neuen Kreditor erstellen + - + {showCreateKreditor && ( + + + Neuen Kreditor erstellen: + + + {validationErrors.length > 0 && ( + +
    + {validationErrors.map((error, index) => ( +
  • {error}
  • + ))} +
+
+ )} + + this.handleNewKreditorChange('name', e.target.value)} + fullWidth + size="small" + sx={{ mb: 2 }} + placeholder="Name des Kreditors" + /> + + + this.handleNewKreditorChange('kreditorId', e.target.value)} + size="small" + placeholder="70001" + sx={{ flexGrow: 1 }} + /> + + + + + Die Kreditor-ID muss mit "70" beginnen, gefolgt von mindestens 3 Ziffern. + Keine IBAN erforderlich, da diese Transaktion über ein Banking-Konto abgewickelt wurde. + + + + + + +
+ )}