u
This commit is contained in:
@@ -8,7 +8,6 @@ import IconButton from '@mui/material/IconButton';
|
|||||||
import TableCell from '@mui/material/TableCell';
|
import TableCell from '@mui/material/TableCell';
|
||||||
import TableRow from '@mui/material/TableRow';
|
import TableRow from '@mui/material/TableRow';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
||||||
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
||||||
@@ -608,9 +607,6 @@ class ManifestFormLine extends PureComponent {
|
|||||||
: (rawWarehouses.length > 0 || isTracked
|
: (rawWarehouses.length > 0 || isTracked
|
||||||
? rawWarehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0)
|
? rawWarehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0)
|
||||||
: null));
|
: null));
|
||||||
const warehouseTooltip = rawWarehouses.length > 0
|
|
||||||
? rawWarehouses.map((w) => `${w.name || 'Lager'}: ${formatStock(w.stock)}`).join(' · ')
|
|
||||||
: (isTracked === false ? 'Keine Bestandsführung' : undefined);
|
|
||||||
const lineAmount = parseNum(getFirstVal(this.props.line?.amount, this.props.line?.menge)) ?? 0;
|
const lineAmount = parseNum(getFirstVal(this.props.line?.amount, this.props.line?.menge)) ?? 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -730,93 +726,65 @@ class ManifestFormLine extends PureComponent {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Bestand */}
|
{/* Bestand */}
|
||||||
<Tooltip
|
<Typography
|
||||||
title={
|
variant="caption"
|
||||||
warehouseTooltip
|
fontWeight={600}
|
||||||
? `Lagerbestand: ${formatStock(stockVal)} (${warehouseTooltip})`
|
sx={{
|
||||||
: isTracked === false
|
color:
|
||||||
? 'Keine Bestandsführung (unbegrenzt verfügbar)'
|
stockVal == null
|
||||||
: `Verfügbarer Lagerbestand: ${formatStock(stockVal)}`
|
? 'text.disabled'
|
||||||
}
|
: stockVal <= 0
|
||||||
|
? 'error.main'
|
||||||
|
: lineAmount > stockVal
|
||||||
|
? 'warning.main'
|
||||||
|
: 'text.secondary',
|
||||||
|
fontSize: '0.72rem',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
Bestand: {isTracked === false ? '∞' : formatStock(stockVal)}
|
||||||
variant="caption"
|
</Typography>
|
||||||
fontWeight={600}
|
|
||||||
sx={{
|
|
||||||
color:
|
|
||||||
stockVal == null
|
|
||||||
? 'text.disabled'
|
|
||||||
: stockVal <= 0
|
|
||||||
? 'error.main'
|
|
||||||
: lineAmount > stockVal
|
|
||||||
? 'warning.main'
|
|
||||||
: 'text.secondary',
|
|
||||||
fontSize: '0.72rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Bestand: {isTracked === false ? '∞' : formatStock(stockVal)}
|
|
||||||
</Typography>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* Marge */}
|
{/* Marge */}
|
||||||
<Tooltip
|
|
||||||
title={
|
<Typography
|
||||||
marge != null
|
variant="caption"
|
||||||
? `Marge: ${marge.toFixed(1).replace('.', ',')}% (EK Netto ${formatPrice(ekNetto)} → VK Netto ${formatPrice(vkNetto)})${rebate > 0 && effectiveMarge != null ? ` · Nach ${rebate}% Rabatt: ${effectiveMarge.toFixed(1).replace('.', ',')}%` : ''}`
|
fontWeight={600}
|
||||||
: 'Kein EK- oder VK-Preis hinterlegt'
|
sx={{
|
||||||
}
|
color: marge != null ? (marge < 0 ? 'error.main' : 'text.secondary') : 'text.disabled',
|
||||||
|
fontSize: '0.72rem',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
Marge: {marge != null ? `${Math.floor(marge)}%` : '—'}
|
||||||
variant="caption"
|
</Typography>
|
||||||
fontWeight={600}
|
|
||||||
sx={{
|
|
||||||
color: marge != null ? (marge < 0 ? 'error.main' : 'text.secondary') : 'text.disabled',
|
|
||||||
fontSize: '0.72rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Marge: {marge != null ? `${Math.floor(marge)}%` : '—'}
|
|
||||||
</Typography>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* EK Netto */}
|
{/* EK Netto */}
|
||||||
<Tooltip
|
|
||||||
title={
|
<Typography
|
||||||
topSupplier?.supplierName
|
variant="caption"
|
||||||
? `Lieferant: ${topSupplier.supplierName}${topSupplier.sku ? ` (Lief.-Art.-Nr.: ${topSupplier.sku})` : ''} (Sort: ${topSupplier.sort ?? 0})`
|
fontWeight={600}
|
||||||
: 'Kein Lieferant mit EK-Preis hinterlegt'
|
sx={{
|
||||||
}
|
color: ekNetto != null ? 'text.secondary' : 'text.disabled',
|
||||||
|
fontSize: '0.72rem',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
EK Netto: {ekNetto != null ? formatPrice(ekNetto) : '—'}
|
||||||
variant="caption"
|
</Typography>
|
||||||
fontWeight={600}
|
|
||||||
sx={{
|
|
||||||
color: ekNetto != null ? 'text.secondary' : 'text.disabled',
|
|
||||||
fontSize: '0.72rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
EK Netto: {ekNetto != null ? formatPrice(ekNetto) : '—'}
|
|
||||||
</Typography>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* EK Brutto */}
|
{/* EK Brutto */}
|
||||||
<Tooltip
|
|
||||||
title={
|
<Typography
|
||||||
topSupplier?.supplierName
|
variant="caption"
|
||||||
? `Lieferant: ${topSupplier.supplierName} (inkl. ${ekTaxRate}% MwSt.)`
|
fontWeight={600}
|
||||||
: 'Kein Lieferant mit EK-Preis hinterlegt'
|
sx={{
|
||||||
}
|
color: ekBrutto != null ? 'text.secondary' : 'text.disabled',
|
||||||
|
fontSize: '0.72rem',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
EK Brutto: {ekBrutto != null ? formatPrice(ekBrutto) : '—'}
|
||||||
variant="caption"
|
</Typography>
|
||||||
fontWeight={600}
|
|
||||||
sx={{
|
|
||||||
color: ekBrutto != null ? 'text.secondary' : 'text.disabled',
|
|
||||||
fontSize: '0.72rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
EK Brutto: {ekBrutto != null ? formatPrice(ekBrutto) : '—'}
|
|
||||||
</Typography>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* VK Netto */}
|
{/* VK Netto */}
|
||||||
<Typography
|
<Typography
|
||||||
@@ -964,9 +932,6 @@ class ManifestFormLine extends PureComponent {
|
|||||||
: (rawWarehouses.length > 0 || isDeliveryTracked
|
: (rawWarehouses.length > 0 || isDeliveryTracked
|
||||||
? rawWarehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0)
|
? rawWarehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0)
|
||||||
: null));
|
: null));
|
||||||
const deliveryWarehouseTooltip = rawWarehouses.length > 0
|
|
||||||
? rawWarehouses.map((w) => `${w.name || 'Lager'}: ${formatStock(w.stock)}`).join(' · ')
|
|
||||||
: (isDeliveryTracked === false ? 'Keine Bestandsführung' : undefined);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -980,26 +945,18 @@ class ManifestFormLine extends PureComponent {
|
|||||||
>
|
>
|
||||||
{/* Unlink button at the front */}
|
{/* Unlink button at the front */}
|
||||||
{this.props.onRemoveSupplierArticle && supplierArticle.id != null && (
|
{this.props.onRemoveSupplierArticle && supplierArticle.id != null && (
|
||||||
<Tooltip
|
<span>
|
||||||
title={
|
<IconButton
|
||||||
this.isFullyLedgered()
|
size="small"
|
||||||
? 'Position ist bereits vollständig im Ledger verbucht und Verknüpfung kann nicht aufgehoben werden'
|
color="error"
|
||||||
: 'Lieferanten-Verknüpfung aufheben'
|
aria-label="Lieferanten-Verknüpfung entfernen"
|
||||||
}
|
disabled={this.props.saving || this.props.supplierArticleBusy || this.isFullyLedgered()}
|
||||||
>
|
onClick={this.handleRemoveSupplierArticle}
|
||||||
<span>
|
sx={{ p: 0.25, mr: -0.25 }}
|
||||||
<IconButton
|
>
|
||||||
size="small"
|
<LinkOffOutlinedIcon sx={{ fontSize: 16 }} />
|
||||||
color="error"
|
</IconButton>
|
||||||
aria-label="Lieferanten-Verknüpfung entfernen"
|
</span>
|
||||||
disabled={this.props.saving || this.props.supplierArticleBusy || this.isFullyLedgered()}
|
|
||||||
onClick={this.handleRemoveSupplierArticle}
|
|
||||||
sx={{ p: 0.25, mr: -0.25 }}
|
|
||||||
>
|
|
||||||
<LinkOffOutlinedIcon sx={{ fontSize: 16 }} />
|
|
||||||
</IconButton>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Article Photo Thumbnail */}
|
{/* Article Photo Thumbnail */}
|
||||||
@@ -1109,48 +1066,34 @@ class ManifestFormLine extends PureComponent {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Bestand */}
|
{/* Bestand */}
|
||||||
<Tooltip
|
<Typography
|
||||||
title={
|
variant="caption"
|
||||||
deliveryWarehouseTooltip
|
fontWeight={600}
|
||||||
? `Lagerbestand: ${formatStock(deliveryStockVal)} (${deliveryWarehouseTooltip})`
|
sx={{
|
||||||
: isDeliveryTracked === false
|
color:
|
||||||
? 'Keine Bestandsführung (unbegrenzt verfügbar)'
|
deliveryStockVal == null
|
||||||
: `Aktueller Lagerbestand: ${formatStock(deliveryStockVal)}`
|
? 'text.disabled'
|
||||||
}
|
: deliveryStockVal <= 0
|
||||||
|
? 'error.main'
|
||||||
|
: 'text.secondary',
|
||||||
|
fontSize: '0.72rem',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
|
Bestand: {isDeliveryTracked === false ? '∞' : formatStock(deliveryStockVal)}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* Marge */}
|
||||||
|
{margeDelivery != null && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
fontWeight={600}
|
fontWeight={600}
|
||||||
sx={{
|
sx={{
|
||||||
color:
|
color: margeDelivery < 0 ? 'error.main' : 'text.secondary',
|
||||||
deliveryStockVal == null
|
|
||||||
? 'text.disabled'
|
|
||||||
: deliveryStockVal <= 0
|
|
||||||
? 'error.main'
|
|
||||||
: 'text.secondary',
|
|
||||||
fontSize: '0.72rem',
|
fontSize: '0.72rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Bestand: {isDeliveryTracked === false ? '∞' : formatStock(deliveryStockVal)}
|
Marge: {Math.floor(margeDelivery)}%
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* Marge */}
|
|
||||||
{margeDelivery != null && (
|
|
||||||
<Tooltip
|
|
||||||
title={`Marge: ${margeDelivery.toFixed(1).replace('.', ',')}% (EK Netto ${formatPrice(priceNet)} → VK Netto ${formatPrice(vkNetto)})`}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
fontWeight={600}
|
|
||||||
sx={{
|
|
||||||
color: margeDelivery < 0 ? 'error.main' : 'text.secondary',
|
|
||||||
fontSize: '0.72rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Marge: {Math.floor(margeDelivery)}%
|
|
||||||
</Typography>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{priceNet != null && (
|
{priceNet != null && (
|
||||||
@@ -1363,13 +1306,6 @@ class ManifestFormLine extends PureComponent {
|
|||||||
aria-label="Zeile entfernen"
|
aria-label="Zeile entfernen"
|
||||||
disabled={saving || Boolean(this.props.isOnShipment) || isFullyLedgered}
|
disabled={saving || Boolean(this.props.isOnShipment) || isFullyLedgered}
|
||||||
onClick={this.handleRemove}
|
onClick={this.handleRemove}
|
||||||
title={
|
|
||||||
isFullyLedgered
|
|
||||||
? 'Position ist bereits vollständig im Ledger verbucht und kann nicht entfernt werden'
|
|
||||||
: this.props.isOnShipment
|
|
||||||
? 'Artikel ist bereits auf einem Lieferschein und kann nicht entfernt werden'
|
|
||||||
: 'Zeile entfernen'
|
|
||||||
}
|
|
||||||
sx={{ p: 0.35 }}
|
sx={{ p: 0.35 }}
|
||||||
>
|
>
|
||||||
<DeleteOutlinedIcon sx={{ fontSize: 18 }} />
|
<DeleteOutlinedIcon sx={{ fontSize: 18 }} />
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ import entityAutomat from '../../automats/entityAutomat';
|
|||||||
import customergroupAutomat from '../../automats/customergroupAutomat';
|
import customergroupAutomat from '../../automats/customergroupAutomat';
|
||||||
import supplierAutomat from '../../automats/supplierAutomat';
|
import supplierAutomat from '../../automats/supplierAutomat';
|
||||||
import numberAutomat from '../../automats/numberAutomat';
|
import numberAutomat from '../../automats/numberAutomat';
|
||||||
|
import quoteAutomat from '../../automats/quoteAutomat';
|
||||||
|
import orderAutomat from '../../automats/orderAutomat';
|
||||||
|
import deliveryAutomat from '../../automats/deliveryAutomat';
|
||||||
import { getNextDocumentNumber, ensureDocumentNumber } from '../../utils/documentNumber';
|
import { getNextDocumentNumber, ensureDocumentNumber } from '../../utils/documentNumber';
|
||||||
import {
|
import {
|
||||||
SURFACE_RAISED,
|
SURFACE_RAISED,
|
||||||
@@ -233,11 +236,30 @@ class ManifestHeader extends PureComponent {
|
|||||||
this.props.onPdfUpload?.(file);
|
this.props.onPdfUpload?.(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subscribeListAutomat(kind) {
|
||||||
|
this.unsubList?.();
|
||||||
|
this.unsubList = null;
|
||||||
|
const automat =
|
||||||
|
kind === 'quote'
|
||||||
|
? quoteAutomat
|
||||||
|
: kind === 'order'
|
||||||
|
? orderAutomat
|
||||||
|
: kind === 'delivery'
|
||||||
|
? deliveryAutomat
|
||||||
|
: null;
|
||||||
|
if (automat) {
|
||||||
|
this.unsubList = automat.subscribe(this, () => {
|
||||||
|
if (!this._cancelled) this.forceUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this._cancelled = false;
|
this._cancelled = false;
|
||||||
this.unsubNumber = numberAutomat.subscribe(this, () => {
|
this.unsubNumber = numberAutomat.subscribe(this, () => {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
});
|
});
|
||||||
|
this.subscribeListAutomat(this.props.kind);
|
||||||
void numberAutomat.actions.ensureLoaded().catch(() => {});
|
void numberAutomat.actions.ensureLoaded().catch(() => {});
|
||||||
if (!this.props.number && this.props.docId == null) {
|
if (!this.props.number && this.props.docId == null) {
|
||||||
void ensureDocumentNumber(this.props.kind)
|
void ensureDocumentNumber(this.props.kind)
|
||||||
@@ -249,6 +271,9 @@ class ManifestHeader extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
if (prevProps.kind !== this.props.kind) {
|
||||||
|
this.subscribeListAutomat(this.props.kind);
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
!this.props.number &&
|
!this.props.number &&
|
||||||
this.props.docId == null &&
|
this.props.docId == null &&
|
||||||
@@ -265,6 +290,7 @@ class ManifestHeader extends PureComponent {
|
|||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this._cancelled = true;
|
this._cancelled = true;
|
||||||
this.unsubNumber?.();
|
this.unsubNumber?.();
|
||||||
|
this.unsubList?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import numberAutomat from '../automats/numberAutomat';
|
|||||||
import quoteAutomat from '../automats/quoteAutomat';
|
import quoteAutomat from '../automats/quoteAutomat';
|
||||||
import orderAutomat from '../automats/orderAutomat';
|
import orderAutomat from '../automats/orderAutomat';
|
||||||
import deliveryAutomat from '../automats/deliveryAutomat';
|
import deliveryAutomat from '../automats/deliveryAutomat';
|
||||||
|
import { order as orderApi, quote as quoteApi, delivery as deliveryApi } from '../api';
|
||||||
|
|
||||||
|
/** Highest integer counter found per document kind. */
|
||||||
|
const maxKnownNumberByKind = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a number pattern given prefix, padding width, and counter.
|
* Formats a number pattern given prefix, padding width, and counter.
|
||||||
@@ -20,6 +24,7 @@ export function formatNumberPattern(prefix, padding, next) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the next document number for quote, order, or delivery based on company sequences.
|
* Returns the next document number for quote, order, or delivery based on company sequences.
|
||||||
|
* Always verifies that the returned number does not collide with existing documents.
|
||||||
*
|
*
|
||||||
* @param {'quote'|'order'|'delivery'|string} kind
|
* @param {'quote'|'order'|'delivery'|string} kind
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
@@ -27,59 +32,63 @@ export function formatNumberPattern(prefix, padding, next) {
|
|||||||
export function getNextDocumentNumber(kind) {
|
export function getNextDocumentNumber(kind) {
|
||||||
const numbers = numberAutomat.state.items ?? [];
|
const numbers = numberAutomat.state.items ?? [];
|
||||||
const seq = numbers.find((r) => r.type === kind);
|
const seq = numbers.find((r) => r.type === kind);
|
||||||
if (seq) {
|
|
||||||
return formatNumberPattern(seq.prefix, seq.padding, seq.next);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect style from existing company sequences (sku, delivery, entity)
|
|
||||||
const skuSeq = numbers.find((r) => r.type === 'sku');
|
|
||||||
const deliverySeq = numbers.find((r) => r.type === 'delivery');
|
|
||||||
const entitySeq = numbers.find((r) => r.type === 'entity');
|
|
||||||
const refSeq = skuSeq || deliverySeq || entitySeq;
|
|
||||||
|
|
||||||
const pad = refSeq ? Math.max(0, Number(refSeq.padding) || 0) : 6;
|
|
||||||
const prefixStyle = refSeq?.prefix ?? '';
|
|
||||||
|
|
||||||
let prefix = '';
|
let prefix = '';
|
||||||
|
let pad = 6;
|
||||||
let startNum = 1;
|
let startNum = 1;
|
||||||
|
|
||||||
if (kind === 'delivery') {
|
if (seq) {
|
||||||
prefix = prefixStyle.startsWith('DEL-') ? 'DEL-' : (prefixStyle.startsWith('L-') ? 'L-' : 'LS-');
|
prefix = seq.prefix ?? '';
|
||||||
} else if (
|
pad = Math.max(0, Number(seq.padding) || 0);
|
||||||
prefixStyle.startsWith('ART-') ||
|
startNum = Math.max(1, Number(seq.next) || 1);
|
||||||
prefixStyle.startsWith('LS-') ||
|
|
||||||
prefixStyle.startsWith('KD-') ||
|
|
||||||
prefixStyle.endsWith('-')
|
|
||||||
) {
|
|
||||||
// Standard German ERP prefix style (ART-, KD-, LS-, KORR-)
|
|
||||||
prefix = kind === 'quote' ? 'ANG-' : (kind === 'order' ? 'AUF-' : '');
|
|
||||||
} else if (
|
|
||||||
prefixStyle.startsWith('SKU-') ||
|
|
||||||
prefixStyle.startsWith('DEL-') ||
|
|
||||||
prefixStyle.startsWith('C-')
|
|
||||||
) {
|
|
||||||
prefix = kind === 'quote' ? 'Q-' : (kind === 'order' ? 'ORD-' : '');
|
|
||||||
} else if (
|
|
||||||
prefixStyle.startsWith('A-') ||
|
|
||||||
prefixStyle.startsWith('L-') ||
|
|
||||||
prefixStyle.startsWith('K-')
|
|
||||||
) {
|
|
||||||
prefix = kind === 'quote' ? 'AG-' : (kind === 'order' ? 'AU-' : '');
|
|
||||||
} else if (!prefixStyle && refSeq?.next >= 10000) {
|
|
||||||
prefix = '';
|
|
||||||
startNum = kind === 'quote' ? 40001 : 50001;
|
|
||||||
} else {
|
} else {
|
||||||
prefix = kind === 'quote' ? 'ANG-' : (kind === 'order' ? 'AUF-' : '');
|
// Detect style from existing company sequences (sku, delivery, entity)
|
||||||
|
const skuSeq = numbers.find((r) => r.type === 'sku');
|
||||||
|
const deliverySeq = numbers.find((r) => r.type === 'delivery');
|
||||||
|
const entitySeq = numbers.find((r) => r.type === 'entity');
|
||||||
|
const refSeq = skuSeq || deliverySeq || entitySeq;
|
||||||
|
|
||||||
|
pad = refSeq ? Math.max(0, Number(refSeq.padding) || 0) : 6;
|
||||||
|
const prefixStyle = refSeq?.prefix ?? '';
|
||||||
|
|
||||||
|
if (kind === 'delivery') {
|
||||||
|
prefix = prefixStyle.startsWith('DEL-') ? 'DEL-' : (prefixStyle.startsWith('L-') ? 'L-' : 'LS-');
|
||||||
|
} else if (
|
||||||
|
prefixStyle.startsWith('ART-') ||
|
||||||
|
prefixStyle.startsWith('LS-') ||
|
||||||
|
prefixStyle.startsWith('KD-') ||
|
||||||
|
prefixStyle.endsWith('-')
|
||||||
|
) {
|
||||||
|
// Standard German ERP prefix style (ART-, KD-, LS-, KORR-)
|
||||||
|
prefix = kind === 'quote' ? 'ANG-' : (kind === 'order' ? 'AUF-' : '');
|
||||||
|
} else if (
|
||||||
|
prefixStyle.startsWith('SKU-') ||
|
||||||
|
prefixStyle.startsWith('DEL-') ||
|
||||||
|
prefixStyle.startsWith('C-')
|
||||||
|
) {
|
||||||
|
prefix = kind === 'quote' ? 'Q-' : (kind === 'order' ? 'ORD-' : '');
|
||||||
|
} else if (
|
||||||
|
prefixStyle.startsWith('A-') ||
|
||||||
|
prefixStyle.startsWith('L-') ||
|
||||||
|
prefixStyle.startsWith('K-')
|
||||||
|
) {
|
||||||
|
prefix = kind === 'quote' ? 'AG-' : (kind === 'order' ? 'AU-' : '');
|
||||||
|
} else if (!prefixStyle && refSeq?.next >= 10000) {
|
||||||
|
prefix = '';
|
||||||
|
startNum = kind === 'quote' ? 40001 : 50001;
|
||||||
|
} else {
|
||||||
|
prefix = kind === 'quote' ? 'ANG-' : (kind === 'order' ? 'AUF-' : '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check existing docs from quoteAutomat / orderAutomat / deliveryAutomat to avoid collisions
|
// Check existing docs from quoteAutomat / orderAutomat / deliveryAutomat to avoid collisions
|
||||||
const listAutomat = kind === 'quote' ? quoteAutomat : (kind === 'order' ? orderAutomat : (kind === 'delivery' ? deliveryAutomat : null));
|
const listAutomat = kind === 'quote' ? quoteAutomat : (kind === 'order' ? orderAutomat : (kind === 'delivery' ? deliveryAutomat : null));
|
||||||
const existingDocs = Array.isArray(listAutomat?.state?.items) ? listAutomat.state.items : [];
|
const existingDocs = Array.isArray(listAutomat?.state?.items) ? listAutomat.state.items : [];
|
||||||
let maxFound = startNum - 1;
|
let maxFound = Math.max(startNum - 1, maxKnownNumberByKind.get(kind) ?? 0);
|
||||||
|
|
||||||
for (const doc of existingDocs) {
|
for (const doc of existingDocs) {
|
||||||
if (!doc || typeof doc !== 'object') continue;
|
if (!doc || typeof doc !== 'object') continue;
|
||||||
const docNum = String(doc.number ?? '').trim();
|
const docNum = String(doc.number ?? doc.orderNumber ?? '').trim();
|
||||||
if (!docNum) continue;
|
if (!docNum) continue;
|
||||||
if (prefix && docNum.startsWith(prefix)) {
|
if (prefix && docNum.startsWith(prefix)) {
|
||||||
const rest = docNum.slice(prefix.length);
|
const rest = docNum.slice(prefix.length);
|
||||||
@@ -95,12 +104,15 @@ export function getNextDocumentNumber(kind) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maxKnownNumberByKind.set(kind, maxFound);
|
||||||
|
|
||||||
const nextVal = maxFound >= startNum ? maxFound + 1 : startNum;
|
const nextVal = maxFound >= startNum ? maxFound + 1 : startNum;
|
||||||
return formatNumberPattern(prefix, pad, nextVal);
|
return formatNumberPattern(prefix, pad, nextVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously ensures required stores are loaded, then returns the next document number.
|
* Asynchronously ensures required stores are loaded, queries recent documents to detect
|
||||||
|
* the highest existing number, then returns the next document number.
|
||||||
*
|
*
|
||||||
* @param {'quote'|'order'|'delivery'|string} kind
|
* @param {'quote'|'order'|'delivery'|string} kind
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
@@ -111,6 +123,51 @@ export async function ensureDocumentNumber(kind) {
|
|||||||
numberAutomat.actions.ensureLoaded().catch(() => {}),
|
numberAutomat.actions.ensureLoaded().catch(() => {}),
|
||||||
listAutomat?.actions?.ensureLoaded ? listAutomat.actions.ensureLoaded().catch(() => {}) : Promise.resolve(),
|
listAutomat?.actions?.ensureLoaded ? listAutomat.actions.ensureLoaded().catch(() => {}) : Promise.resolve(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let recentDocs = [];
|
||||||
|
if (kind === 'order') {
|
||||||
|
const res = await orderApi.list({ offset: 0, limit: 10 });
|
||||||
|
recentDocs = Array.isArray(res?.orders) ? res.orders : [];
|
||||||
|
} else if (kind === 'quote') {
|
||||||
|
const res = await quoteApi.list({ offset: 0, limit: 10 });
|
||||||
|
recentDocs = Array.isArray(res?.quotes) ? res.quotes : [];
|
||||||
|
} else if (kind === 'delivery') {
|
||||||
|
const res = await deliveryApi.list({ offset: 0, limit: 10 });
|
||||||
|
recentDocs = Array.isArray(res?.deliveries) ? res.deliveries : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recentDocs.length > 0) {
|
||||||
|
const numbers = numberAutomat.state.items ?? [];
|
||||||
|
const seq = numbers.find((r) => r.type === kind);
|
||||||
|
let prefix = seq?.prefix;
|
||||||
|
if (prefix == null) {
|
||||||
|
prefix = kind === 'quote' ? 'ANG-' : (kind === 'order' ? 'AUF-' : 'LS-');
|
||||||
|
}
|
||||||
|
|
||||||
|
let maxFound = maxKnownNumberByKind.get(kind) ?? 0;
|
||||||
|
for (const doc of recentDocs) {
|
||||||
|
const docNum = String(doc.number ?? doc.orderNumber ?? '').trim();
|
||||||
|
if (!docNum) continue;
|
||||||
|
if (prefix && docNum.startsWith(prefix)) {
|
||||||
|
const rest = docNum.slice(prefix.length);
|
||||||
|
const parsed = parseInt(rest, 10);
|
||||||
|
if (!Number.isNaN(parsed) && parsed > maxFound) {
|
||||||
|
maxFound = parsed;
|
||||||
|
}
|
||||||
|
} else if (!prefix) {
|
||||||
|
const parsed = parseInt(docNum, 10);
|
||||||
|
if (!Number.isNaN(parsed) && parsed > maxFound) {
|
||||||
|
maxFound = parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maxKnownNumberByKind.set(kind, maxFound);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`[documentNumber] ensureDocumentNumber error fetching latest ${kind}:`, err);
|
||||||
|
}
|
||||||
|
|
||||||
return getNextDocumentNumber(kind);
|
return getNextDocumentNumber(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user