Enhance Accounting Items Management with JTL Kontierung Integration
- Added a new API route to fetch JTL Kontierung data based on transaction ID. - Implemented loading of JTL Kontierung data in the AccountingItemsManager component. - Updated UI to display JTL Kontierung data for debugging purposes. - Enhanced user feedback during processing tasks in the App component with tooltips and progress indicators.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||||
import CssBaseline from '@mui/material/CssBaseline';
|
import CssBaseline from '@mui/material/CssBaseline';
|
||||||
import { Container, AppBar, Toolbar, Typography, Button, Box, Tabs, Tab, Badge, Chip, Divider, Snackbar, Alert } from '@mui/material';
|
import { Container, AppBar, Toolbar, Typography, Button, Box, Tabs, Tab, Badge, Chip, Divider, Snackbar, Alert, LinearProgress, Tooltip, CircularProgress } from '@mui/material';
|
||||||
import LoginIcon from '@mui/icons-material/Login';
|
import LoginIcon from '@mui/icons-material/Login';
|
||||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||||
import DownloadIcon from '@mui/icons-material/Download';
|
import DownloadIcon from '@mui/icons-material/Download';
|
||||||
@@ -302,72 +302,87 @@ class App extends Component {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
<Divider orientation="vertical" flexItem sx={{ mx: 2, backgroundColor: 'rgba(255, 255, 255, 0.3)' }} />
|
<Divider orientation="vertical" flexItem sx={{ mx: 2, backgroundColor: 'rgba(255, 255, 255, 0.3)' }} />
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Button
|
<Tooltip title={processingStatus.markdown ? 'Running markdown conversion… this can take a while' : 'Process markdown conversion'} arrow>
|
||||||
color="inherit"
|
<span>
|
||||||
size="small"
|
<Button
|
||||||
onClick={() => this.handleProcessing('markdown')}
|
color="inherit"
|
||||||
disabled={processingStatus.markdown || !documentStatus}
|
size="small"
|
||||||
sx={{
|
onClick={() => this.handleProcessing('markdown')}
|
||||||
minWidth: 'auto',
|
disabled={processingStatus.markdown || !documentStatus}
|
||||||
px: 1,
|
sx={{
|
||||||
'&:hover': { backgroundColor: 'rgba(255, 255, 255, 0.1)' }
|
minWidth: 'auto',
|
||||||
}}
|
px: 1,
|
||||||
title="Process markdown conversion"
|
'&:hover': { backgroundColor: 'rgba(255, 255, 255, 0.1)' }
|
||||||
>
|
}}
|
||||||
<Badge
|
>
|
||||||
badgeContent={documentStatus?.needMarkdown || 0}
|
<Badge
|
||||||
color={documentStatus?.needMarkdown > 0 ? "error" : "default"}
|
badgeContent={documentStatus?.needMarkdown || 0}
|
||||||
max={999999}
|
color={documentStatus?.needMarkdown > 0 ? "error" : "default"}
|
||||||
sx={{ mr: 0.5 }}
|
max={999999}
|
||||||
>
|
sx={{ mr: 0.5 }}
|
||||||
<DocumentScannerIcon fontSize="small" />
|
>
|
||||||
</Badge>
|
<DocumentScannerIcon fontSize="small" />
|
||||||
{processingStatus.markdown && <PlayArrowIcon fontSize="small" />}
|
</Badge>
|
||||||
</Button>
|
{processingStatus.markdown && (
|
||||||
<Button
|
<CircularProgress size={14} color="inherit" />
|
||||||
color="inherit"
|
)}
|
||||||
size="small"
|
</Button>
|
||||||
onClick={() => this.handleProcessing('extraction')}
|
</span>
|
||||||
disabled={processingStatus.extraction || !documentStatus}
|
</Tooltip>
|
||||||
sx={{
|
<Tooltip title={processingStatus.extraction ? 'Running data extraction… this can take a while' : 'Process data extraction'} arrow>
|
||||||
minWidth: 'auto',
|
<span>
|
||||||
px: 1,
|
<Button
|
||||||
'&:hover': { backgroundColor: 'rgba(255, 255, 255, 0.1)' }
|
color="inherit"
|
||||||
}}
|
size="small"
|
||||||
title="Process data extraction"
|
onClick={() => this.handleProcessing('extraction')}
|
||||||
>
|
disabled={processingStatus.extraction || !documentStatus}
|
||||||
<Badge
|
sx={{
|
||||||
badgeContent={documentStatus?.needExtraction || 0}
|
minWidth: 'auto',
|
||||||
color={documentStatus?.needExtraction > 0 ? "warning" : "default"}
|
px: 1,
|
||||||
max={999999}
|
'&:hover': { backgroundColor: 'rgba(255, 255, 255, 0.1)' }
|
||||||
sx={{ mr: 0.5 }}
|
}}
|
||||||
>
|
>
|
||||||
<ExtractIcon fontSize="small" />
|
<Badge
|
||||||
</Badge>
|
badgeContent={documentStatus?.needExtraction || 0}
|
||||||
{processingStatus.extraction && <PlayArrowIcon fontSize="small" />}
|
color={documentStatus?.needExtraction > 0 ? "warning" : "default"}
|
||||||
</Button>
|
max={999999}
|
||||||
<Button
|
sx={{ mr: 0.5 }}
|
||||||
color="inherit"
|
>
|
||||||
size="small"
|
<ExtractIcon fontSize="small" />
|
||||||
onClick={() => this.handleProcessing('datev-sync')}
|
</Badge>
|
||||||
disabled={processingStatus.datevSync || !documentStatus}
|
{processingStatus.extraction && (
|
||||||
sx={{
|
<CircularProgress size={14} color="inherit" />
|
||||||
minWidth: 'auto',
|
)}
|
||||||
px: 1,
|
</Button>
|
||||||
'&:hover': { backgroundColor: 'rgba(255, 255, 255, 0.1)' }
|
</span>
|
||||||
}}
|
</Tooltip>
|
||||||
title="Process Datev sync"
|
<Tooltip title={processingStatus.datevSync ? 'Running DATEV sync… this can take a while' : 'Process Datev sync'} arrow>
|
||||||
>
|
<span>
|
||||||
<Badge
|
<Button
|
||||||
badgeContent={documentStatus?.needDatevSync || 0}
|
color="inherit"
|
||||||
color={documentStatus?.needDatevSync > 0 ? "info" : "default"}
|
size="small"
|
||||||
max={999999}
|
onClick={() => this.handleProcessing('datev-sync')}
|
||||||
sx={{ mr: 0.5 }}
|
disabled={processingStatus.datevSync || !documentStatus}
|
||||||
>
|
sx={{
|
||||||
<EmailIcon fontSize="small" />
|
minWidth: 'auto',
|
||||||
</Badge>
|
px: 1,
|
||||||
{processingStatus.datevSync && <PlayArrowIcon fontSize="small" />}
|
'&:hover': { backgroundColor: 'rgba(255, 255, 255, 0.1)' }
|
||||||
</Button>
|
}}
|
||||||
|
>
|
||||||
|
<Badge
|
||||||
|
badgeContent={documentStatus?.needDatevSync || 0}
|
||||||
|
color={documentStatus?.needDatevSync > 0 ? "info" : "default"}
|
||||||
|
max={999999}
|
||||||
|
sx={{ mr: 0.5 }}
|
||||||
|
>
|
||||||
|
<EmailIcon fontSize="small" />
|
||||||
|
</Badge>
|
||||||
|
{processingStatus.datevSync && (
|
||||||
|
<CircularProgress size={14} color="inherit" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
<Button
|
<Button
|
||||||
color="inherit"
|
color="inherit"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -426,6 +441,9 @@ class App extends Component {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
{(processingStatus.markdown || processingStatus.extraction || processingStatus.datevSync) && (
|
||||||
|
<LinearProgress color="secondary" />
|
||||||
|
)}
|
||||||
</AppBar>
|
</AppBar>
|
||||||
|
|
||||||
<Box sx={{ height: 'calc(100vh - 64px)', display: 'flex', flexDirection: 'column' }}>
|
<Box sx={{ height: 'calc(100vh - 64px)', display: 'flex', flexDirection: 'column' }}>
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class AccountingItemsManager extends Component {
|
|||||||
editingItem: null,
|
editingItem: null,
|
||||||
showCreateDialog: false,
|
showCreateDialog: false,
|
||||||
showCreateKontoDialog: false,
|
showCreateKontoDialog: false,
|
||||||
|
jtlKontierung: null,
|
||||||
newItem: {
|
newItem: {
|
||||||
umsatz_brutto: '',
|
umsatz_brutto: '',
|
||||||
soll_haben_kz: 'S',
|
soll_haben_kz: 'S',
|
||||||
@@ -65,6 +66,7 @@ class AccountingItemsManager extends Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.loadData();
|
this.loadData();
|
||||||
|
this.loadJtlKontierung();
|
||||||
}
|
}
|
||||||
|
|
||||||
loadData = async () => {
|
loadData = async () => {
|
||||||
@@ -88,6 +90,31 @@ class AccountingItemsManager extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
loadJtlKontierung = async () => {
|
||||||
|
try {
|
||||||
|
const { transaction } = this.props;
|
||||||
|
if (!transaction || !transaction.jtlId) {
|
||||||
|
this.setState({ jtlKontierung: undefined });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.authService.apiCall(`/data/jtl-kontierung/${transaction.jtlId}`);
|
||||||
|
if (!response) return;
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
this.setState({ jtlKontierung: data });
|
||||||
|
} else {
|
||||||
|
const err = await response.json();
|
||||||
|
console.error('Failed to load JTL Kontierung:', err);
|
||||||
|
this.setState({ jtlKontierung: undefined });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading JTL Kontierung:', e);
|
||||||
|
this.setState({ jtlKontierung: undefined });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loadAccountingItems = async () => {
|
loadAccountingItems = async () => {
|
||||||
const { transaction } = this.props;
|
const { transaction } = this.props;
|
||||||
if (!transaction?.id) return;
|
if (!transaction?.id) return;
|
||||||
@@ -319,6 +346,21 @@ class AccountingItemsManager extends Component {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{transaction?.jtlId && (
|
||||||
|
<Box sx={{ mb: 2, p: 2, border: '1px dashed #999', borderRadius: 1 }}>
|
||||||
|
<Typography variant="subtitle2">Debug: tUmsatzKontierung.data</Typography>
|
||||||
|
<Typography variant="caption" component="div" sx={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
||||||
|
{this.state.jtlKontierung === undefined
|
||||||
|
? 'undefined'
|
||||||
|
: this.state.jtlKontierung === null
|
||||||
|
? 'null'
|
||||||
|
: typeof this.state.jtlKontierung === 'object'
|
||||||
|
? JSON.stringify(this.state.jtlKontierung, null, 2)
|
||||||
|
: String(this.state.jtlKontierung)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
<Table size="small">
|
<Table size="small">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
|
|||||||
@@ -3,6 +3,39 @@ const { authenticateToken } = require('../../middleware/auth');
|
|||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Debug: Get JTL Kontierung data for a specific JTL Umsatz (by kZahlungsabgleichUmsatz)
|
||||||
|
router.get('/jtl-kontierung/:jtlId', authenticateToken, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { executeQuery } = require('../../config/database');
|
||||||
|
const { jtlId } = req.params;
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
SELECT
|
||||||
|
uk.data
|
||||||
|
FROM eazybusiness.dbo.tZahlungsabgleichUmsatz z
|
||||||
|
LEFT JOIN eazybusiness.dbo.tUmsatzKontierung uk
|
||||||
|
ON uk.kZahlungsabgleichUmsatz = z.kZahlungsabgleichUmsatz
|
||||||
|
WHERE z.kZahlungsabgleichUmsatz = @jtlId
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await executeQuery(query, { jtlId: parseInt(jtlId, 10) });
|
||||||
|
// Return undefined when no data found (do not lie with empty array/string)
|
||||||
|
if (!result.recordset || result.recordset.length === 0) {
|
||||||
|
return res.json({ data: undefined });
|
||||||
|
}
|
||||||
|
|
||||||
|
// If multiple rows exist, return all; otherwise single object
|
||||||
|
const rows = result.recordset.map(r => ({ data: r.data }));
|
||||||
|
if (rows.length === 1) {
|
||||||
|
return res.json(rows[0]);
|
||||||
|
}
|
||||||
|
return res.json(rows);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching JTL Kontierung data:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to fetch JTL Kontierung data' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Get accounting items for a specific transaction
|
// Get accounting items for a specific transaction
|
||||||
router.get('/accounting-items/:transactionId', authenticateToken, async (req, res) => {
|
router.get('/accounting-items/:transactionId', authenticateToken, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user