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