Compare commits
10 Commits
94f385e753
...
479cac85a5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
479cac85a5 | ||
|
|
0a9deac0da | ||
|
|
abd40ff4fe | ||
|
|
5faea634ba | ||
|
|
35cfea91f8 | ||
|
|
9f9310e1ff | ||
|
|
c211040cd3 | ||
|
|
ac9034685f | ||
|
|
328ba164ec | ||
|
|
180f65f4d6 |
@@ -140,11 +140,11 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
|||||||
|
|
||||||
### Reusable types
|
### Reusable types
|
||||||
|
|
||||||
- `T1` = `{ id: int, number: str, type: str, entityId: int|null, date: str, lines: T20[], externalNumber?: str|null, createdAt?: str, shipping?: T19|null, shippings?: T19[], payment?: T30|null, payments?: T30[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T3[], files?: T9[] }`
|
- `T1` = `{ id: int, number: str, type: str, entityId: int, date: str, lines: T20[], externalNumber?: str|null, createdAt?: str, shipping?: T19|null, shippings?: T19[], payment?: T30|null, payments?: T30[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T3[], files?: T9[] }`
|
||||||
- `T2` = `{ id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T27, brand: T26|null, categoryIds: int[], photos: T24[], eans: str[], prices: T35[], suppliers: T11[] }`
|
- `T2` = `{ id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T27, brand: T26|null, categoryIds: int[], photos: T24[], eans: str[], prices: T35[], suppliers: T11[] }`
|
||||||
- `T3` = `{ id: int, number: str, type: str, date: str, lines: T20[], shippings: T19[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T9[], state?: str }`
|
- `T3` = `{ id: int, number: str, type: str, date: str, lines: T20[], shippings: T19[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T9[], state?: str }`
|
||||||
- `T4` = `{ id: int, number: str, type: str, entityId: int|null, date: str, lines: T20[], externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T9[] }`
|
- `T4` = `{ id: int, number: str, type: str, date: str, lines: T20[], entityId?: int|null, externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T9[] }`
|
||||||
- `T5` = `{ id: int, number: str, type: str, supplierId: int|null, date: str, lines: T20[], files: T9[], externalNumber?: str|null, createdAt?: str, ledger?: T39 }`
|
- `T5` = `{ id: int, number: str, type: str, supplierId: int, date: str, lines: T20[], files: T9[], externalNumber?: str|null, createdAt?: str, ledger?: T39 }`
|
||||||
- `T6` = `{ name: str, path: str, totalSizeBytes: int, timestamp: str|null, manifest?: T7|null }`
|
- `T6` = `{ name: str, path: str, totalSizeBytes: int, timestamp: str|null, manifest?: T7|null }`
|
||||||
- `T7` = `{ id: str, timestamp: str, dateKey: str, monthKey: str, dbProvider: str, files: T12, totalSizeBytes: int, durationMs: int }`
|
- `T7` = `{ id: str, timestamp: str, dateKey: str, monthKey: str, dbProvider: str, files: T12, totalSizeBytes: int, durationMs: int }`
|
||||||
- `T8` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: { objects: T21[] }, thumb?: str|null }`
|
- `T8` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: { objects: T21[] }, thumb?: str|null }`
|
||||||
@@ -534,9 +534,9 @@ _none_
|
|||||||
|
|
||||||
#### List deliveries — `GET /delivery`
|
#### List deliveries — `GET /delivery`
|
||||||
- Params: `query: offset?: int, limit?: int, search?: str, onlyUnbooked?: bool, supplierId?: int, seekMonths?: int`
|
- Params: `query: offset?: int, limit?: int, search?: str, onlyUnbooked?: bool, supplierId?: int, seekMonths?: int`
|
||||||
- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int|null, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T39 }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T39 }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||||
#### Create delivery — `POST /delivery`
|
#### Create delivery — `POST /delivery`
|
||||||
- Params: `body: { externalNumber: str, date: str, lines: T23[], supplierId?: int|null, files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }`
|
- Params: `body: { externalNumber: str, date: str, supplierId: int, lines: T23[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }`
|
||||||
- Returns: `201 { delivery: T5 }`
|
- Returns: `201 { delivery: T5 }`
|
||||||
#### Delete delivery — `DELETE /delivery/{id}`
|
#### Delete delivery — `DELETE /delivery/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
@@ -750,7 +750,7 @@ _none_
|
|||||||
- Params: `query: offset?: int, limit?: int, search?: str, onlyUnpaid?: bool, onlyUndelivered?: bool, seekMonths?: int`
|
- Params: `query: offset?: int, limit?: int, search?: str, onlyUnpaid?: bool, onlyUndelivered?: bool, seekMonths?: int`
|
||||||
- Returns: `200 { orders: T1[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
- Returns: `200 { orders: T1[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||||
#### Create order — `POST /order`
|
#### Create order — `POST /order`
|
||||||
- Params: `body: { number: str, date: str, lines: T23[], entityId?: int|null, externalNumber?: str|null }`
|
- Params: `body: { number: str, date: str, entityId: int, lines: T23[], externalNumber?: str|null }`
|
||||||
- Returns: `201 { order: T1 }`
|
- Returns: `201 { order: T1 }`
|
||||||
#### Delete order — `DELETE /order/{id}`
|
#### Delete order — `DELETE /order/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ Reference guide for the backend LLM agent when emitting route navigation actions
|
|||||||
| **New Delivery Draft** | `/lieferung/neu` | Static (receipt & draft line editor) | `/lieferung/neu` |
|
| **New Delivery Draft** | `/lieferung/neu` | Static (receipt & draft line editor) | `/lieferung/neu` |
|
||||||
| **Delivery Detail** | `/lieferung/:deliveryId` | Delivery document ID: `/lieferung/${deliveryId}` | `/lieferung/42` |
|
| **Delivery Detail** | `/lieferung/:deliveryId` | Delivery document ID: `/lieferung/${deliveryId}` | `/lieferung/42` |
|
||||||
| **Suppliers Directory** | `/lieferung/lieferanten` | Static | `/lieferung/lieferanten` |
|
| **Suppliers Directory** | `/lieferung/lieferanten` | Static | `/lieferung/lieferanten` |
|
||||||
| **Quote List (Angebote)** | `/auftrag` | Static | `/auftrag` |
|
| **Order List (Auftrag / Bestellungen)** | `/auftrag` | Static (legacy `/auftrag/bestellung` redirects here) | `/auftrag` |
|
||||||
| **New Quote** | `/auftrag/neuesangebot` | Static draft form (Neues Angebot) | `/auftrag/neuesangebot` |
|
|
||||||
| **Quote Detail** | `/auftrag/:quoteId` | Quote document ID: `/auftrag/${quoteId}` | `/auftrag/1001` |
|
|
||||||
| **Order List (Bestellungen)**| `/auftrag/bestellung` | Static | `/auftrag/bestellung` |
|
|
||||||
| **New Order** | `/auftrag/bestellung/neu` | Static draft form | `/auftrag/bestellung/neu` |
|
| **New Order** | `/auftrag/bestellung/neu` | Static draft form | `/auftrag/bestellung/neu` |
|
||||||
| **Order Detail** | `/auftrag/bestellung/:orderId` | Order document ID: `/auftrag/bestellung/${orderId}` | `/auftrag/bestellung/2005` |
|
| **Order Detail** | `/auftrag/bestellung/:orderId` | Order document ID: `/auftrag/bestellung/${orderId}` | `/auftrag/bestellung/2005` |
|
||||||
|
| **Quote List (Angebote)** | `/auftrag/angebot` | Static | `/auftrag/angebot` |
|
||||||
|
| **New Quote** | `/auftrag/neuesangebot` | Static draft form (Neues Angebot) | `/auftrag/neuesangebot` |
|
||||||
|
| **Quote Detail** | `/auftrag/:quoteId` or `/auftrag/angebot/:quoteId` | Quote document ID: `/auftrag/${quoteId}` | `/auftrag/1001` |
|
||||||
| **Shipments (Ausliefern)** | `/auftrag/ausliefern` | Static (shipment manifests & parcels) | `/auftrag/ausliefern` |
|
| **Shipments (Ausliefern)** | `/auftrag/ausliefern` | Static (shipment manifests & parcels) | `/auftrag/ausliefern` |
|
||||||
| **Payments (Zahlungen)** | `/auftrag/zahlungen` | Static (order payments ledger) | `/auftrag/zahlungen` |
|
| **Payments (Zahlungen)** | `/auftrag/zahlungen` | Static (order payments ledger) | `/auftrag/zahlungen` |
|
||||||
| **Carriers (Versandarten)** | `/auftrag/versandarten` | Static (Versanddienstleister & Versandarten) | `/auftrag/versandarten` |
|
| **Carriers (Versandarten)** | `/auftrag/versandarten` | Static (Versanddienstleister & Versandarten) | `/auftrag/versandarten` |
|
||||||
@@ -70,8 +70,8 @@ Reference guide for the backend LLM agent when emitting route navigation actions
|
|||||||
|
|
||||||
### Quotes & Orders (`/auftrag`)
|
### Quotes & Orders (`/auftrag`)
|
||||||
- Distinction:
|
- Distinction:
|
||||||
- Quotes (Angebote) live at `/auftrag` and `/auftrag/:quoteId`.
|
- Orders (Auftrag / Bestellungen) live at `/auftrag` and `/auftrag/bestellung/:orderId`.
|
||||||
- Orders (Bestellungen) live at `/auftrag/bestellung` and `/auftrag/bestellung/:orderId`.
|
- Quotes (Angebote) live at `/auftrag/angebot` and `/auftrag/:quoteId` (or `/auftrag/angebot/:quoteId`).
|
||||||
- Shipments (Ausliefern) live at `/auftrag/ausliefern` (connected to shipment manifests and packages).
|
- Shipments (Ausliefern) live at `/auftrag/ausliefern` (connected to shipment manifests and packages).
|
||||||
- Payments (Zahlungen) live at `/auftrag/zahlungen` (payment bookings and payment balances).
|
- Payments (Zahlungen) live at `/auftrag/zahlungen` (payment bookings and payment balances).
|
||||||
- Both IDs (`quoteId`, `orderId`) are document IDs.
|
- Both IDs (`quoteId`, `orderId`) are document IDs.
|
||||||
|
|||||||
@@ -99,10 +99,13 @@ const routes = [
|
|||||||
path: 'auftrag',
|
path: 'auftrag',
|
||||||
element: <SectionLayout sectionPath="/auftrag" />,
|
element: <SectionLayout sectionPath="/auftrag" />,
|
||||||
children: [
|
children: [
|
||||||
{ index: true, element: <AuftragAngebotPage /> },
|
{ index: true, element: <AuftragBestellungPage /> },
|
||||||
|
{ path: 'angebot', element: <AuftragAngebotPage /> },
|
||||||
|
{ path: 'angebot/:quoteId', element: <AuftragAngebotEditPage /> },
|
||||||
{ path: 'neuesangebot', element: <AuftragAngebotNeuPage /> },
|
{ path: 'neuesangebot', element: <AuftragAngebotNeuPage /> },
|
||||||
|
{ path: 'angebot/neu', element: <Navigate to="/auftrag/neuesangebot" replace /> },
|
||||||
{ path: 'neu', element: <Navigate to="/auftrag/neuesangebot" replace /> },
|
{ path: 'neu', element: <Navigate to="/auftrag/neuesangebot" replace /> },
|
||||||
{ path: 'bestellung', element: <AuftragBestellungPage /> },
|
{ path: 'bestellung', element: <Navigate to="/auftrag" replace /> },
|
||||||
{ path: 'bestellung/neu', element: <AuftragBestellungNeuPage /> },
|
{ path: 'bestellung/neu', element: <AuftragBestellungNeuPage /> },
|
||||||
{ path: 'bestellung/:orderId', element: <AuftragBestellungEditPage /> },
|
{ path: 'bestellung/:orderId', element: <AuftragBestellungEditPage /> },
|
||||||
{ path: 'ausliefern', element: <AuftragAusliefernPage /> },
|
{ path: 'ausliefern', element: <AuftragAusliefernPage /> },
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function createInitialManifestDraftState(kind) {
|
|||||||
partyId: null,
|
partyId: null,
|
||||||
number: '',
|
number: '',
|
||||||
externalNumber: '',
|
externalNumber: '',
|
||||||
lines: [emptyLine()],
|
lines: kind === 'order' ? [] : [emptyLine()],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ import {
|
|||||||
import { backendErrorFrom, isBackendFailure } from '../utils/backendError';
|
import { backendErrorFrom, isBackendFailure } from '../utils/backendError';
|
||||||
|
|
||||||
const NUMBER_SCHEMES = [
|
const NUMBER_SCHEMES = [
|
||||||
{ type: 'sku', label: 'Artikel-SKU', icon: Inventory2Icon },
|
{ type: 'sku', label: 'Artikelnummer (ArtNr)', icon: Inventory2Icon },
|
||||||
{ type: 'entity', label: 'Kundennummer', icon: PeopleIcon },
|
{ type: 'entity', label: 'Kundennummer', icon: PeopleIcon },
|
||||||
{ type: 'delivery', label: 'Lieferschein', icon: LocalShippingIcon },
|
{ type: 'delivery', label: 'Lieferschein', icon: LocalShippingIcon },
|
||||||
{ type: 'adjustment', label: 'Bestandskorrektur', icon: TuneIcon },
|
{ type: 'adjustment', label: 'Bestandskorrektur', icon: TuneIcon },
|
||||||
|
|||||||
@@ -77,14 +77,14 @@ export default function SectionLayout({ sectionPath }) {
|
|||||||
const isDetail =
|
const isDetail =
|
||||||
isArticleDetail || isKundeDetail || isLieferungDetail || isAngebotDoc;
|
isArticleDetail || isKundeDetail || isLieferungDetail || isAngebotDoc;
|
||||||
|
|
||||||
const angebotLabel =
|
|
||||||
subNav?.find((item) => item.segment === '')?.title ?? subNav?.find((item) => item.segment === '')?.label ?? 'Angebot';
|
|
||||||
let pageTitle = isDetail
|
let pageTitle = isDetail
|
||||||
? title
|
? title
|
||||||
: (activeSub?.title ?? activeSub?.label ?? title);
|
: (activeSub?.title ?? activeSub?.label ?? title);
|
||||||
if (pathname === '/auftrag/neuesangebot' || pathname === '/auftrag/neu') pageTitle = 'Neues Angebot';
|
if (pathname === '/auftrag/neuesangebot' || pathname === '/auftrag/neu' || pathname === '/auftrag/angebot/neu') pageTitle = 'Neues Angebot';
|
||||||
else if (isAngebotDoc) pageTitle = angebotLabel;
|
else if (pathname === '/auftrag/bestellung/neu') pageTitle = 'Neuer Auftrag';
|
||||||
else if (pathname === '/auftrag/bestellung/neu') pageTitle = 'Neue Bestellung';
|
else if (pathname.startsWith('/auftrag/bestellung/')) pageTitle = 'Bestellung';
|
||||||
|
else if (pathname.startsWith('/auftrag/angebot/')) pageTitle = 'Angebot';
|
||||||
|
else if (isAngebotDoc) pageTitle = 'Angebot';
|
||||||
|
|
||||||
const showsRenameSwitch = activeSub?.segment === 'kategorien';
|
const showsRenameSwitch = activeSub?.segment === 'kategorien';
|
||||||
|
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ export default class ArticleImageSearchDialog extends PureComponent {
|
|||||||
suggestions.push({ label: `${brandName} ${articleName}`, value: `${brandName} ${articleName}` });
|
suggestions.push({ label: `${brandName} ${articleName}`, value: `${brandName} ${articleName}` });
|
||||||
}
|
}
|
||||||
if (sku && sku !== query && sku !== articleName) {
|
if (sku && sku !== query && sku !== articleName) {
|
||||||
suggestions.push({ label: `SKU: ${sku}`, value: sku });
|
suggestions.push({ label: `ArtNr: ${sku}`, value: sku });
|
||||||
}
|
}
|
||||||
if (firstEan && firstEan !== query) {
|
if (firstEan && firstEan !== query) {
|
||||||
suggestions.push({ label: `EAN: ${firstEan}`, value: firstEan });
|
suggestions.push({ label: `EAN: ${firstEan}`, value: firstEan });
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export default class ArticleSupplierDialog extends PureComponent {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Lieferanten-Artikelnummer (SKU)"
|
label="Lieferanten-Artikelnummer (ArtNr)"
|
||||||
value={sku}
|
value={sku}
|
||||||
onChange={(e) => this.setState({ sku: e.target.value })}
|
onChange={(e) => this.setState({ sku: e.target.value })}
|
||||||
placeholder="z. B. LF-09412"
|
placeholder="z. B. LF-09412"
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export default class ArticleSearchDialog extends PureComponent {
|
|||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
{line ? (
|
{line ? (
|
||||||
<>
|
<>
|
||||||
{isDelivery ? 'Lieferanten-SKU' : 'SKU'}: <strong>{rawSku || '–'}</strong>
|
{isDelivery ? 'Lieferanten-ArtNr' : 'ArtNr'}: <strong>{rawSku || '–'}</strong>
|
||||||
{line?.name ? ` · ${line.name}` : ''}
|
{line?.name ? ` · ${line.name}` : ''}
|
||||||
</>
|
</>
|
||||||
) : isDelivery ? (
|
) : isDelivery ? (
|
||||||
|
|||||||
@@ -505,8 +505,8 @@ export default class BookDeliveryDialog extends PureComponent {
|
|||||||
<TableHead sx={{ backgroundColor: SURFACE_RAISED }}>
|
<TableHead sx={{ backgroundColor: SURFACE_RAISED }}>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell sx={{ fontWeight: 600, width: 44 }} align="center">Pos.</TableCell>
|
<TableCell sx={{ fontWeight: 600, width: 44 }} align="center">Pos.</TableCell>
|
||||||
<TableCell sx={{ fontWeight: 600 }}>Lieferanten-SKU</TableCell>
|
<TableCell sx={{ fontWeight: 600 }}>Lieferanten-ArtNr</TableCell>
|
||||||
<TableCell sx={{ fontWeight: 600 }}>Eigene SKU</TableCell>
|
<TableCell sx={{ fontWeight: 600 }}>Eigene ArtNr</TableCell>
|
||||||
<TableCell sx={{ fontWeight: 600 }}>EAN</TableCell>
|
<TableCell sx={{ fontWeight: 600 }}>EAN</TableCell>
|
||||||
<TableCell sx={{ fontWeight: 600 }}>Artikel</TableCell>
|
<TableCell sx={{ fontWeight: 600 }}>Artikel</TableCell>
|
||||||
<TableCell align="right" sx={{ fontWeight: 600 }}>Bestand</TableCell>
|
<TableCell align="right" sx={{ fontWeight: 600 }}>Bestand</TableCell>
|
||||||
@@ -780,7 +780,7 @@ export default class BookDeliveryDialog extends PureComponent {
|
|||||||
{prefillLine && (
|
{prefillLine && (
|
||||||
<Alert severity="info" sx={{ mb: 2 }}>
|
<Alert severity="info" sx={{ mb: 2 }}>
|
||||||
Vorausgewählt zum Verbuchen: Position{' '}
|
Vorausgewählt zum Verbuchen: Position{' '}
|
||||||
<strong>{prefillLine.sku ? `SKU ${prefillLine.sku}` : prefillLine.name}</strong> ({amountsToBook[prefillLine.id] || 0} Stk.). Andere Positionen stehen auf 0.
|
<strong>{prefillLine.sku ? `ArtNr ${prefillLine.sku}` : prefillLine.name}</strong> ({amountsToBook[prefillLine.id] || 0} Stk.). Andere Positionen stehen auf 0.
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
{this.renderLinesTable()}
|
{this.renderLinesTable()}
|
||||||
|
|||||||
@@ -69,13 +69,20 @@ class ManifestForm extends PureComponent {
|
|||||||
initialDoc?.date ||
|
initialDoc?.date ||
|
||||||
(props.mode === 'create' ? todayInputValue() : '');
|
(props.mode === 'create' ? todayInputValue() : '');
|
||||||
|
|
||||||
if (
|
if (props.mode === 'create' && this.draftAutomat) {
|
||||||
props.mode === 'create' &&
|
if (!this.draftAutomat.state.date) {
|
||||||
this.draftAutomat &&
|
|
||||||
!this.draftAutomat.state.date
|
|
||||||
) {
|
|
||||||
this.draftAutomat.setState({ date: initialDate });
|
this.draftAutomat.setState({ date: initialDate });
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
props.kind === 'order' &&
|
||||||
|
Array.isArray(this.draftAutomat.state.lines) &&
|
||||||
|
this.draftAutomat.state.lines.length === 1 &&
|
||||||
|
!this.draftAutomat.state.lines[0]?.sku &&
|
||||||
|
!this.draftAutomat.state.lines[0]?.name
|
||||||
|
) {
|
||||||
|
this.draftAutomat.setState({ lines: [] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
status: props.mode === 'edit' && !initialEdit ? 'loading' : (partiesReady ? 'ready' : 'loadingParties'),
|
status: props.mode === 'edit' && !initialEdit ? 'loading' : (partiesReady ? 'ready' : 'loadingParties'),
|
||||||
@@ -86,7 +93,11 @@ class ManifestForm extends PureComponent {
|
|||||||
partyId: initialDoc?.partyId ?? null,
|
partyId: initialDoc?.partyId ?? null,
|
||||||
number: initialDoc?.number || '',
|
number: initialDoc?.number || '',
|
||||||
externalNumber: initialDoc?.externalNumber || '',
|
externalNumber: initialDoc?.externalNumber || '',
|
||||||
lines: Array.isArray(initialDoc?.lines) ? initialDoc.lines : (props.mode === 'create' ? [emptyLine()] : []),
|
lines: Array.isArray(initialDoc?.lines)
|
||||||
|
? (props.kind === 'order' && props.mode === 'create' && initialDoc.lines.length === 1 && !initialDoc.lines[0]?.sku && !initialDoc.lines[0]?.name
|
||||||
|
? []
|
||||||
|
: initialDoc.lines)
|
||||||
|
: (props.mode === 'create' && props.kind !== 'order' ? [emptyLine()] : []),
|
||||||
attachments: initialDoc?.attachments || [],
|
attachments: initialDoc?.attachments || [],
|
||||||
shipments: initialEdit?.shipments || [],
|
shipments: initialEdit?.shipments || [],
|
||||||
payments: initialEdit?.payments || [],
|
payments: initialEdit?.payments || [],
|
||||||
@@ -209,6 +220,7 @@ class ManifestForm extends PureComponent {
|
|||||||
void this.loadEdit();
|
void this.loadEdit();
|
||||||
} else {
|
} else {
|
||||||
void this.loadPartiesOnly();
|
void this.loadPartiesOnly();
|
||||||
|
void ensureDocumentNumber(this.props.kind).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.draftAutomat) {
|
if (this.draftAutomat) {
|
||||||
@@ -857,6 +869,7 @@ class ManifestForm extends PureComponent {
|
|||||||
|
|
||||||
handleRemoveAllLines() {
|
handleRemoveAllLines() {
|
||||||
this.setState((prev) => {
|
this.setState((prev) => {
|
||||||
|
let nextLines = [];
|
||||||
if (this.props.kind === 'order') {
|
if (this.props.kind === 'order') {
|
||||||
const shippedSkus = new Set();
|
const shippedSkus = new Set();
|
||||||
for (const sh of prev.shipments || []) {
|
for (const sh of prev.shipments || []) {
|
||||||
@@ -865,7 +878,7 @@ class ManifestForm extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (shippedSkus.size > 0) {
|
if (shippedSkus.size > 0) {
|
||||||
const remaining = prev.lines.filter((l) => {
|
const remaining = (prev.lines || []).filter((l) => {
|
||||||
const raw = l.sku != null ? String(l.sku).trim().toLowerCase() : '';
|
const raw = l.sku != null ? String(l.sku).trim().toLowerCase() : '';
|
||||||
return shippedSkus.has(raw);
|
return shippedSkus.has(raw);
|
||||||
});
|
});
|
||||||
@@ -874,10 +887,10 @@ class ManifestForm extends PureComponent {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
enqueueSnackbar('Nicht ausgelieferte Positionen wurden entfernt.', { variant: 'info' });
|
enqueueSnackbar('Nicht ausgelieferte Positionen wurden entfernt.', { variant: 'info' });
|
||||||
return { lines: remaining, isDirty: true };
|
nextLines = remaining;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { lines: [], isDirty: true };
|
return { lines: nextLines, isDirty: true };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1170,7 +1183,7 @@ class ManifestForm extends PureComponent {
|
|||||||
{
|
{
|
||||||
kind: cfg.kind,
|
kind: cfg.kind,
|
||||||
partyField: cfg.partyField,
|
partyField: cfg.partyField,
|
||||||
requireAtLeastOne: true,
|
requireAtLeastOne: !isEdit,
|
||||||
externalNumber: cfg.externalNumber,
|
externalNumber: cfg.externalNumber,
|
||||||
isUpdate: isEdit,
|
isUpdate: isEdit,
|
||||||
mode,
|
mode,
|
||||||
@@ -1323,7 +1336,7 @@ class ManifestForm extends PureComponent {
|
|||||||
isDirty={this.state.isDirty}
|
isDirty={this.state.isDirty}
|
||||||
dirtyFields={this.state.dirtyFields}
|
dirtyFields={this.state.dirtyFields}
|
||||||
dirtySummary={this.state.dirtySummary}
|
dirtySummary={this.state.dirtySummary}
|
||||||
onRestore={this.props.mode === 'edit' && this.state.isDirty ? this.handleRestore : undefined}
|
onRestore={this.props.mode === 'edit' ? this.handleRestore : undefined}
|
||||||
onPdfUpload={kind === 'delivery' ? this.handlePdfUpload : undefined}
|
onPdfUpload={kind === 'delivery' ? this.handlePdfUpload : undefined}
|
||||||
onPdfDelete={this.handleAttachmentDelete}
|
onPdfDelete={this.handleAttachmentDelete}
|
||||||
onPdfCreate={kind === 'quote' || kind === 'order' ? this.handleCreatePdf : undefined}
|
onPdfCreate={kind === 'quote' || kind === 'order' ? this.handleCreatePdf : undefined}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import LinkOffOutlinedIcon from '@mui/icons-material/LinkOffOutlined';
|
|||||||
import { getFirstVal, parseNum, LINE_COLUMNS } from './manifestConfig';
|
import { getFirstVal, parseNum, LINE_COLUMNS } from './manifestConfig';
|
||||||
import ArticlePhoto from '../article/ArticlePhoto';
|
import ArticlePhoto from '../article/ArticlePhoto';
|
||||||
import { formatPrice, grossFromNet } from '../../utils/price';
|
import { formatPrice, grossFromNet } from '../../utils/price';
|
||||||
|
import { formatStock } from '../article/searchFormat';
|
||||||
import { primaryPrice, priceBrutto } from '../../utils/articleDetail';
|
import { primaryPrice, priceBrutto } from '../../utils/articleDetail';
|
||||||
import artikelAutomat, { createArticleSelector, getArticle } from '../../automats/artikelAutomat';
|
import artikelAutomat, { createArticleSelector, getArticle } from '../../automats/artikelAutomat';
|
||||||
import { artikelDetailPath } from '../article/articleRoute';
|
import { artikelDetailPath } from '../article/articleRoute';
|
||||||
@@ -578,9 +579,40 @@ class ManifestFormLine extends PureComponent {
|
|||||||
? Math.round((ekNetto * (1 + Number(ekTaxRate) / 100) + Number.EPSILON) * 100) / 100
|
? Math.round((ekNetto * (1 + Number(ekTaxRate) / 100) + Number.EPSILON) * 100) / 100
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const marge =
|
||||||
|
vkNetto > 0 && ekNetto != null
|
||||||
|
? ((vkNetto - ekNetto) / vkNetto) * 100
|
||||||
|
: vkBrutto > 0 && ekBrutto != null
|
||||||
|
? ((vkBrutto - ekBrutto) / vkBrutto) * 100
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const rebate = parseNum(this.props.line?.rebate) ?? 0;
|
||||||
|
const effectiveNet =
|
||||||
|
vkNetto != null && Number.isFinite(vkNetto)
|
||||||
|
? vkNetto * (1 - rebate / 100)
|
||||||
|
: null;
|
||||||
|
const effectiveMarge =
|
||||||
|
effectiveNet != null && effectiveNet > 0 && ekNetto != null
|
||||||
|
? ((effectiveNet - ekNetto) / effectiveNet) * 100
|
||||||
|
: null;
|
||||||
|
|
||||||
const hasPriceInfo =
|
const hasPriceInfo =
|
||||||
vkNetto != null || vkBrutto != null || ekNetto != null || ekBrutto != null || kind === 'quote' || kind === 'order';
|
vkNetto != null || vkBrutto != null || ekNetto != null || ekBrutto != null || kind === 'quote' || kind === 'order';
|
||||||
|
|
||||||
|
const rawWarehouses = Array.isArray(cachedArticle.warehouses) ? cachedArticle.warehouses : [];
|
||||||
|
const isTracked = cachedArticle.track_stock ?? cachedArticle.trackStock ?? (rawWarehouses.length > 0);
|
||||||
|
const stockVal = isTracked === false
|
||||||
|
? null
|
||||||
|
: (cachedArticle.stock != null && !Number.isNaN(Number(cachedArticle.stock))
|
||||||
|
? Number(cachedArticle.stock)
|
||||||
|
: (rawWarehouses.length > 0 || isTracked
|
||||||
|
? rawWarehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0)
|
||||||
|
: 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;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -697,6 +729,55 @@ class ManifestFormLine extends PureComponent {
|
|||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Bestand */}
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
warehouseTooltip
|
||||||
|
? `Lagerbestand: ${formatStock(stockVal)} (${warehouseTooltip})`
|
||||||
|
: isTracked === false
|
||||||
|
? 'Keine Bestandsführung (unbegrenzt verfügbar)'
|
||||||
|
: `Verfügbarer Lagerbestand: ${formatStock(stockVal)}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
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 */}
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
marge != null
|
||||||
|
? `Marge: ${marge.toFixed(1).replace('.', ',')}% (EK Netto ${formatPrice(ekNetto)} → VK Netto ${formatPrice(vkNetto)})${rebate > 0 && effectiveMarge != null ? ` · Nach ${rebate}% Rabatt: ${effectiveMarge.toFixed(1).replace('.', ',')}%` : ''}`
|
||||||
|
: 'Kein EK- oder VK-Preis hinterlegt'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
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
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
@@ -793,7 +874,7 @@ class ManifestFormLine extends PureComponent {
|
|||||||
)}
|
)}
|
||||||
<HelpOutlineIcon sx={{ fontSize: 15, color: 'warning.main' }} />
|
<HelpOutlineIcon sx={{ fontSize: 15, color: 'warning.main' }} />
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.72rem' }}>
|
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.72rem' }}>
|
||||||
Kein Artikel für SKU „{rawSku}“ gefunden.
|
Kein Artikel für ArtNr „{rawSku}“ gefunden.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -867,6 +948,26 @@ class ManifestFormLine extends PureComponent {
|
|||||||
: null)
|
: null)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const priceNetNum = Number(priceNet);
|
||||||
|
const vkNettoNum = Number(vkNetto);
|
||||||
|
const margeDelivery =
|
||||||
|
vkNettoNum > 0 && priceNet != null && Number.isFinite(priceNetNum)
|
||||||
|
? ((vkNettoNum - priceNetNum) / vkNettoNum) * 100
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const rawWarehouses = Array.isArray(fullArticle.warehouses) ? fullArticle.warehouses : [];
|
||||||
|
const isDeliveryTracked = fullArticle.track_stock ?? fullArticle.trackStock ?? (rawWarehouses.length > 0);
|
||||||
|
const deliveryStockVal = isDeliveryTracked === false
|
||||||
|
? null
|
||||||
|
: (fullArticle.stock != null && !Number.isNaN(Number(fullArticle.stock))
|
||||||
|
? Number(fullArticle.stock)
|
||||||
|
: (rawWarehouses.length > 0 || isDeliveryTracked
|
||||||
|
? rawWarehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0)
|
||||||
|
: 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
|
||||||
sx={{
|
sx={{
|
||||||
@@ -981,7 +1082,7 @@ class ManifestFormLine extends PureComponent {
|
|||||||
{/* Supplier SKU if distinct */}
|
{/* Supplier SKU if distinct */}
|
||||||
{supplierArticle.sku && supplierArticle.sku !== rawSku && supplierArticle.sku !== article.sku && (
|
{supplierArticle.sku && supplierArticle.sku !== rawSku && supplierArticle.sku !== article.sku && (
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
|
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
|
||||||
(Lief.-SKU: <strong>{supplierArticle.sku}</strong>)
|
(Lief.-ArtNr: <strong>{supplierArticle.sku}</strong>)
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -996,8 +1097,8 @@ class ManifestFormLine extends PureComponent {
|
|||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Stored Purchase Price (Netto), VK Netto & VK Brutto */}
|
{/* Stored Purchase Price (Netto), VK Netto, VK Brutto, Marge & Bestand */}
|
||||||
{(priceNet != null || vkNetto != null || vkBrutto != null) && (
|
{(priceNet != null || vkNetto != null || vkBrutto != null || deliveryStockVal != null) && (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -1007,6 +1108,51 @@ class ManifestFormLine extends PureComponent {
|
|||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Bestand */}
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
deliveryWarehouseTooltip
|
||||||
|
? `Lagerbestand: ${formatStock(deliveryStockVal)} (${deliveryWarehouseTooltip})`
|
||||||
|
: isDeliveryTracked === false
|
||||||
|
? 'Keine Bestandsführung (unbegrenzt verfügbar)'
|
||||||
|
: `Aktueller Lagerbestand: ${formatStock(deliveryStockVal)}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
fontWeight={600}
|
||||||
|
sx={{
|
||||||
|
color:
|
||||||
|
deliveryStockVal == null
|
||||||
|
? 'text.disabled'
|
||||||
|
: deliveryStockVal <= 0
|
||||||
|
? 'error.main'
|
||||||
|
: 'text.secondary',
|
||||||
|
fontSize: '0.72rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Bestand: {isDeliveryTracked === false ? '∞' : formatStock(deliveryStockVal)}
|
||||||
|
</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 && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
@@ -1093,8 +1239,8 @@ class ManifestFormLine extends PureComponent {
|
|||||||
<HelpOutlineIcon sx={{ fontSize: 15, color: 'warning.main' }} />
|
<HelpOutlineIcon sx={{ fontSize: 15, color: 'warning.main' }} />
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.72rem' }}>
|
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.72rem' }}>
|
||||||
{this.props.kind === 'delivery'
|
{this.props.kind === 'delivery'
|
||||||
? `Kein Lieferanten-Artikel für SKU „${rawSku}“ gefunden.`
|
? `Kein Lieferanten-Artikel für ArtNr „${rawSku}“ gefunden.`
|
||||||
: `Kein Artikel für SKU „${rawSku}“ gefunden.`}
|
: `Kein Artikel für ArtNr „${rawSku}“ gefunden.`}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import { notifyError, notifyErrorMessage } from '../../utils/notify';
|
|||||||
import entityAutomat from '../../automats/entityAutomat';
|
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 { getNextDocumentNumber, ensureDocumentNumber } from '../../utils/documentNumber';
|
||||||
import {
|
import {
|
||||||
SURFACE_RAISED,
|
SURFACE_RAISED,
|
||||||
} from '../../colors';
|
} from '../../colors';
|
||||||
@@ -231,6 +233,40 @@ class ManifestHeader extends PureComponent {
|
|||||||
this.props.onPdfUpload?.(file);
|
this.props.onPdfUpload?.(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this._cancelled = false;
|
||||||
|
this.unsubNumber = numberAutomat.subscribe(this, () => {
|
||||||
|
this.forceUpdate();
|
||||||
|
});
|
||||||
|
void numberAutomat.actions.ensureLoaded().catch(() => {});
|
||||||
|
if (!this.props.number && this.props.docId == null) {
|
||||||
|
void ensureDocumentNumber(this.props.kind)
|
||||||
|
.then(() => {
|
||||||
|
if (!this._cancelled) this.forceUpdate();
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (
|
||||||
|
!this.props.number &&
|
||||||
|
this.props.docId == null &&
|
||||||
|
(prevProps.kind !== this.props.kind || prevProps.docId !== this.props.docId)
|
||||||
|
) {
|
||||||
|
void ensureDocumentNumber(this.props.kind)
|
||||||
|
.then(() => {
|
||||||
|
if (!this._cancelled) this.forceUpdate();
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this._cancelled = true;
|
||||||
|
this.unsubNumber?.();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
kind,
|
kind,
|
||||||
@@ -273,6 +309,9 @@ class ManifestHeader extends PureComponent {
|
|||||||
|
|
||||||
const hasArticles = this.hasArticles();
|
const hasArticles = this.hasArticles();
|
||||||
const isEdit = docId != null;
|
const isEdit = docId != null;
|
||||||
|
const previewNumber =
|
||||||
|
this.props.previewNumber ??
|
||||||
|
(!number && !isEdit ? getNextDocumentNumber(kind) : null);
|
||||||
const hasAttachments = Array.isArray(attachments) && attachments.length > 0;
|
const hasAttachments = Array.isArray(attachments) && attachments.length > 0;
|
||||||
const hasParty = partyId != null && partyId !== '';
|
const hasParty = partyId != null && partyId !== '';
|
||||||
const partyValid = kind !== 'order' || hasParty;
|
const partyValid = kind !== 'order' || hasParty;
|
||||||
@@ -376,8 +415,7 @@ class ManifestHeader extends PureComponent {
|
|||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
inputRef={this.partyInputRef}
|
inputRef={this.partyInputRef}
|
||||||
label={dirtyFields?.partyId ? `${partyLabel} *` : partyLabel}
|
label={partyLabel}
|
||||||
required={kind === 'order'}
|
|
||||||
error={isPartyEmpty}
|
error={isPartyEmpty}
|
||||||
onKeyDown={this.handlePartyKeyDown}
|
onKeyDown={this.handlePartyKeyDown}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
@@ -401,12 +439,25 @@ class ManifestHeader extends PureComponent {
|
|||||||
<SingleLineTextEdit
|
<SingleLineTextEdit
|
||||||
size="small"
|
size="small"
|
||||||
label="Nummer"
|
label="Nummer"
|
||||||
value={number || ''}
|
value={number || previewNumber || ''}
|
||||||
disabled
|
disabled
|
||||||
placeholder="–"
|
placeholder="–"
|
||||||
|
title={
|
||||||
|
!number && previewNumber
|
||||||
|
? 'Vorschau der nächsten Nummer (wird beim Speichern vergeben)'
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
slotProps={{
|
||||||
|
inputLabel: { shrink: true },
|
||||||
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
flex: { xs: '1 1 100%', sm: '1 1 120px' },
|
flex: { xs: '1 1 100%', sm: '1 1 120px' },
|
||||||
minWidth: { sm: 100 },
|
minWidth: { sm: 100 },
|
||||||
|
...(!number && previewNumber && {
|
||||||
|
'& .MuiInputBase-input.Mui-disabled': {
|
||||||
|
fontStyle: 'italic',
|
||||||
|
},
|
||||||
|
}),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{useExternalNumber ? (
|
{useExternalNumber ? (
|
||||||
@@ -482,27 +533,45 @@ class ManifestHeader extends PureComponent {
|
|||||||
>
|
>
|
||||||
{saving ? 'Speichern…' : 'Speichern'}
|
{saving ? 'Speichern…' : 'Speichern'}
|
||||||
</Button>
|
</Button>
|
||||||
{isDirty && onRestore && (
|
{(() => {
|
||||||
|
const canRestore = Boolean(onRestore && isDirty && !saving && !orderCreating);
|
||||||
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="warning"
|
color={canRestore ? 'warning' : 'inherit'}
|
||||||
size="small"
|
size="small"
|
||||||
startIcon={<RestoreIcon />}
|
startIcon={<RestoreIcon />}
|
||||||
disabled={saving || orderCreating}
|
disabled={!canRestore}
|
||||||
onClick={onRestore}
|
onClick={canRestore ? onRestore : undefined}
|
||||||
|
title={
|
||||||
|
canRestore
|
||||||
|
? 'Änderungen verwerfen und ursprünglichen Stand wiederherstellen'
|
||||||
|
: 'Keine Änderungen zum Wiederherstellen'
|
||||||
|
}
|
||||||
sx={{
|
sx={{
|
||||||
height: 34,
|
height: 34,
|
||||||
|
width: '100%',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
fontSize: '0.8rem',
|
fontSize: '0.8rem',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
borderWidth: 1.5,
|
borderWidth: 1.5,
|
||||||
'&:hover': { borderWidth: 1.5 },
|
'&:hover': { borderWidth: 1.5 },
|
||||||
|
...(!canRestore && {
|
||||||
|
borderColor: 'action.disabled',
|
||||||
|
color: 'text.disabled',
|
||||||
|
'&.Mui-disabled': {
|
||||||
|
borderColor: 'action.disabled',
|
||||||
|
color: 'text.disabled',
|
||||||
|
borderWidth: 1.5,
|
||||||
|
},
|
||||||
|
}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Wiederherstellen
|
Wiederherstellen
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
);
|
||||||
|
})()}
|
||||||
{docId != null && kind === 'delivery' && onOpenBookDialog && Boolean(ledgerSummary && (ledgerSummary.totalPositions > 0 || ledgerSummary.lines?.length > 0 || ledgerSummary.hasTrackStock)) && (
|
{docId != null && kind === 'delivery' && onOpenBookDialog && Boolean(ledgerSummary && (ledgerSummary.totalPositions > 0 || ledgerSummary.lines?.length > 0 || ledgerSummary.hasTrackStock)) && (
|
||||||
<Button
|
<Button
|
||||||
variant={ledgerSummary?.status === 'booked' ? 'outlined' : 'contained'}
|
variant={ledgerSummary?.status === 'booked' ? 'outlined' : 'contained'}
|
||||||
@@ -562,14 +631,34 @@ class ManifestHeader extends PureComponent {
|
|||||||
{orderCreating ? 'Wird erstellt…' : 'Bestellung erstellen'}
|
{orderCreating ? 'Wird erstellt…' : 'Bestellung erstellen'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{(kind === 'quote' || kind === 'order' || kind === 'delivery') && onPdfCreate && docId != null && Boolean(number) && (
|
{(kind === 'quote' || kind === 'order' || kind === 'delivery') && (() => {
|
||||||
|
const canCreatePdf = Boolean(
|
||||||
|
onPdfCreate &&
|
||||||
|
docId != null &&
|
||||||
|
Boolean(number) &&
|
||||||
|
!saving &&
|
||||||
|
!pdfCreating &&
|
||||||
|
!orderCreating
|
||||||
|
);
|
||||||
|
return (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
color={canCreatePdf ? 'primary' : 'inherit'}
|
||||||
startIcon={pdfCreating ? <CircularProgress size={16} color="inherit" /> : <PictureAsPdfIcon />}
|
startIcon={pdfCreating ? <CircularProgress size={16} color="inherit" /> : <PictureAsPdfIcon />}
|
||||||
disabled={saving || pdfCreating || orderCreating}
|
disabled={!canCreatePdf}
|
||||||
onClick={onPdfCreate}
|
onClick={canCreatePdf ? onPdfCreate : undefined}
|
||||||
title="PDF erstellen"
|
title={
|
||||||
|
pdfCreating
|
||||||
|
? 'PDF wird erstellt…'
|
||||||
|
: canCreatePdf
|
||||||
|
? 'PDF erstellen'
|
||||||
|
: docId == null
|
||||||
|
? 'PDF kann erst nach dem Speichern erstellt werden'
|
||||||
|
: !number
|
||||||
|
? 'Dokumentnummer erforderlich für PDF'
|
||||||
|
: 'PDF erstellen'
|
||||||
|
}
|
||||||
sx={{
|
sx={{
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
height: 40,
|
height: 40,
|
||||||
@@ -578,11 +667,20 @@ class ManifestHeader extends PureComponent {
|
|||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: '0.875rem',
|
fontSize: '0.875rem',
|
||||||
|
...(!canCreatePdf && {
|
||||||
|
borderColor: 'action.disabled',
|
||||||
|
color: 'text.disabled',
|
||||||
|
'&.Mui-disabled': {
|
||||||
|
borderColor: 'action.disabled',
|
||||||
|
color: 'text.disabled',
|
||||||
|
},
|
||||||
|
}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{pdfCreating ? 'Wird erstellt…' : 'PDF erstellen'}
|
{pdfCreating ? 'Wird erstellt…' : 'PDF erstellen'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
);
|
||||||
|
})()}
|
||||||
{onPdfUpload ? (
|
{onPdfUpload ? (
|
||||||
<FileDropzone
|
<FileDropzone
|
||||||
accept="application/pdf,.pdf,text/plain,.txt,text/csv,.csv"
|
accept="application/pdf,.pdf,text/plain,.txt,text/csv,.csv"
|
||||||
|
|||||||
@@ -199,8 +199,28 @@ class ManifestLines extends PureComponent {
|
|||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
{(() => {
|
{(() => {
|
||||||
const hasShippedLines = kind === 'order' && Array.isArray(shipments) && shipments.some((sh) => (sh.lines?.length || 0) > 0);
|
const shippedSkus = new Set();
|
||||||
const hasLedgeredLines = kind === 'delivery' && Array.isArray(lines) && lines.some((line) => {
|
if (kind === 'order' && Array.isArray(shipments)) {
|
||||||
|
for (const sh of shipments) {
|
||||||
|
for (const sl of sh.lines || []) {
|
||||||
|
if (sl.sku) shippedSkus.add(String(sl.sku).trim().toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const allLinesShipped =
|
||||||
|
kind === 'order' &&
|
||||||
|
shippedSkus.size > 0 &&
|
||||||
|
Array.isArray(lines) &&
|
||||||
|
lines.length > 0 &&
|
||||||
|
lines.every((l) => {
|
||||||
|
const raw = l?.sku != null ? String(l.sku).trim().toLowerCase() : '';
|
||||||
|
return raw && shippedSkus.has(raw);
|
||||||
|
});
|
||||||
|
const allLinesLedgered =
|
||||||
|
kind === 'delivery' &&
|
||||||
|
Array.isArray(lines) &&
|
||||||
|
lines.length > 0 &&
|
||||||
|
lines.every((line) => {
|
||||||
const ledgerLine = line?.id != null ? ledgerLinesById?.[line.id] : null;
|
const ledgerLine = line?.id != null ? ledgerLinesById?.[line.id] : null;
|
||||||
return Boolean(
|
return Boolean(
|
||||||
ledgerLine?.isBooked ||
|
ledgerLine?.isBooked ||
|
||||||
@@ -210,11 +230,11 @@ class ManifestLines extends PureComponent {
|
|||||||
line?.is_booked
|
line?.is_booked
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const disabled = saving || !lines || lines.length === 0 || hasShippedLines || hasLedgeredLines;
|
const disabled = saving || !lines || lines.length === 0 || allLinesShipped || allLinesLedgered;
|
||||||
const title = hasLedgeredLines
|
const title = allLinesLedgered
|
||||||
? 'Lieferung enthält bereits im Ledger verbuchte Positionen'
|
? 'Alle Positionen der Lieferung sind bereits im Ledger verbucht'
|
||||||
: hasShippedLines
|
: allLinesShipped
|
||||||
? 'Auftrag enthält bereits ausgelieferte Positionen'
|
? 'Alle Positionen des Auftrags sind bereits ausgeliefert'
|
||||||
: 'Alle Zeilen entfernen';
|
: 'Alle Zeilen entfernen';
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ export default class OrderShipmentDialog extends PureComponent {
|
|||||||
onChange={() => this.handleToggleAll(openItems)}
|
onChange={() => this.handleToggleAll(openItems)}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell sx={{ fontWeight: 600 }}>SKU</TableCell>
|
<TableCell sx={{ fontWeight: 600 }}>ArtNr</TableCell>
|
||||||
<TableCell sx={{ fontWeight: 600 }}>Artikel</TableCell>
|
<TableCell sx={{ fontWeight: 600 }}>Artikel</TableCell>
|
||||||
<TableCell align="right" sx={{ fontWeight: 600 }}>Bestellt</TableCell>
|
<TableCell align="right" sx={{ fontWeight: 600 }}>Bestellt</TableCell>
|
||||||
<TableCell align="right" sx={{ fontWeight: 600 }}>Geliefert</TableCell>
|
<TableCell align="right" sx={{ fontWeight: 600 }}>Geliefert</TableCell>
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ export default class ShipmentDetailDialog extends PureComponent {
|
|||||||
<Table size="small">
|
<Table size="small">
|
||||||
<TableHead sx={{ bgcolor: SURFACE_RAISED }}>
|
<TableHead sx={{ bgcolor: SURFACE_RAISED }}>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell sx={{ fontWeight: 600, py: 0.75 }}>SKU</TableCell>
|
<TableCell sx={{ fontWeight: 600, py: 0.75 }}>ArtNr</TableCell>
|
||||||
<TableCell sx={{ fontWeight: 600, py: 0.75 }}>Bezeichnung</TableCell>
|
<TableCell sx={{ fontWeight: 600, py: 0.75 }}>Bezeichnung</TableCell>
|
||||||
<TableCell align="right" sx={{ fontWeight: 600, py: 0.75 }}>Menge</TableCell>
|
<TableCell align="right" sx={{ fontWeight: 600, py: 0.75 }}>Menge</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ export function buildLines(lines, { requireAtLeastOne }) {
|
|||||||
|
|
||||||
const missing = [];
|
const missing = [];
|
||||||
if (!sku && !name) {
|
if (!sku && !name) {
|
||||||
missing.push('Artikel (SKU oder Name)');
|
missing.push('Artikel (ArtNr oder Name)');
|
||||||
}
|
}
|
||||||
if (amount == null) {
|
if (amount == null) {
|
||||||
missing.push(rawAmount != null ? 'gültige Menge' : 'Menge');
|
missing.push(rawAmount != null ? 'gültige Menge' : 'Menge');
|
||||||
@@ -555,36 +555,36 @@ export async function buildManifestBody(
|
|||||||
|
|
||||||
export const LINE_COLUMNS = [
|
export const LINE_COLUMNS = [
|
||||||
{ id: 'pos', label: 'Pos', width: 44, align: 'center' },
|
{ id: 'pos', label: 'Pos', width: 44, align: 'center' },
|
||||||
{ id: 'amount', label: 'Amount', width: 80, align: 'right', type: 'number' },
|
{ id: 'amount', label: 'Anzahl', width: 80, align: 'right', type: 'number' },
|
||||||
{ id: 'sku', label: 'SKU', width: 130, align: 'left', type: 'text' },
|
{ id: 'sku', label: 'ArtNr', width: 130, align: 'left', type: 'text' },
|
||||||
{ id: 'name', label: 'Name', width: 200, align: 'left', type: 'text' },
|
{ id: 'name', label: 'Name', width: 200, align: 'left', type: 'text' },
|
||||||
{ id: 'tax', label: 'Tax', width: 70, align: 'right', type: 'number' },
|
{ id: 'tax', label: 'MwSt', width: 70, align: 'right', type: 'number' },
|
||||||
{ id: 'rebate', label: 'Rebate', width: 75, align: 'right', type: 'number' },
|
{ id: 'rebate', label: 'Rabatt', width: 75, align: 'right', type: 'number' },
|
||||||
{ id: 'net', label: 'Net', width: 95, align: 'right', type: 'number' },
|
{ id: 'net', label: 'Netto', width: 95, align: 'right', type: 'number' },
|
||||||
{ id: 'gross', label: 'Gross', width: 95, align: 'right', type: 'number' },
|
{ id: 'gross', label: 'Brutto', width: 95, align: 'right', type: 'number' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const QUOTE_LINE_COLUMNS = LINE_COLUMNS;
|
export const QUOTE_LINE_COLUMNS = LINE_COLUMNS;
|
||||||
|
|
||||||
export const ORDER_LINE_COLUMNS = [
|
export const ORDER_LINE_COLUMNS = [
|
||||||
{ id: 'pos', label: 'Pos', width: 44, align: 'center' },
|
{ id: 'pos', label: 'Pos', width: 44, align: 'center' },
|
||||||
{ id: 'sku', label: 'SKU', width: 130, align: 'left', type: 'text' },
|
{ id: 'sku', label: 'ArtNr', width: 130, align: 'left', type: 'text' },
|
||||||
{ id: 'name', label: 'Name', width: 200, align: 'left', type: 'text' },
|
{ id: 'name', label: 'Name', width: 200, align: 'left', type: 'text' },
|
||||||
{ id: 'amount', label: 'Amount', width: 80, align: 'right', type: 'number' },
|
{ id: 'amount', label: 'Anzahl', width: 80, align: 'right', type: 'number' },
|
||||||
{ id: 'tax', label: 'Tax', width: 70, align: 'right', type: 'number' },
|
{ id: 'tax', label: 'MwSt', width: 70, align: 'right', type: 'number' },
|
||||||
{ id: 'rebate', label: 'Rebate', width: 75, align: 'right', type: 'number' },
|
{ id: 'rebate', label: 'Rabatt', width: 75, align: 'right', type: 'number' },
|
||||||
{ id: 'net', label: 'Net', width: 95, align: 'right', type: 'number' },
|
{ id: 'net', label: 'Netto', width: 95, align: 'right', type: 'number' },
|
||||||
{ id: 'gross', label: 'Gross', width: 95, align: 'right', type: 'number' },
|
{ id: 'gross', label: 'Brutto', width: 95, align: 'right', type: 'number' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const DELIVERY_LINE_COLUMNS = [
|
export const DELIVERY_LINE_COLUMNS = [
|
||||||
{ id: 'pos', label: 'Pos', width: 44, align: 'center' },
|
{ id: 'pos', label: 'Pos', width: 44, align: 'center' },
|
||||||
{ id: 'sku', label: 'SKU', width: 130, align: 'left', type: 'text' },
|
{ id: 'sku', label: 'ArtNr', width: 130, align: 'left', type: 'text' },
|
||||||
{ id: 'name', label: 'Name', width: 220, align: 'left', type: 'text' },
|
{ id: 'name', label: 'Name', width: 220, align: 'left', type: 'text' },
|
||||||
{ id: 'amount', label: 'Amount', width: 88, align: 'right', type: 'number' },
|
{ id: 'amount', label: 'Anzahl', width: 88, align: 'right', type: 'number' },
|
||||||
{ id: 'pu', label: 'PU', width: 80, align: 'right', type: 'number' },
|
{ id: 'pu', label: 'PU', width: 80, align: 'right', type: 'number' },
|
||||||
{ id: 'taxrate', label: 'Tax', width: 80, align: 'right', type: 'number' },
|
{ id: 'taxrate', label: 'MwSt', width: 80, align: 'right', type: 'number' },
|
||||||
{ id: 'netprice', label: 'Net', width: 104, align: 'right', type: 'number' },
|
{ id: 'netprice', label: 'Netto', width: 104, align: 'right', type: 'number' },
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @typedef {'delivery'|'quote'|'order'} ManifestKind */
|
/** @typedef {'delivery'|'quote'|'order'} ManifestKind */
|
||||||
@@ -687,7 +687,7 @@ export function manifestConfig(cfgOrKind) {
|
|||||||
listField: 'quotes',
|
listField: 'quotes',
|
||||||
docField: 'quote',
|
docField: 'quote',
|
||||||
partyField: 'entityId',
|
partyField: 'entityId',
|
||||||
listPath: '/auftrag',
|
listPath: '/auftrag/angebot',
|
||||||
createPath: '/auftrag/neuesangebot',
|
createPath: '/auftrag/neuesangebot',
|
||||||
editPath: (id) => `/auftrag/${id}`,
|
editPath: (id) => `/auftrag/${id}`,
|
||||||
dateLabel: 'Gültig bis',
|
dateLabel: 'Gültig bis',
|
||||||
@@ -720,13 +720,13 @@ export function manifestConfig(cfgOrKind) {
|
|||||||
listField: 'orders',
|
listField: 'orders',
|
||||||
docField: 'order',
|
docField: 'order',
|
||||||
partyField: 'entityId',
|
partyField: 'entityId',
|
||||||
listPath: '/auftrag/bestellung',
|
listPath: '/auftrag',
|
||||||
createPath: '/auftrag/bestellung/neu',
|
createPath: '/auftrag/bestellung/neu',
|
||||||
editPath: (id) => `/auftrag/bestellung/${id}`,
|
editPath: (id) => `/auftrag/bestellung/${id}`,
|
||||||
dateLabel: 'Lieferdatum',
|
dateLabel: 'Lieferdatum',
|
||||||
partyLabel: 'Kunde',
|
partyLabel: 'Kunde',
|
||||||
emptyList: 'Noch keine Bestellungen.',
|
emptyList: 'Noch keine Bestellungen.',
|
||||||
createButton: 'Neue Bestellung',
|
createButton: 'Neuer Auftrag',
|
||||||
createdMsg: 'Bestellung angelegt.',
|
createdMsg: 'Bestellung angelegt.',
|
||||||
savedMsg: 'Bestellung gespeichert.',
|
savedMsg: 'Bestellung gespeichert.',
|
||||||
deletedMsg: 'Bestellung gelöscht.',
|
deletedMsg: 'Bestellung gelöscht.',
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ function getInitialTarget() {
|
|||||||
const p = window.location.pathname;
|
const p = window.location.pathname;
|
||||||
if (p.startsWith('/lieferung')) return 'delivery';
|
if (p.startsWith('/lieferung')) return 'delivery';
|
||||||
if (p.startsWith('/auftrag/ausliefern')) return 'shipment';
|
if (p.startsWith('/auftrag/ausliefern')) return 'shipment';
|
||||||
if (p.startsWith('/auftrag/bestellung')) return 'order';
|
if (p.startsWith('/auftrag/angebot')) return 'quote';
|
||||||
if (p.startsWith('/auftrag')) return 'quote';
|
if (p.startsWith('/auftrag')) return 'order';
|
||||||
}
|
}
|
||||||
return 'order';
|
return 'order';
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/nav.js
21
src/nav.js
@@ -202,18 +202,18 @@ export const SUB_NAV = {
|
|||||||
'/auftrag': [
|
'/auftrag': [
|
||||||
{
|
{
|
||||||
segment: '',
|
segment: '',
|
||||||
|
label: 'Auftrag',
|
||||||
|
title: 'Auftrag',
|
||||||
|
icon: ShoppingCartIcon,
|
||||||
|
help: 'Bestellungen prüfen, freigeben und kommissionieren',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
segment: 'angebot',
|
||||||
label: 'Angebot',
|
label: 'Angebot',
|
||||||
title: 'Angebot',
|
title: 'Angebot',
|
||||||
icon: RequestQuoteIcon,
|
icon: RequestQuoteIcon,
|
||||||
help: 'Angebote für Kunden kalkulieren und versenden',
|
help: 'Angebote für Kunden kalkulieren und versenden',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
segment: 'bestellung',
|
|
||||||
label: 'Bestellung',
|
|
||||||
title: 'Bestellung',
|
|
||||||
icon: ShoppingCartIcon,
|
|
||||||
help: 'Bestellungen prüfen, freigeben und kommissionieren',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
segment: 'ausliefern',
|
segment: 'ausliefern',
|
||||||
label: 'Ausliefern',
|
label: 'Ausliefern',
|
||||||
@@ -336,11 +336,16 @@ export function resolveActiveSub(pathname, sectionPath) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep Bestellung active for nested paths (e.g. /auftrag/bestellung/neu).
|
// Keep sub active for nested paths (e.g. /auftrag/ausliefern, /auftrag/angebot).
|
||||||
if (sectionPath === '/auftrag' && rest && knownSegments.has(rest)) {
|
if (sectionPath === '/auftrag' && rest && knownSegments.has(rest)) {
|
||||||
return rest;
|
return rest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Treat /auftrag/bestellung as segment ''
|
||||||
|
if (sectionPath === '/auftrag' && rest === 'bestellung') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// Treat /infos/articlehistory as segment ''
|
// Treat /infos/articlehistory as segment ''
|
||||||
if (sectionPath === '/infos' && rest === 'articlehistory') {
|
if (sectionPath === '/infos' && rest === 'articlehistory') {
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -635,7 +635,7 @@ class ArtikelSearchPage extends Component {
|
|||||||
sx={{ maxWidth: 520, flexGrow: 1 }}
|
sx={{ maxWidth: 520, flexGrow: 1 }}
|
||||||
autoFocus
|
autoFocus
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder="(EAN, SKU, Name)"
|
placeholder="(EAN, ArtNr, Name)"
|
||||||
value={input}
|
value={input}
|
||||||
onChange={this.handleInputChange}
|
onChange={this.handleInputChange}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
this.handleRemoveLine = this.handleRemoveLine.bind(this);
|
this.handleRemoveLine = this.handleRemoveLine.bind(this);
|
||||||
this.handleMoveLine = this.handleMoveLine.bind(this);
|
this.handleMoveLine = this.handleMoveLine.bind(this);
|
||||||
this.handleRemoveAllLines = this.handleRemoveAllLines.bind(this);
|
this.handleRemoveAllLines = this.handleRemoveAllLines.bind(this);
|
||||||
|
this.handleRestore = this.handleRestore.bind(this);
|
||||||
this.handleSave = this.handleSave.bind(this);
|
this.handleSave = this.handleSave.bind(this);
|
||||||
this.handlePdfUpload = this.handlePdfUpload.bind(this);
|
this.handlePdfUpload = this.handlePdfUpload.bind(this);
|
||||||
this.handlePdfRescan = this.handlePdfRescan.bind(this);
|
this.handlePdfRescan = this.handlePdfRescan.bind(this);
|
||||||
@@ -527,7 +528,7 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
|
|
||||||
const internalSku = article?.sku;
|
const internalSku = article?.sku;
|
||||||
if (!internalSku) {
|
if (!internalSku) {
|
||||||
const msg = 'Artikel hat keine SKU.';
|
const msg = 'Artikel hat keine ArtNr.';
|
||||||
enqueueSnackbar(msg, { variant: 'error' });
|
enqueueSnackbar(msg, { variant: 'error' });
|
||||||
throw new Error(msg);
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
@@ -686,6 +687,24 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
|
|
||||||
handleRemoveAllLines() {
|
handleRemoveAllLines() {
|
||||||
this.automat.actions.clearLineitems();
|
this.automat.actions.clearLineitems();
|
||||||
|
this.setState({ lines: [] });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRestore() {
|
||||||
|
if (!this._initialDoc) return;
|
||||||
|
const base = this._initialDoc;
|
||||||
|
this.automat.actions.setDate(base.date);
|
||||||
|
if (base.partyId != null) {
|
||||||
|
this.automat.actions.setDeliveryEntity(base.partyId);
|
||||||
|
}
|
||||||
|
this.automat.actions.setExternalNumber(base.externalNumber || '');
|
||||||
|
const restoredLines = (base.lines || []).map((l) => ({ ...l }));
|
||||||
|
this.automat.actions.setLineitems(restoredLines);
|
||||||
|
this.automat.actions.setAttachments([...(base.attachments || [])]);
|
||||||
|
this.setState({
|
||||||
|
lines: restoredLines,
|
||||||
|
});
|
||||||
|
enqueueSnackbar('Änderungen verworfen und ursprünglicher Stand wiederhergestellt.', { variant: 'info' });
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePdfUpload(file) {
|
async handlePdfUpload(file) {
|
||||||
@@ -1331,6 +1350,7 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
onNumberChange={this.handleNumberChange}
|
onNumberChange={this.handleNumberChange}
|
||||||
onExternalNumberChange={this.handleExternalNumberChange}
|
onExternalNumberChange={this.handleExternalNumberChange}
|
||||||
docId={this.props.docId}
|
docId={this.props.docId}
|
||||||
|
onRestore={isEdit ? this.handleRestore : undefined}
|
||||||
ledgerSummary={this.state.ledgerSummary}
|
ledgerSummary={this.state.ledgerSummary}
|
||||||
onOpenBookDialog={this.handleOpenBookDialog}
|
onOpenBookDialog={this.handleOpenBookDialog}
|
||||||
/>
|
/>
|
||||||
@@ -1497,8 +1517,8 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
<TableHead sx={{ bgcolor: SURFACE_RAISED }}>
|
<TableHead sx={{ bgcolor: SURFACE_RAISED }}>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell sx={{ fontWeight: 600, py: 0.5, whiteSpace: 'nowrap' }}>Zeitpunkt</TableCell>
|
<TableCell sx={{ fontWeight: 600, py: 0.5, whiteSpace: 'nowrap' }}>Zeitpunkt</TableCell>
|
||||||
<TableCell sx={{ fontWeight: 600, py: 0.5, whiteSpace: 'nowrap' }}>Lieferanten SKU</TableCell>
|
<TableCell sx={{ fontWeight: 600, py: 0.5, whiteSpace: 'nowrap' }}>Lieferanten-ArtNr</TableCell>
|
||||||
<TableCell sx={{ fontWeight: 600, py: 0.5, whiteSpace: 'nowrap' }}>Eigene SKU</TableCell>
|
<TableCell sx={{ fontWeight: 600, py: 0.5, whiteSpace: 'nowrap' }}>Eigene ArtNr</TableCell>
|
||||||
<TableCell sx={{ fontWeight: 600, py: 0.5 }}>Name</TableCell>
|
<TableCell sx={{ fontWeight: 600, py: 0.5 }}>Name</TableCell>
|
||||||
<TableCell align="right" sx={{ fontWeight: 600, py: 0.5, whiteSpace: 'nowrap' }}>Menge geliefert</TableCell>
|
<TableCell align="right" sx={{ fontWeight: 600, py: 0.5, whiteSpace: 'nowrap' }}>Menge geliefert</TableCell>
|
||||||
<TableCell sx={{ fontWeight: 600, py: 0.5, whiteSpace: 'nowrap' }}>Lager</TableCell>
|
<TableCell sx={{ fontWeight: 600, py: 0.5, whiteSpace: 'nowrap' }}>Lager</TableCell>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import numberAutomat from '../automats/numberAutomat';
|
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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a number pattern given prefix, padding width, and counter.
|
* Formats a number pattern given prefix, padding width, and counter.
|
||||||
@@ -18,9 +19,9 @@ export function formatNumberPattern(prefix, padding, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the next document number for quote or order based on company sequences.
|
* Returns the next document number for quote, order, or delivery based on company sequences.
|
||||||
*
|
*
|
||||||
* @param {'quote'|'order'|string} kind
|
* @param {'quote'|'order'|'delivery'|string} kind
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getNextDocumentNumber(kind) {
|
export function getNextDocumentNumber(kind) {
|
||||||
@@ -42,7 +43,9 @@ export function getNextDocumentNumber(kind) {
|
|||||||
let prefix = '';
|
let prefix = '';
|
||||||
let startNum = 1;
|
let startNum = 1;
|
||||||
|
|
||||||
if (
|
if (kind === 'delivery') {
|
||||||
|
prefix = prefixStyle.startsWith('DEL-') ? 'DEL-' : (prefixStyle.startsWith('L-') ? 'L-' : 'LS-');
|
||||||
|
} else if (
|
||||||
prefixStyle.startsWith('ART-') ||
|
prefixStyle.startsWith('ART-') ||
|
||||||
prefixStyle.startsWith('LS-') ||
|
prefixStyle.startsWith('LS-') ||
|
||||||
prefixStyle.startsWith('KD-') ||
|
prefixStyle.startsWith('KD-') ||
|
||||||
@@ -69,8 +72,8 @@ export function getNextDocumentNumber(kind) {
|
|||||||
prefix = kind === 'quote' ? 'ANG-' : (kind === 'order' ? 'AUF-' : '');
|
prefix = kind === 'quote' ? 'ANG-' : (kind === 'order' ? 'AUF-' : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check existing docs from quoteAutomat / orderAutomat to avoid collisions
|
// Check existing docs from quoteAutomat / orderAutomat / deliveryAutomat to avoid collisions
|
||||||
const listAutomat = kind === 'quote' ? quoteAutomat : (kind === 'order' ? orderAutomat : 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 = startNum - 1;
|
||||||
|
|
||||||
@@ -99,11 +102,11 @@ export function getNextDocumentNumber(kind) {
|
|||||||
/**
|
/**
|
||||||
* Asynchronously ensures required stores are loaded, then returns the next document number.
|
* Asynchronously ensures required stores are loaded, then returns the next document number.
|
||||||
*
|
*
|
||||||
* @param {'quote'|'order'|string} kind
|
* @param {'quote'|'order'|'delivery'|string} kind
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
export async function ensureDocumentNumber(kind) {
|
export async function ensureDocumentNumber(kind) {
|
||||||
const listAutomat = kind === 'quote' ? quoteAutomat : (kind === 'order' ? orderAutomat : null);
|
const listAutomat = kind === 'quote' ? quoteAutomat : (kind === 'order' ? orderAutomat : (kind === 'delivery' ? deliveryAutomat : null));
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
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(),
|
||||||
|
|||||||
Reference in New Issue
Block a user