Compare commits
6 Commits
3df8c5e9c7
...
596d0dc6f1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
596d0dc6f1 | ||
|
|
c15fa0a53e | ||
|
|
9cb93bddeb | ||
|
|
b42b63a0af | ||
|
|
cf85ec102d | ||
|
|
5644678b7d |
@@ -35,6 +35,7 @@ Reference guide for the backend LLM agent when emitting route navigation actions
|
|||||||
| **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` |
|
||||||
| **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` |
|
||||||
|
|
||||||
| **Customer List** | `/kunde` | Static | `/kunde` |
|
| **Customer List** | `/kunde` | Static | `/kunde` |
|
||||||
| **New Customer** | `/kunde/neu` | Static form | `/kunde/neu` |
|
| **New Customer** | `/kunde/neu` | Static form | `/kunde/neu` |
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="de" class="notranslate" translate="no">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="google" content="notranslate" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
<title>QuixPOS * Management Interface</title>
|
<title>QuixPOS * Management Interface</title>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { http } from './http.js';
|
import { http } from './http.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Company-scoped carriers / Lieferarten.
|
* Company-scoped carriers / Versandarten.
|
||||||
* Shape: Carrier = { id, name, sort, hasPackages, isReverse }
|
* Shape: Carrier = { id, name, sort, hasPackages, isReverse }
|
||||||
*/
|
*/
|
||||||
export const carrier = {
|
export const carrier = {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import AuftragBestellungNeuPage from './routes/auftrag/bestellung/neu';
|
|||||||
import AuftragBestellungEditPage from './routes/auftrag/bestellung/_orderId';
|
import AuftragBestellungEditPage from './routes/auftrag/bestellung/_orderId';
|
||||||
import AuftragAusliefernPage from './routes/auftrag/ausliefern';
|
import AuftragAusliefernPage from './routes/auftrag/ausliefern';
|
||||||
import AuftragZahlungenPage from './routes/auftrag/zahlungen';
|
import AuftragZahlungenPage from './routes/auftrag/zahlungen';
|
||||||
import AuftragLieferartenPage from './routes/auftrag/lieferarten';
|
import AuftragVersandartenPage from './routes/auftrag/versandarten';
|
||||||
|
|
||||||
import KundeListePage from './routes/kunde';
|
import KundeListePage from './routes/kunde';
|
||||||
import KundeNeuPage from './routes/kunde/neu';
|
import KundeNeuPage from './routes/kunde/neu';
|
||||||
@@ -107,7 +107,8 @@ const routes = [
|
|||||||
{ path: 'bestellung/:orderId', element: <AuftragBestellungEditPage /> },
|
{ path: 'bestellung/:orderId', element: <AuftragBestellungEditPage /> },
|
||||||
{ path: 'ausliefern', element: <AuftragAusliefernPage /> },
|
{ path: 'ausliefern', element: <AuftragAusliefernPage /> },
|
||||||
{ path: 'zahlungen', element: <AuftragZahlungenPage /> },
|
{ path: 'zahlungen', element: <AuftragZahlungenPage /> },
|
||||||
{ path: 'lieferarten', element: <AuftragLieferartenPage /> },
|
{ path: 'versandarten', element: <AuftragVersandartenPage /> },
|
||||||
|
{ path: 'lieferarten', element: <Navigate to="/auftrag/versandarten" replace /> },
|
||||||
{ path: ':quoteId', element: <AuftragAngebotEditPage /> },
|
{ path: ':quoteId', element: <AuftragAngebotEditPage /> },
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @file carrierAutomat.js
|
* @file carrierAutomat.js
|
||||||
* @description Manages cached shipping carriers / Lieferarten (`GET /api/carrier`) for the active company.
|
* @description Manages cached shipping carriers / Versandarten (`GET /api/carrier`) for the active company.
|
||||||
*
|
*
|
||||||
* Upstream Triggers:
|
* Upstream Triggers:
|
||||||
* - `authAutomat`: Subscribes to auth state changes (`syncWithAuth`) — triggers
|
* - `authAutomat`: Subscribes to auth state changes (`syncWithAuth`) — triggers
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
import { Automat } from 'react-automat';
|
import { Automat } from 'react-automat';
|
||||||
import { article as articleApi, supplier as supplierApi } from '../api';
|
import { article as articleApi, supplier as supplierApi } from '../api';
|
||||||
|
import { todayInputValue } from '../components/manifest/manifestConfig';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} DeliveryLine
|
* @typedef {Object} DeliveryLine
|
||||||
@@ -155,7 +156,7 @@ export function buildSupplierArticlesMap(articles = [], existingMap = {}) {
|
|||||||
export const initialDelivery = {
|
export const initialDelivery = {
|
||||||
id: null,
|
id: null,
|
||||||
deliveryId: null,
|
deliveryId: null,
|
||||||
date: null,
|
date: todayInputValue(),
|
||||||
deliveryEntity: null,
|
deliveryEntity: null,
|
||||||
partyId: null,
|
partyId: null,
|
||||||
externalNumber: null,
|
externalNumber: null,
|
||||||
@@ -226,12 +227,14 @@ export function createDeliveryEditorAutomat(options = {}) {
|
|||||||
: delivery.notes ? [delivery.notes] : [];
|
: delivery.notes ? [delivery.notes] : [];
|
||||||
const rawAttachments = delivery.attachments ?? delivery.files ?? [];
|
const rawAttachments = delivery.attachments ?? delivery.files ?? [];
|
||||||
const attachments = Array.isArray(rawAttachments) ? rawAttachments : [];
|
const attachments = Array.isArray(rawAttachments) ? rawAttachments : [];
|
||||||
|
const date = delivery.date !== undefined ? delivery.date : (id != null ? null : (automat.state.date || todayInputValue()));
|
||||||
|
|
||||||
automat.setState({
|
automat.setState({
|
||||||
...initialDelivery,
|
...initialDelivery,
|
||||||
...delivery,
|
...delivery,
|
||||||
id,
|
id,
|
||||||
deliveryId: id,
|
deliveryId: id,
|
||||||
|
date,
|
||||||
partyId,
|
partyId,
|
||||||
deliveryEntity: partyId,
|
deliveryEntity: partyId,
|
||||||
externalNumber: extNum,
|
externalNumber: extNum,
|
||||||
@@ -670,6 +673,7 @@ export function createDeliveryEditorAutomat(options = {}) {
|
|||||||
resetDelivery() {
|
resetDelivery() {
|
||||||
automat.setState({
|
automat.setState({
|
||||||
...initialDelivery,
|
...initialDelivery,
|
||||||
|
date: todayInputValue(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -677,6 +681,7 @@ export function createDeliveryEditorAutomat(options = {}) {
|
|||||||
resetDeliveryEditor() {
|
resetDeliveryEditor() {
|
||||||
automat.setState({
|
automat.setState({
|
||||||
...initialState,
|
...initialState,
|
||||||
|
date: todayInputValue(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -718,7 +723,7 @@ export function createDeliveryEditorAutomat(options = {}) {
|
|||||||
basedata.invoiceNumber ??
|
basedata.invoiceNumber ??
|
||||||
basedata.invoice_number ??
|
basedata.invoice_number ??
|
||||||
null;
|
null;
|
||||||
const date = basedata.date ?? null;
|
const date = basedata.date ?? automat.state.date ?? todayInputValue();
|
||||||
const type = basedata.manifestType ?? basedata.type ?? 'rechnung';
|
const type = basedata.manifestType ?? basedata.type ?? 'rechnung';
|
||||||
|
|
||||||
let matchedPartyId = partyId != null && partyId !== '' ? partyId : null;
|
let matchedPartyId = partyId != null && partyId !== '' ? partyId : null;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function createInitialManifestDraftState(kind) {
|
|||||||
kind,
|
kind,
|
||||||
mode: 'create',
|
mode: 'create',
|
||||||
id: null,
|
id: null,
|
||||||
date: kind === 'quote' ? todayInputValue() : '',
|
date: todayInputValue(),
|
||||||
partyId: null,
|
partyId: null,
|
||||||
number: '',
|
number: '',
|
||||||
externalNumber: '',
|
externalNumber: '',
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined
|
|||||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||||
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
||||||
|
import GoogleIcon from '@mui/icons-material/Google';
|
||||||
import { enqueueSnackbar } from 'notistack';
|
import { enqueueSnackbar } from 'notistack';
|
||||||
import AppDialog from './AppDialog';
|
import AppDialog from './AppDialog';
|
||||||
import { thumb, article as articleApi } from '../api';
|
import { thumb, article as articleApi } from '../api';
|
||||||
@@ -29,6 +30,7 @@ import {
|
|||||||
articleWithPhotoOrder,
|
articleWithPhotoOrder,
|
||||||
articleWithUploadedPhoto,
|
articleWithUploadedPhoto,
|
||||||
articleWithoutPhoto,
|
articleWithoutPhoto,
|
||||||
|
buildGoogleSearchQuery,
|
||||||
formatPhotoDimensions,
|
formatPhotoDimensions,
|
||||||
formatPhotoSizeKb,
|
formatPhotoSizeKb,
|
||||||
sortedPhotos,
|
sortedPhotos,
|
||||||
@@ -182,6 +184,22 @@ function resolveTitle(props, single) {
|
|||||||
}
|
}
|
||||||
return single ? 'Bild' : 'Bilder';
|
return single ? 'Bild' : 'Bilder';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveGoogleImagesQuery(props) {
|
||||||
|
if (props.article) {
|
||||||
|
return buildGoogleSearchQuery(props.article);
|
||||||
|
}
|
||||||
|
if (props.brand?.name) {
|
||||||
|
return String(props.brand.name).trim();
|
||||||
|
}
|
||||||
|
if (props.category?.name) {
|
||||||
|
return String(props.category.name).trim();
|
||||||
|
}
|
||||||
|
if (props.attributeValue) {
|
||||||
|
return String(props.attributeValue.value || props.attributeValue.name || '').trim();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -400,6 +418,13 @@ export default class PicturesDialog extends PureComponent {
|
|||||||
handleSearchClose = () => {
|
handleSearchClose = () => {
|
||||||
this.setState({ searchOpen: false });
|
this.setState({ searchOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleOpenGoogleImages = () => {
|
||||||
|
const query = resolveGoogleImagesQuery(this.props);
|
||||||
|
if (!query) return;
|
||||||
|
const url = `https://www.google.com/search?tbm=isch&q=${encodeURIComponent(query)}`;
|
||||||
|
window.open(url, '_blank', 'noopener,noreferrer');
|
||||||
|
};
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region render helpers
|
// #region render helpers
|
||||||
@@ -627,6 +652,7 @@ export default class PicturesDialog extends PureComponent {
|
|||||||
const pics = resolvePictures(this.props, single);
|
const pics = resolvePictures(this.props, single);
|
||||||
const title = resolveTitle(this.props, single);
|
const title = resolveTitle(this.props, single);
|
||||||
const hasPic = Boolean(pics[0]?.fullHash || pics[0]?.thumbHash);
|
const hasPic = Boolean(pics[0]?.fullHash || pics[0]?.thumbHash);
|
||||||
|
const googleQuery = resolveGoogleImagesQuery(this.props);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -781,6 +807,16 @@ export default class PicturesDialog extends PureComponent {
|
|||||||
>
|
>
|
||||||
Bild suchen
|
Bild suchen
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<GoogleIcon />}
|
||||||
|
disabled={busy || disabled || !googleQuery}
|
||||||
|
onClick={this.handleOpenGoogleImages}
|
||||||
|
title={googleQuery ? `Google Bilder-Suche: ${googleQuery}` : 'Google'}
|
||||||
|
>
|
||||||
|
Google
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@@ -244,7 +244,6 @@ class SearchResultRow extends PureComponent {
|
|||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{article.available != null && ` · Verfügbar ${formatStock(article.available)}`}
|
|
||||||
{primaryEan ? ` · EAN ${primaryEan}` : ''}
|
{primaryEan ? ` · EAN ${primaryEan}` : ''}
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,24 @@ import Stack from '@mui/material/Stack';
|
|||||||
import Tooltip from '@mui/material/Tooltip';
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import CallMergeIcon from '@mui/icons-material/CallMerge';
|
import CallMergeIcon from '@mui/icons-material/CallMerge';
|
||||||
|
import GoogleIcon from '@mui/icons-material/Google';
|
||||||
import { enqueueSnackbar } from 'notistack';
|
import { enqueueSnackbar } from 'notistack';
|
||||||
|
|
||||||
import { article as articleApi } from '../../../api';
|
import { article as articleApi } from '../../../api';
|
||||||
import { artikelAutomat, getArticle } from '../../../automats';
|
import { artikelAutomat, getArticle } from '../../../automats';
|
||||||
import { notifyError } from '../../../utils/notify';
|
import { notifyError } from '../../../utils/notify';
|
||||||
|
import {
|
||||||
|
articleEans,
|
||||||
|
getArticleEan,
|
||||||
|
getArticleBrand,
|
||||||
|
getArticleName,
|
||||||
|
buildGoogleSearchQuery,
|
||||||
|
} from '../../../utils/articleDetail';
|
||||||
import ArticleSearch from '../ArticleSearch';
|
import ArticleSearch from '../ArticleSearch';
|
||||||
import ArticleUnifyDialog from '../ArticleUnifyDialog';
|
import ArticleUnifyDialog from '../ArticleUnifyDialog';
|
||||||
|
|
||||||
|
export { getArticleEan, getArticleBrand, getArticleName, buildGoogleSearchQuery };
|
||||||
|
|
||||||
export function isArticleFather(article) {
|
export function isArticleFather(article) {
|
||||||
if (!article) return false;
|
if (!article) return false;
|
||||||
return Boolean(
|
return Boolean(
|
||||||
@@ -48,6 +58,15 @@ export default class ArticleSettingsTab extends Component {
|
|||||||
this.handleDeleteArticle = this.handleDeleteArticle.bind(this);
|
this.handleDeleteArticle = this.handleDeleteArticle.bind(this);
|
||||||
this.handleOpenSearch = this.handleOpenSearch.bind(this);
|
this.handleOpenSearch = this.handleOpenSearch.bind(this);
|
||||||
this.handleSelectTargetArticle = this.handleSelectTargetArticle.bind(this);
|
this.handleSelectTargetArticle = this.handleSelectTargetArticle.bind(this);
|
||||||
|
this.handleOpenGoogle = this.handleOpenGoogle.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOpenGoogle() {
|
||||||
|
const { article: articleData } = this.props;
|
||||||
|
const query = buildGoogleSearchQuery(articleData);
|
||||||
|
if (!query) return;
|
||||||
|
const url = `https://www.google.com/search?q=${encodeURIComponent(query)}`;
|
||||||
|
window.open(url, '_blank', 'noopener,noreferrer');
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDeleteArticle() {
|
async handleDeleteArticle() {
|
||||||
@@ -128,6 +147,7 @@ export default class ArticleSettingsTab extends Component {
|
|||||||
: isStueckliste
|
: isStueckliste
|
||||||
? 'Ein Stücklisten-Artikel kann nicht vereint werden.'
|
? 'Ein Stücklisten-Artikel kann nicht vereint werden.'
|
||||||
: null;
|
: null;
|
||||||
|
const googleQuery = buildGoogleSearchQuery(articleData);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@@ -169,6 +189,18 @@ export default class ArticleSettingsTab extends Component {
|
|||||||
Artikel vereinen
|
Artikel vereinen
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
color="primary"
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<GoogleIcon />}
|
||||||
|
disabled={busy || !googleQuery}
|
||||||
|
onClick={this.handleOpenGoogle}
|
||||||
|
title={googleQuery ? `Google-Suche: ${googleQuery}` : 'Google'}
|
||||||
|
>
|
||||||
|
Google
|
||||||
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<ArticleSearch
|
<ArticleSearch
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class CarrierItem extends PureComponent {
|
|||||||
async updateCarrier(item, patch) {
|
async updateCarrier(item, patch) {
|
||||||
try {
|
try {
|
||||||
await carrierAutomat.actions.update(item.id, patch);
|
await carrierAutomat.actions.update(item.id, patch);
|
||||||
enqueueSnackbar('Lieferart gespeichert.', { variant: 'success' });
|
enqueueSnackbar('Versandart gespeichert.', { variant: 'success' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({ draft: item.name ?? '' });
|
this.setState({ draft: item.name ?? '' });
|
||||||
notifyError(err);
|
notifyError(err);
|
||||||
@@ -93,11 +93,11 @@ class CarrierItem extends PureComponent {
|
|||||||
handleDelete = () => {
|
handleDelete = () => {
|
||||||
const item = this.props.carrier ?? this.props.item;
|
const item = this.props.carrier ?? this.props.item;
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
if (!window.confirm(`Lieferart „${item.name}“ wirklich löschen?`)) return;
|
if (!window.confirm(`Versandart „${item.name}“ wirklich löschen?`)) return;
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
await carrierAutomat.actions.remove(item.id);
|
await carrierAutomat.actions.remove(item.id);
|
||||||
enqueueSnackbar('Lieferart gelöscht.', { variant: 'success' });
|
enqueueSnackbar('Versandart gelöscht.', { variant: 'success' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifyError(err);
|
notifyError(err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,11 +62,10 @@ class ManifestForm extends PureComponent {
|
|||||||
|
|
||||||
const initialDate =
|
const initialDate =
|
||||||
initialDoc?.date ||
|
initialDoc?.date ||
|
||||||
(props.mode === 'create' && props.kind === 'quote' ? todayInputValue() : '');
|
(props.mode === 'create' ? todayInputValue() : '');
|
||||||
|
|
||||||
if (
|
if (
|
||||||
props.mode === 'create' &&
|
props.mode === 'create' &&
|
||||||
props.kind === 'quote' &&
|
|
||||||
this.draftAutomat &&
|
this.draftAutomat &&
|
||||||
!this.draftAutomat.state.date
|
!this.draftAutomat.state.date
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -118,14 +118,14 @@ class AddPackageDialog extends PureComponent {
|
|||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mb: 3 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mb: 3 }}>
|
||||||
<TextField
|
<TextField
|
||||||
select
|
select
|
||||||
label="Lieferart (Carrier)"
|
label="Versandart (Carrier)"
|
||||||
size="small"
|
size="small"
|
||||||
value={carrierId}
|
value={carrierId}
|
||||||
onChange={(e) => this.setState({ carrierId: e.target.value })}
|
onChange={(e) => this.setState({ carrierId: e.target.value })}
|
||||||
error={carriers.length === 0}
|
error={carriers.length === 0}
|
||||||
helperText={
|
helperText={
|
||||||
carriers.length === 0
|
carriers.length === 0
|
||||||
? 'Keine Lieferarten eingerichtet (unter Auftrag → Lieferarten anlegen)'
|
? 'Keine Versandarten eingerichtet (unter Auftrag → Versandarten anlegen)'
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -229,11 +229,11 @@ export const SUB_NAV = {
|
|||||||
help: 'Zahlungseingänge erfassen und Auftragszahlungen überwachen',
|
help: 'Zahlungseingänge erfassen und Auftragszahlungen überwachen',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
segment: 'lieferarten',
|
segment: 'versandarten',
|
||||||
label: 'Lieferarten',
|
label: 'Versandarten',
|
||||||
title: 'Lieferarten',
|
title: 'Versandarten',
|
||||||
icon: LocalShippingOutlinedIcon,
|
icon: LocalShippingOutlinedIcon,
|
||||||
help: 'Versanddienstleister und Lieferarten (Carriers) verwalten',
|
help: 'Versanddienstleister und Versandarten (Carriers) verwalten',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'/kunde': [
|
'/kunde': [
|
||||||
|
|||||||
@@ -202,8 +202,11 @@ class ResultRow extends Component {
|
|||||||
height: '20px',
|
height: '20px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(row.brand?.name || row.brandName) && (
|
{(row.brand?.name || row.brandName) ? (
|
||||||
<>
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.5, flexShrink: 0 }}
|
||||||
|
>
|
||||||
<BrandLabel
|
<BrandLabel
|
||||||
brand={row.brand || { name: row.brandName, thumbHash: row.brandThumbHash }}
|
brand={row.brand || { name: row.brandName, thumbHash: row.brandThumbHash }}
|
||||||
name={row.brand?.name || row.brandName}
|
name={row.brand?.name || row.brandName}
|
||||||
@@ -215,13 +218,18 @@ class ResultRow extends Component {
|
|||||||
noWrap: true,
|
noWrap: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{' · '}
|
<span>·</span>
|
||||||
</>
|
</Box>
|
||||||
)}
|
) : null}
|
||||||
|
<Box component="span" sx={{ flexShrink: 0 }}>
|
||||||
{row.sku}
|
{row.sku}
|
||||||
{' · '}
|
</Box>
|
||||||
|
<Box component="span" sx={{ flexShrink: 0 }}>
|
||||||
|
·
|
||||||
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
component="span"
|
component="span"
|
||||||
|
sx={{ flexShrink: 0 }}
|
||||||
title={
|
title={
|
||||||
row.warehouses?.length > 0
|
row.warehouses?.length > 0
|
||||||
? row.warehouses.map((w) => `${w.name || 'Lager'}: ${formatStock(w.stock)}`).join(' · ')
|
? row.warehouses.map((w) => `${w.name || 'Lager'}: ${formatStock(w.stock)}`).join(' · ')
|
||||||
@@ -230,11 +238,14 @@ class ResultRow extends Component {
|
|||||||
>
|
>
|
||||||
Bestand {formatStock(row.stock)}
|
Bestand {formatStock(row.stock)}
|
||||||
</Box>
|
</Box>
|
||||||
{' · '}
|
{row.categories ? (
|
||||||
Verfügbar {formatStock(row.available)}
|
<Box
|
||||||
{row.categories
|
component="span"
|
||||||
? ` · ${formatCategoryList(row.categories, categoryFacets ?? {})}`
|
sx={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
|
||||||
: ''}
|
>
|
||||||
|
{` · ${formatCategoryList(row.categories, categoryFacets ?? {})}`}
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
slotProps={{ primary: { fontWeight: 500, noWrap: true } }}
|
slotProps={{ primary: { fontWeight: 500, noWrap: true } }}
|
||||||
@@ -1028,7 +1039,7 @@ class ArtikelSearchPage extends Component {
|
|||||||
<PanelTile minHeight={0} sx={{ pb: 0 }}>
|
<PanelTile minHeight={0} sx={{ pb: 0 }}>
|
||||||
<FillSpace
|
<FillSpace
|
||||||
ref={this.fillSpaceRef}
|
ref={this.fillSpaceRef}
|
||||||
aboveRef={[this.filterHeaderRef]}
|
aboveRef={this.filterHeaderRef}
|
||||||
items={rows}
|
items={rows}
|
||||||
renderItem={ResultRow}
|
renderItem={ResultRow}
|
||||||
reserveBottom={32}
|
reserveBottom={32}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import carrierAutomat from '../../automats/carrierAutomat';
|
|||||||
import { CarrierItem } from '../../components/carrier';
|
import { CarrierItem } from '../../components/carrier';
|
||||||
import { GREEN_PANEL } from '../../colors';
|
import { GREEN_PANEL } from '../../colors';
|
||||||
|
|
||||||
class LieferartenSearchField extends PureComponent {
|
class VersandartenSearchField extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const initial = carrierAutomat.state.filter || '';
|
const initial = carrierAutomat.state.filter || '';
|
||||||
@@ -53,7 +53,7 @@ class LieferartenSearchField extends PureComponent {
|
|||||||
hasPackages: true,
|
hasPackages: true,
|
||||||
isReverse: false,
|
isReverse: false,
|
||||||
});
|
});
|
||||||
enqueueSnackbar(`Lieferart „${row.name || name}“ angelegt.`, { variant: 'success' });
|
enqueueSnackbar(`Versandart „${row.name || name}“ angelegt.`, { variant: 'success' });
|
||||||
this.setState({ input: '', submitting: false });
|
this.setState({ input: '', submitting: false });
|
||||||
carrierAutomat.actions.filter('');
|
carrierAutomat.actions.filter('');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -89,7 +89,7 @@ class LieferartenSearchField extends PureComponent {
|
|||||||
size="small"
|
size="small"
|
||||||
search
|
search
|
||||||
clear
|
clear
|
||||||
placeholder="Lieferart suchen oder neu anlegen..."
|
placeholder="Versandart suchen oder neu anlegen..."
|
||||||
value={input}
|
value={input}
|
||||||
disabled={submitting}
|
disabled={submitting}
|
||||||
onChange={(next) => this.onInputChange(next)}
|
onChange={(next) => this.onInputChange(next)}
|
||||||
@@ -114,7 +114,7 @@ class LieferartenSearchField extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuftragLieferartenPage extends PureComponent {
|
class AuftragVersandartenPage extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const initialCarriers =
|
const initialCarriers =
|
||||||
@@ -240,7 +240,7 @@ class AuftragLieferartenPage extends PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LieferartenSearchField inputRef={this.searchRef} />
|
<VersandartenSearchField inputRef={this.searchRef} />
|
||||||
<PanelTileGrid>
|
<PanelTileGrid>
|
||||||
<PanelTile wide={2} minHeight={0} color={GREEN_PANEL}>
|
<PanelTile wide={2} minHeight={0} color={GREEN_PANEL}>
|
||||||
<List dense disablePadding ref={this.listRef} sx={{ width: '100%' }}>
|
<List dense disablePadding ref={this.listRef} sx={{ width: '100%' }}>
|
||||||
@@ -248,7 +248,7 @@ class AuftragLieferartenPage extends PureComponent {
|
|||||||
<Typography variant="body2" color="text.secondary" sx={{ py: 1 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ py: 1 }}>
|
||||||
{trimmedFilter
|
{trimmedFilter
|
||||||
? `Keine Treffer für „${trimmedFilter}“.`
|
? `Keine Treffer für „${trimmedFilter}“.`
|
||||||
: 'Noch keine Lieferarten angelegt.'}
|
: 'Noch keine Versandarten angelegt.'}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
carriers.map((c) => (
|
carriers.map((c) => (
|
||||||
@@ -266,5 +266,5 @@ class AuftragLieferartenPage extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { CarrierItem, LieferartenSearchField };
|
export { CarrierItem, VersandartenSearchField };
|
||||||
export default withNavigate(AuftragLieferartenPage);
|
export default withNavigate(AuftragVersandartenPage);
|
||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
buildManifestBody,
|
buildManifestBody,
|
||||||
restoreAttachmentsFromApi,
|
restoreAttachmentsFromApi,
|
||||||
dateInputValue,
|
dateInputValue,
|
||||||
|
todayInputValue,
|
||||||
emptyLine,
|
emptyLine,
|
||||||
lineFromApi,
|
lineFromApi,
|
||||||
manifestConfig,
|
manifestConfig,
|
||||||
@@ -107,8 +108,13 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
super(props);
|
super(props);
|
||||||
this.cfg = manifestConfig('delivery');
|
this.cfg = manifestConfig('delivery');
|
||||||
this.automat = getDeliveryEditorAutomat(props.docId);
|
this.automat = getDeliveryEditorAutomat(props.docId);
|
||||||
|
const initialDate = this.automat.state.date || (props.docId == null ? todayInputValue() : null);
|
||||||
|
if (props.docId == null && !this.automat.state.date) {
|
||||||
|
this.automat.actions.setDate(initialDate);
|
||||||
|
}
|
||||||
this.state = {
|
this.state = {
|
||||||
...this.automat.state,
|
...this.automat.state,
|
||||||
|
date: initialDate,
|
||||||
status: props.docId != null ? 'loading' : 'loadingParties',
|
status: props.docId != null ? 'loading' : 'loadingParties',
|
||||||
error: null,
|
error: null,
|
||||||
partiesLoading: true,
|
partiesLoading: true,
|
||||||
@@ -199,6 +205,9 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
if (this.props.docId != null) {
|
if (this.props.docId != null) {
|
||||||
void this.loadEdit();
|
void this.loadEdit();
|
||||||
} else {
|
} else {
|
||||||
|
if (!this.automat.state.date) {
|
||||||
|
this.automat.actions.setDate(todayInputValue());
|
||||||
|
}
|
||||||
void this.loadPartiesOnly();
|
void this.loadPartiesOnly();
|
||||||
if (this.props.initialDelivery) {
|
if (this.props.initialDelivery) {
|
||||||
this.automat.actions.setDelivery(this.props.initialDelivery);
|
this.automat.actions.setDelivery(this.props.initialDelivery);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
netFromGross,
|
netFromGross,
|
||||||
parsePriceInput,
|
parsePriceInput,
|
||||||
} from './price';
|
} from './price';
|
||||||
|
import brandAutomat from '../automats/brandAutomat';
|
||||||
|
|
||||||
export function sortedPhotos(article) {
|
export function sortedPhotos(article) {
|
||||||
if (!Array.isArray(article?.photos)) return [];
|
if (!Array.isArray(article?.photos)) return [];
|
||||||
@@ -150,6 +151,51 @@ export function articleEans(article) {
|
|||||||
return article.eans.filter(Boolean);
|
return article.eans.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getArticleEan(article) {
|
||||||
|
if (!article) return '';
|
||||||
|
const eans = articleEans(article);
|
||||||
|
if (eans.length > 0) {
|
||||||
|
const first = eans[0];
|
||||||
|
return String(typeof first === 'object' && first ? (first.ean ?? '') : first ?? '').trim();
|
||||||
|
}
|
||||||
|
if (article.ean) {
|
||||||
|
return String(typeof article.ean === 'object' && article.ean ? (article.ean.ean ?? '') : article.ean).trim();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getArticleBrand(article) {
|
||||||
|
if (!article) return '';
|
||||||
|
if (typeof article.brand === 'object' && article.brand?.name) {
|
||||||
|
return String(article.brand.name).trim();
|
||||||
|
}
|
||||||
|
if (typeof article.brand === 'string' && article.brand.trim()) {
|
||||||
|
return article.brand.trim();
|
||||||
|
}
|
||||||
|
if (article.brandName) {
|
||||||
|
return String(article.brandName).trim();
|
||||||
|
}
|
||||||
|
if (article.brandId != null) {
|
||||||
|
const brands = brandAutomat?.state?.brands || [];
|
||||||
|
const b = brands.find((x) => x.id === article.brandId);
|
||||||
|
if (b?.name) return String(b.name).trim();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getArticleName(article) {
|
||||||
|
if (!article) return '';
|
||||||
|
return String(article.name ?? article.sku ?? '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildGoogleSearchQuery(article) {
|
||||||
|
if (!article) return '';
|
||||||
|
const ean = getArticleEan(article);
|
||||||
|
const brand = getArticleBrand(article);
|
||||||
|
const name = getArticleName(article);
|
||||||
|
return [ean, brand, name].filter(Boolean).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
export function formatPhotoSizeKb(fullSize) {
|
export function formatPhotoSizeKb(fullSize) {
|
||||||
return `${Math.round(fullSize / 1024)} KB`;
|
return `${Math.round(fullSize / 1024)} KB`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user