u
This commit is contained in:
@@ -6,6 +6,17 @@ import CircularProgress from '@mui/material/CircularProgress';
|
||||
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined';
|
||||
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { PanelTile, PanelTileGrid } from 'dashgui/panels';
|
||||
import BackendError from '../../components/BackendError';
|
||||
@@ -24,6 +35,7 @@ import {
|
||||
TextEditorPanel,
|
||||
PdfViewerPanel,
|
||||
ArticleSearchDialog,
|
||||
BookDeliveryDialog,
|
||||
} from '../../components/manifest';
|
||||
import ManifestPdfDialog from '../../components/manifest/ManifestPdfDialog';
|
||||
import { loadParties } from '../../components/manifest/manifestConfig';
|
||||
@@ -36,7 +48,8 @@ import { generateTextThumbnail } from '../../utils/textThumbnail';
|
||||
import { extractPdfText } from '../../utils/pdfText';
|
||||
import { getDeliveryEditorAutomat } from '../../automats/deliveryEditorAutomat';
|
||||
import { setLastDeliveryPath, userSettingsAutomat, setViewerPosition } from '../../automats';
|
||||
import { delivery as deliveryApi } from '../../api';
|
||||
import { delivery as deliveryApi, ledger as defaultLedgerApi } from '../../api';
|
||||
import { fetchDeliveryLedgerSummary } from '../../utils/deliveryLedger';
|
||||
|
||||
class LieferungNeuPage extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
@@ -63,18 +76,26 @@ class LieferungNeuPage extends PureComponent {
|
||||
articleSearchDialogOpen: false,
|
||||
articleSearchLine: null,
|
||||
unmatchedSupplierName: null,
|
||||
ledgerSummary: null,
|
||||
bookDeliveryDialogOpen: false,
|
||||
bookDeliveryPrefillLineId: null,
|
||||
};
|
||||
this._cancelled = false;
|
||||
this.handleRetry = this.handleRetry.bind(this);
|
||||
this.handlePartyChange = this.handlePartyChange.bind(this);
|
||||
this.handleCreateSupplierFromPdf = this.handleCreateSupplierFromPdf.bind(this);
|
||||
this.handleDateChange = this.handleDateChange.bind(this);
|
||||
this.handleOpenBookDialog = this.handleOpenBookDialog.bind(this);
|
||||
this.handleBookLine = this.handleBookLine.bind(this);
|
||||
this.handleCloseBookDialog = this.handleCloseBookDialog.bind(this);
|
||||
this.handleBookSuccess = this.handleBookSuccess.bind(this);
|
||||
|
||||
this.handleNumberChange = this.handleNumberChange.bind(this);
|
||||
this.handleExternalNumberChange = this.handleExternalNumberChange.bind(this);
|
||||
this.handleLineChange = this.handleLineChange.bind(this);
|
||||
this.handleAddLine = this.handleAddLine.bind(this);
|
||||
this.handleRemoveLine = this.handleRemoveLine.bind(this);
|
||||
this.handleRemoveAllLines = this.handleRemoveAllLines.bind(this);
|
||||
this.handleSave = this.handleSave.bind(this);
|
||||
this.handlePdfUpload = this.handlePdfUpload.bind(this);
|
||||
this.handlePdfRescan = this.handlePdfRescan.bind(this);
|
||||
@@ -206,9 +227,22 @@ class LieferungNeuPage extends PureComponent {
|
||||
const attachments = await restoreAttachmentsFromApi(apiFiles, this.state.attachments);
|
||||
this.automat.actions.setAttachments(attachments);
|
||||
|
||||
let ledgerSummary = null;
|
||||
try {
|
||||
const ledgerClient = this.context?.ledger || defaultLedgerApi;
|
||||
const deliveryClient = this.context?.[cfg.api] || deliveryApi;
|
||||
ledgerSummary = await fetchDeliveryLedgerSummary(docId, {
|
||||
ledgerApi: ledgerClient,
|
||||
deliveryApi: deliveryClient,
|
||||
});
|
||||
} catch (lErr) {
|
||||
console.error('[neu] Failed to load ledger summary:', lErr);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
status: 'ready',
|
||||
partiesLoading: false,
|
||||
ledgerSummary,
|
||||
});
|
||||
} catch (err) {
|
||||
if (this._cancelled) return;
|
||||
@@ -220,6 +254,43 @@ class LieferungNeuPage extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleOpenBookDialog() {
|
||||
this.setState({ bookDeliveryDialogOpen: true, bookDeliveryPrefillLineId: null });
|
||||
}
|
||||
|
||||
handleBookLine(ledgerLine) {
|
||||
this.setState({
|
||||
bookDeliveryDialogOpen: true,
|
||||
bookDeliveryPrefillLineId: ledgerLine?.id ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
handleCloseBookDialog() {
|
||||
this.setState({ bookDeliveryDialogOpen: false, bookDeliveryPrefillLineId: null });
|
||||
}
|
||||
|
||||
async reloadLedgerSummary() {
|
||||
const { docId } = this.props;
|
||||
if (docId == null) return;
|
||||
try {
|
||||
const ledgerClient = this.context?.ledger || defaultLedgerApi;
|
||||
const deliveryClient = this.context?.[this.cfg.api] || deliveryApi;
|
||||
const ledgerSummary = await fetchDeliveryLedgerSummary(docId, {
|
||||
ledgerApi: ledgerClient,
|
||||
deliveryApi: deliveryClient,
|
||||
});
|
||||
if (this._cancelled) return;
|
||||
this.setState({ ledgerSummary });
|
||||
} catch (err) {
|
||||
console.error('[neu] Failed to reload ledger summary:', err);
|
||||
}
|
||||
}
|
||||
|
||||
handleBookSuccess(updatedSummary) {
|
||||
this.setState({ ledgerSummary: updatedSummary });
|
||||
void this.reloadLedgerSummary();
|
||||
}
|
||||
|
||||
handleRetry() {
|
||||
if (this.props.docId != null) void this.loadEdit();
|
||||
else void this.loadPartiesOnly();
|
||||
@@ -271,7 +342,9 @@ class LieferungNeuPage extends PureComponent {
|
||||
key,
|
||||
[field]: value,
|
||||
...(field === 'net' ? { netprice: value } : {}),
|
||||
...(field === 'netprice' ? { net: value } : {}),
|
||||
...(field === 'tax' ? { taxrate: value } : {}),
|
||||
...(field === 'taxrate' ? { tax: value } : {}),
|
||||
});
|
||||
|
||||
if (field === 'sku') {
|
||||
@@ -339,7 +412,7 @@ class LieferungNeuPage extends PureComponent {
|
||||
entityId: partyId,
|
||||
sku: supplierSku || undefined,
|
||||
pu: line.pu ? Number(line.pu) : undefined,
|
||||
stock: line.amount ? Number(line.amount) : undefined,
|
||||
stock: 0,
|
||||
netPrice: line.netprice ? Number(line.netprice) : undefined,
|
||||
taxRate: line.taxrate ? Number(line.taxrate) : undefined,
|
||||
});
|
||||
@@ -389,7 +462,7 @@ class LieferungNeuPage extends PureComponent {
|
||||
entityId: partyId,
|
||||
sku: supplierSku || undefined,
|
||||
pu: line?.pu ? Number(line.pu) : undefined,
|
||||
stock: line?.amount ? Number(line.amount) : undefined,
|
||||
stock: 0,
|
||||
netPrice: line?.netprice ? Number(line.netprice) : undefined,
|
||||
taxRate: line?.taxrate ? Number(line.taxrate) : undefined,
|
||||
});
|
||||
@@ -445,6 +518,10 @@ class LieferungNeuPage extends PureComponent {
|
||||
this.automat.actions.removeLineitem(index >= 0 ? index : key);
|
||||
}
|
||||
|
||||
handleRemoveAllLines() {
|
||||
this.automat.actions.clearLineitems();
|
||||
}
|
||||
|
||||
async handlePdfUpload(file) {
|
||||
if (!file || this.state.pdfUploading) return;
|
||||
|
||||
@@ -661,18 +738,26 @@ class LieferungNeuPage extends PureComponent {
|
||||
? result.items.items
|
||||
: null;
|
||||
const newLines = Array.isArray(rawItems)
|
||||
? rawItems.map((l, idx) => ({
|
||||
key: l.key ?? `${Date.now()}-${idx}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
name: l.name ?? l.articleName ?? null,
|
||||
sku: l.sku ?? l.liefArtNr ?? '',
|
||||
amount: (l.amount ?? l.count) != null ? String(l.amount ?? l.count) : '',
|
||||
pu: (l.pu ?? l.vpe) != null ? String(l.pu ?? l.vpe) : '',
|
||||
unit: l.unit ?? '',
|
||||
taxrate: (l.taxrate ?? l.tax ?? l.mwst) != null ? String(l.taxrate ?? l.tax ?? l.mwst) : '',
|
||||
netprice: (l.netprice ?? l.net ?? (l.eknetto != null ? (l.eknetto / 100).toFixed(2) : null)) != null
|
||||
? String(l.netprice ?? l.net ?? (l.eknetto != null ? (l.eknetto / 100).toFixed(2) : ''))
|
||||
: '',
|
||||
}))
|
||||
? rawItems.map((l, idx) => {
|
||||
const taxVal = (l.taxrate ?? l.tax ?? l.taxRate ?? l.mwst) != null
|
||||
? String(l.taxrate ?? l.tax ?? l.taxRate ?? l.mwst)
|
||||
: '';
|
||||
const netVal = (l.netprice ?? l.net ?? l.netPrice ?? (l.eknetto != null ? (l.eknetto / 100).toFixed(2) : null)) != null
|
||||
? String(l.netprice ?? l.net ?? l.netPrice ?? (l.eknetto != null ? (l.eknetto / 100).toFixed(2) : ''))
|
||||
: '';
|
||||
return {
|
||||
key: l.key ?? `${Date.now()}-${idx}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
name: l.name ?? l.articleName ?? null,
|
||||
sku: l.sku ?? l.liefArtNr ?? '',
|
||||
amount: (l.amount ?? l.count) != null ? String(l.amount ?? l.count) : '',
|
||||
pu: (l.pu ?? l.vpe) != null ? String(l.pu ?? l.vpe) : '',
|
||||
unit: l.unit ?? '',
|
||||
taxrate: taxVal,
|
||||
tax: taxVal,
|
||||
netprice: netVal,
|
||||
net: netVal,
|
||||
};
|
||||
})
|
||||
: null;
|
||||
|
||||
this.setState((prev) => ({
|
||||
@@ -1108,6 +1193,7 @@ class LieferungNeuPage extends PureComponent {
|
||||
if (doc.number != null) this.automat.actions.setNumber(doc.number);
|
||||
if (doc.externalNumber != null) this.automat.actions.setExternalNumber(String(doc.externalNumber));
|
||||
this.automat.actions.setLineitems(lines);
|
||||
void this.reloadLedgerSummary();
|
||||
|
||||
// Restore attachments with thumbnails
|
||||
const apiFiles = doc.files ?? [];
|
||||
@@ -1157,18 +1243,15 @@ class LieferungNeuPage extends PureComponent {
|
||||
return <BackendError error={error} onRetry={this.handleRetry} />;
|
||||
}
|
||||
|
||||
const ledgerLinesById = {};
|
||||
if (Array.isArray(this.state.ledgerSummary?.lines)) {
|
||||
for (const l of this.state.ledgerSummary.lines) {
|
||||
ledgerLinesById[l.id] = l;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitleRight>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={saving}
|
||||
onClick={this.handleSave}
|
||||
>
|
||||
{saving ? 'Speichern…' : 'Speichern'}
|
||||
</Button>
|
||||
</PageTitleRight>
|
||||
<PanelTileGrid>
|
||||
<ManifestHeader
|
||||
kind="delivery"
|
||||
@@ -1184,95 +1267,73 @@ class LieferungNeuPage extends PureComponent {
|
||||
saving={saving}
|
||||
pdfUploading={pdfUploading}
|
||||
attachments={attachments}
|
||||
lines={lines}
|
||||
onPdfUpload={this.handlePdfUpload}
|
||||
onPdfRescan={this.handlePdfRescan}
|
||||
onPdfDelete={this.handlePdfDelete}
|
||||
onAttachmentClick={this.handleAttachmentClick}
|
||||
onSave={this.handleSave}
|
||||
onPartyChange={this.handlePartyChange}
|
||||
onDateChange={this.handleDateChange}
|
||||
onNumberChange={this.handleNumberChange}
|
||||
onExternalNumberChange={this.handleExternalNumberChange}
|
||||
docId={this.props.docId}
|
||||
ledgerSummary={this.state.ledgerSummary}
|
||||
onOpenBookDialog={this.handleOpenBookDialog}
|
||||
/>
|
||||
{this.state.viewerPosition === 'top' && this.renderViewerPanels(3)}
|
||||
{partyId == null && (!lines || lines.length === 0) ? (
|
||||
<PanelTile wide={3} minHeight={160} color="#e2e8f0">
|
||||
{this.state.unmatchedSupplierName ? (
|
||||
<Stack spacing={2} sx={{ p: 1 }}>
|
||||
<Typography color="text.secondary">
|
||||
Lieferant <strong>„{this.state.unmatchedSupplierName}“</strong> wurde im Dokument erkannt, existiert aber noch nicht in den Stammdaten.
|
||||
</Typography>
|
||||
<Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={() => this.handleCreateSupplierFromPdf(this.state.unmatchedSupplierName)}
|
||||
>
|
||||
Lieferant „{this.state.unmatchedSupplierName}“ anlegen
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
) : (
|
||||
<Typography color="text.secondary">
|
||||
{cfg.partyGateMessage}
|
||||
</Typography>
|
||||
)}
|
||||
</PanelTile>
|
||||
) : (
|
||||
<>
|
||||
{partyId == null && (
|
||||
<PanelTile wide={3} color="#fef3c7">
|
||||
<Alert
|
||||
severity="warning"
|
||||
action={
|
||||
this.state.unmatchedSupplierName ? (
|
||||
<Button
|
||||
color="inherit"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
onClick={() => this.handleCreateSupplierFromPdf(this.state.unmatchedSupplierName)}
|
||||
>
|
||||
Lieferant anlegen
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
sx={{ py: 0.5, alignItems: 'center' }}
|
||||
{this.state.viewerPosition === 'top' && this.renderViewerPanels(4)}
|
||||
{this.state.unmatchedSupplierName && (
|
||||
<PanelTile wide={4} color="#fef3c7" sx={{ gridColumn: { xs: '1 / -1', sm: '1 / -1', md: 'span 4' } }}>
|
||||
<Alert
|
||||
severity="warning"
|
||||
action={
|
||||
<Button
|
||||
color="inherit"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
onClick={() => this.handleCreateSupplierFromPdf(this.state.unmatchedSupplierName)}
|
||||
>
|
||||
{this.state.unmatchedSupplierName
|
||||
? `Lieferant „${this.state.unmatchedSupplierName}“ wurde im Dokument erkannt, existiert aber noch nicht in den Stammdaten.`
|
||||
: 'Bitte wählen Sie oben einen Lieferanten aus, um die erfassten Positionen speichern zu können.'}
|
||||
</Alert>
|
||||
</PanelTile>
|
||||
)}
|
||||
<ManifestLines
|
||||
columns={DELIVERY_LINE_COLUMNS}
|
||||
lines={lines}
|
||||
saving={saving}
|
||||
wide={
|
||||
this.state.viewerPosition === 'right' && this.isViewerOpen()
|
||||
? 2
|
||||
: 3
|
||||
Lieferant anlegen
|
||||
</Button>
|
||||
}
|
||||
linesColor={cfg.linesColor}
|
||||
supplierArticlesMap={this.state.supplierArticlesMap}
|
||||
supplierArticlesByLineKey={this.state.supplierArticlesByLineKey}
|
||||
supplierArticlesLoading={this.state.supplierArticlesLoading}
|
||||
loadingLineKeys={this.state.loadingLineKeys}
|
||||
onLineChange={this.handleLineChange}
|
||||
onSkuBlur={this.handleSkuBlur}
|
||||
onAddLine={this.handleAddLine}
|
||||
onRemoveLine={this.handleRemoveLine}
|
||||
onSave={() => void this.handleSave()}
|
||||
onCreateArticle={this.handleCreateArticleForLine}
|
||||
onSearchArticle={this.handleOpenArticleSearchForLine}
|
||||
onRemoveSupplierArticle={this.handleRemoveSupplierArticle}
|
||||
busySupplierArticleLineKeys={this.state.busySupplierArticleLineKeys}
|
||||
/>
|
||||
</>
|
||||
sx={{ py: 0.5, alignItems: 'center' }}
|
||||
>
|
||||
Lieferant „{this.state.unmatchedSupplierName}“ wurde im Dokument erkannt, existiert aber noch nicht in den Stammdaten.
|
||||
</Alert>
|
||||
</PanelTile>
|
||||
)}
|
||||
<ManifestLines
|
||||
columns={DELIVERY_LINE_COLUMNS}
|
||||
lines={lines}
|
||||
saving={saving}
|
||||
wide={
|
||||
this.state.viewerPosition === 'right' && this.isViewerOpen()
|
||||
? 2
|
||||
: 4
|
||||
}
|
||||
linesColor={cfg.linesColor}
|
||||
supplierArticlesMap={this.state.supplierArticlesMap}
|
||||
supplierArticlesByLineKey={this.state.supplierArticlesByLineKey}
|
||||
supplierArticlesLoading={this.state.supplierArticlesLoading}
|
||||
loadingLineKeys={this.state.loadingLineKeys}
|
||||
onLineChange={this.handleLineChange}
|
||||
onSkuBlur={this.handleSkuBlur}
|
||||
onAddLine={this.handleAddLine}
|
||||
onRemoveLine={this.handleRemoveLine}
|
||||
onRemoveAllLines={this.handleRemoveAllLines}
|
||||
onSave={() => void this.handleSave()}
|
||||
onCreateArticle={this.handleCreateArticleForLine}
|
||||
onSearchArticle={this.handleOpenArticleSearchForLine}
|
||||
onRemoveSupplierArticle={this.handleRemoveSupplierArticle}
|
||||
busySupplierArticleLineKeys={this.state.busySupplierArticleLineKeys}
|
||||
ledgerLinesById={ledgerLinesById}
|
||||
onBookLine={this.handleBookLine}
|
||||
/>
|
||||
{this.state.viewerPosition === 'right' && this.renderViewerPanels(2)}
|
||||
{this.state.viewerPosition === 'below' && this.renderViewerPanels(3)}
|
||||
{this.state.viewerPosition === 'below' && this.renderViewerPanels(4)}
|
||||
{this.props.docId != null && this.renderLedgerPanel()}
|
||||
{notes && notes.length > 0 && (
|
||||
<PanelTile wide={3} color="#fffbeb">
|
||||
<PanelTile wide={4} color="#fffbeb" sx={{ gridColumn: { xs: '1 / -1', sm: '1 / -1', md: 'span 4' } }}>
|
||||
<Typography variant="subtitle2" fontWeight={700} color="warning.dark" sx={{ mb: 1.5 }}>
|
||||
Hinweise zur PDF-Analyse ({notes.length})
|
||||
</Typography>
|
||||
@@ -1302,9 +1363,240 @@ class LieferungNeuPage extends PureComponent {
|
||||
onClose={this.handleCloseArticleSearch}
|
||||
onAssign={this.handleAssignArticleForLine}
|
||||
/>
|
||||
|
||||
{this.props.docId != null && (
|
||||
<BookDeliveryDialog
|
||||
open={this.state.bookDeliveryDialogOpen}
|
||||
delivery={{
|
||||
id: this.props.docId,
|
||||
number: this.state.number,
|
||||
externalNumber: this.state.externalNumber,
|
||||
date: this.state.date,
|
||||
supplierId: this.state.partyId,
|
||||
}}
|
||||
summary={this.state.ledgerSummary}
|
||||
prefillLineId={this.state.bookDeliveryPrefillLineId}
|
||||
partyName={this.state.deliveryEntities?.find((p) => p.id === this.state.partyId)?.name}
|
||||
onClose={this.handleCloseBookDialog}
|
||||
onBookSuccess={this.handleBookSuccess}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderLedgerPanel() {
|
||||
const { ledgerSummary } = this.state;
|
||||
if (!ledgerSummary) return null;
|
||||
|
||||
const {
|
||||
status,
|
||||
totalPositions,
|
||||
bookedPositions,
|
||||
totalQuantity,
|
||||
bookedQuantity,
|
||||
lines,
|
||||
} = ledgerSummary;
|
||||
|
||||
const allEntries = [];
|
||||
if (Array.isArray(lines)) {
|
||||
for (const line of lines) {
|
||||
if (Array.isArray(line.ledgerEntries)) {
|
||||
for (const entry of line.ledgerEntries) {
|
||||
allEntries.push({
|
||||
...entry,
|
||||
lineSku: line.sku,
|
||||
lineName: line.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
allEntries.sort((a, b) => String(b.created_at || '').localeCompare(String(a.created_at || '')));
|
||||
|
||||
return (
|
||||
<PanelTile
|
||||
wide={4}
|
||||
title="Lagerstatus (Ledger)"
|
||||
color="#f8fafc"
|
||||
sx={{
|
||||
gridColumn: { xs: '1 / -1', sm: '1 / -1', md: 'span 4' },
|
||||
border: '1px solid',
|
||||
borderColor:
|
||||
status === 'booked'
|
||||
? 'success.light'
|
||||
: status === 'partial'
|
||||
? 'warning.light'
|
||||
: 'divider',
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 3,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 3, alignItems: 'center' }}>
|
||||
<Box>
|
||||
<Typography variant="caption" color="text.secondary" display="block">
|
||||
Status
|
||||
</Typography>
|
||||
{status === 'booked' && (
|
||||
<Chip
|
||||
size="small"
|
||||
icon={<CheckCircleOutlineOutlinedIcon />}
|
||||
label="Vollständig gebucht"
|
||||
color="success"
|
||||
variant="outlined"
|
||||
sx={{ fontWeight: 600, height: 26 }}
|
||||
/>
|
||||
)}
|
||||
{status === 'partial' && (
|
||||
<Chip
|
||||
size="small"
|
||||
icon={<Inventory2OutlinedIcon />}
|
||||
label={`Teilweise gebucht (${bookedPositions}/${totalPositions})`}
|
||||
color="warning"
|
||||
variant="outlined"
|
||||
sx={{ fontWeight: 600, height: 26 }}
|
||||
/>
|
||||
)}
|
||||
{status === 'unbooked' && (
|
||||
<Chip
|
||||
size="small"
|
||||
label={`Offen (${totalPositions} Positionen)`}
|
||||
variant="outlined"
|
||||
sx={{ color: 'text.secondary', borderColor: 'divider', fontWeight: 500, height: 26 }}
|
||||
/>
|
||||
)}
|
||||
{status === 'empty' && (
|
||||
<Chip size="small" label="Keine Artikel" variant="outlined" />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography variant="caption" color="text.secondary" display="block">
|
||||
Positionen
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||||
{bookedPositions} / {totalPositions} gebucht
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography variant="caption" color="text.secondary" display="block">
|
||||
Menge
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||||
{bookedQuantity} / {totalQuantity} Stk. gebucht
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{totalQuantity > bookedQuantity && (
|
||||
<Box>
|
||||
<Typography variant="caption" color="text.secondary" display="block">
|
||||
Noch offen
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600, color: 'warning.dark' }}>
|
||||
{totalQuantity - bookedQuantity} Stk.
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
variant={status === 'booked' ? 'outlined' : 'contained'}
|
||||
color={status === 'booked' ? 'success' : status === 'partial' ? 'warning' : 'primary'}
|
||||
startIcon={
|
||||
status === 'booked' ? (
|
||||
<CheckCircleOutlineOutlinedIcon />
|
||||
) : (
|
||||
<Inventory2OutlinedIcon />
|
||||
)
|
||||
}
|
||||
onClick={this.handleOpenBookDialog}
|
||||
sx={{ fontWeight: 600, textTransform: 'none' }}
|
||||
>
|
||||
{status === 'booked'
|
||||
? 'Buchungsdetails anzeigen'
|
||||
: status === 'partial'
|
||||
? 'Restliche Mengen einbuchen'
|
||||
: 'Ins Lager buchen'}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{allEntries.length > 0 && (
|
||||
<Box>
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: 'text.secondary', mb: 1, display: 'block' }}>
|
||||
Bisherige Ledger-Buchungen ({allEntries.length}):
|
||||
</Typography>
|
||||
<TableContainer component={Paper} variant="outlined">
|
||||
<Table size="small">
|
||||
<TableHead sx={{ bgcolor: '#f1f5f9' }}>
|
||||
<TableRow>
|
||||
<TableCell sx={{ fontWeight: 600, py: 0.5 }}>Zeitpunkt</TableCell>
|
||||
<TableCell sx={{ fontWeight: 600, py: 0.5 }}>Typ</TableCell>
|
||||
<TableCell sx={{ fontWeight: 600, py: 0.5 }}>SKU / Artikel</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 600, py: 0.5 }}>Menge</TableCell>
|
||||
<TableCell sx={{ fontWeight: 600, py: 0.5 }}>Buchungstext</TableCell>
|
||||
<TableCell sx={{ fontWeight: 600, py: 0.5, fontFamily: 'monospace', fontSize: '0.75rem' }}>Prüfsumme (prev_hash)</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{allEntries.map((entry) => (
|
||||
<TableRow key={entry.id} hover>
|
||||
<TableCell sx={{ py: 0.5, whiteSpace: 'nowrap' }}>
|
||||
<Typography variant="caption">
|
||||
{entry.created_at ? new Date(entry.created_at).toLocaleString('de-DE') : '–'}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell sx={{ py: 0.5 }}>
|
||||
<Chip
|
||||
size="small"
|
||||
label={entry.transaction_type}
|
||||
color="success"
|
||||
variant="outlined"
|
||||
sx={{ height: 20, fontSize: '0.68rem', fontWeight: 600 }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell sx={{ py: 0.5 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, fontFamily: 'monospace' }}>
|
||||
{entry.lineSku || entry.sku || '–'}
|
||||
</Typography>
|
||||
{entry.lineName && (
|
||||
<Typography variant="caption" color="text.secondary" sx={{ ml: 1 }}>
|
||||
{entry.lineName}
|
||||
</Typography>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ py: 0.5, fontWeight: 700, color: 'success.main' }}>
|
||||
+{entry.amount}
|
||||
</TableCell>
|
||||
<TableCell sx={{ py: 0.5 }}>
|
||||
<Typography variant="caption">
|
||||
{entry.transaction_text || '–'}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell sx={{ py: 0.5, fontFamily: 'monospace', fontSize: '0.7rem', color: 'text.secondary' }}>
|
||||
<Tooltip title={entry.prev_hash}>
|
||||
<span>{entry.prev_hash ? `${entry.prev_hash.slice(0, 16)}…` : '–'}</span>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</PanelTile>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withNavigate(LieferungNeuPage);
|
||||
|
||||
Reference in New Issue
Block a user