u
This commit is contained in:
@@ -8,7 +8,7 @@ import ArticlePriceTiersDialog from '../ArticlePriceTiersDialog';
|
|||||||
import ArticleUnitEditorDialog from '../ArticleUnitEditorDialog';
|
import ArticleUnitEditorDialog from '../ArticleUnitEditorDialog';
|
||||||
import unitAutomat from '../../../automats/unitAutomat';
|
import unitAutomat from '../../../automats/unitAutomat';
|
||||||
import companyAutomat from '../../../automats/companyAutomat';
|
import companyAutomat from '../../../automats/companyAutomat';
|
||||||
import artikelAutomat from '../../../automats/artikelAutomat';
|
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||||
import { requireDefined, requireObject } from '../../../utils/apiShape';
|
import { requireDefined, requireObject } from '../../../utils/apiShape';
|
||||||
import {
|
import {
|
||||||
buildPosPricesPutBody,
|
buildPosPricesPutBody,
|
||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
} from '../../../utils/articleDetail';
|
} from '../../../utils/articleDetail';
|
||||||
import {
|
import {
|
||||||
formatPriceAmount,
|
formatPriceAmount,
|
||||||
|
getArticleGross,
|
||||||
netFromGross,
|
netFromGross,
|
||||||
parsePriceInput,
|
parsePriceInput,
|
||||||
} from '../../../utils/price';
|
} from '../../../utils/price';
|
||||||
@@ -33,7 +34,6 @@ import TierPricePills from './TierPricePills';
|
|||||||
import { PriceUnitRow, PriceUnitCorner } from './PriceUnitRow';
|
import { PriceUnitRow, PriceUnitCorner } from './PriceUnitRow';
|
||||||
import {
|
import {
|
||||||
BLUE_PANEL,
|
BLUE_PANEL,
|
||||||
BORDER,
|
|
||||||
} from '../../../colors';
|
} from '../../../colors';
|
||||||
|
|
||||||
const SECOND_ROW_MIN_HEIGHT = 220;
|
const SECOND_ROW_MIN_HEIGHT = 220;
|
||||||
@@ -387,6 +387,20 @@ export default class ArticleViewPrice extends PureComponent {
|
|||||||
this.unsubCompany = companyAutomat.subscribe(this, (state) => ({
|
this.unsubCompany = companyAutomat.subscribe(this, (state) => ({
|
||||||
company: state.current ?? null,
|
company: state.current ?? null,
|
||||||
}));
|
}));
|
||||||
|
this.unsubArtikel = artikelAutomat.subscribe(this, () => ({}));
|
||||||
|
if (this.props.article?.isParent) {
|
||||||
|
this.ensureChildren(this.props.article.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (
|
||||||
|
this.props.article?.isParent &&
|
||||||
|
(prevProps.article?.sku !== this.props.article?.sku ||
|
||||||
|
prevProps.article?.children !== this.props.article?.children)
|
||||||
|
) {
|
||||||
|
this.ensureChildren(this.props.article.children);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@@ -395,6 +409,17 @@ export default class ArticleViewPrice extends PureComponent {
|
|||||||
this.unsubUnits = null;
|
this.unsubUnits = null;
|
||||||
this.unsubCompany?.();
|
this.unsubCompany?.();
|
||||||
this.unsubCompany = null;
|
this.unsubCompany = null;
|
||||||
|
this.unsubArtikel?.();
|
||||||
|
this.unsubArtikel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureChildren(children) {
|
||||||
|
if (!Array.isArray(children)) return;
|
||||||
|
for (const child of children) {
|
||||||
|
if (child?.sku && !getArticle(child.sku)) {
|
||||||
|
void artikelAutomat.actions.loadArticle(child.sku).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSave = async (draft) => {
|
handleSave = async (draft) => {
|
||||||
@@ -481,11 +506,33 @@ export default class ArticleViewPrice extends PureComponent {
|
|||||||
const { article: articleData } = this.props;
|
const { article: articleData } = this.props;
|
||||||
|
|
||||||
if (articleData?.isParent) {
|
if (articleData?.isParent) {
|
||||||
|
const { company } = this.state;
|
||||||
|
const defaultTaxRate = company?.taxrate ?? 19;
|
||||||
|
const children = Array.isArray(articleData.children) ? articleData.children : [];
|
||||||
|
const prices = [];
|
||||||
|
for (const child of children) {
|
||||||
|
const fullChild = getArticle(child.sku) || child;
|
||||||
|
const gross = getArticleGross(fullChild, defaultTaxRate);
|
||||||
|
if (gross != null && Number.isFinite(gross)) {
|
||||||
|
prices.push(gross);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let priceText = 'Kein Preis';
|
||||||
|
if (prices.length > 0) {
|
||||||
|
const min = Math.min(...prices);
|
||||||
|
const max = Math.max(...prices);
|
||||||
|
priceText =
|
||||||
|
min === max
|
||||||
|
? `€ ${formatPriceAmount(min)}`
|
||||||
|
: `€ ${formatPriceAmount(min)} bis € ${formatPriceAmount(max)}`;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelTile
|
<PanelTile
|
||||||
wide={1}
|
wide={1}
|
||||||
minHeight={SECOND_ROW_MIN_HEIGHT}
|
minHeight={SECOND_ROW_MIN_HEIGHT}
|
||||||
color={BORDER}
|
color={BLUE_PANEL}
|
||||||
contentAlignItems="flex-start"
|
contentAlignItems="flex-start"
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', width: '100%' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', width: '100%' }}>
|
||||||
@@ -493,32 +540,25 @@ export default class ArticleViewPrice extends PureComponent {
|
|||||||
variant="caption"
|
variant="caption"
|
||||||
color="text.secondary"
|
color="text.secondary"
|
||||||
sx={{
|
sx={{
|
||||||
textTransform: 'uppercase',
|
|
||||||
letterSpacing: 0.6,
|
letterSpacing: 0.6,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
display: 'block',
|
display: 'block',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Preis
|
Preis auf Variantenebene pflegen
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ my: 0.75 }}>
|
<Box sx={{ my: 0.75 }}>
|
||||||
<Typography
|
<Typography
|
||||||
component="div"
|
component="div"
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: '1.25rem',
|
fontSize: '1.5rem',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
lineHeight: 1.2,
|
lineHeight: 1.2,
|
||||||
color: 'text.secondary',
|
letterSpacing: '-0.02em',
|
||||||
|
color: 'text.primary',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Kein Preis
|
{priceText}
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
color="text.secondary"
|
|
||||||
sx={{ display: 'block', mt: 0.25 }}
|
|
||||||
>
|
|
||||||
Preise werden auf Variantenebene gepflegt
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import Tooltip from '@mui/material/Tooltip';
|
|||||||
import { PanelTile } from 'dashgui/panels';
|
import { PanelTile } from 'dashgui/panels';
|
||||||
import { formatStock } from '../searchFormat';
|
import { formatStock } from '../searchFormat';
|
||||||
import ArticleStockAdjustDialog from '../ArticleStockAdjustDialog';
|
import ArticleStockAdjustDialog from '../ArticleStockAdjustDialog';
|
||||||
|
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||||
import {
|
import {
|
||||||
BORDER,
|
|
||||||
GREEN_PANEL,
|
GREEN_PANEL,
|
||||||
PETROL_PANEL,
|
PETROL_PANEL,
|
||||||
SORT_GHOST_BG,
|
SORT_GHOST_BG,
|
||||||
@@ -28,6 +28,36 @@ export default class ArticleViewStock extends PureComponent {
|
|||||||
this.handleCloseAdjust = this.handleCloseAdjust.bind(this);
|
this.handleCloseAdjust = this.handleCloseAdjust.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.unsubArtikel = artikelAutomat.subscribe(this, () => ({}));
|
||||||
|
if (this.props.article?.isParent) {
|
||||||
|
this.ensureChildren(this.props.article.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (
|
||||||
|
this.props.article?.isParent &&
|
||||||
|
(prevProps.article?.sku !== this.props.article?.sku ||
|
||||||
|
prevProps.article?.children !== this.props.article?.children)
|
||||||
|
) {
|
||||||
|
this.ensureChildren(this.props.article.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.unsubArtikel?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureChildren(children) {
|
||||||
|
if (!Array.isArray(children)) return;
|
||||||
|
for (const child of children) {
|
||||||
|
if (child?.sku && !getArticle(child.sku)) {
|
||||||
|
void artikelAutomat.actions.loadArticle(child.sku).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleOpenAdjust() {
|
handleOpenAdjust() {
|
||||||
this.setState({ adjustOpen: true });
|
this.setState({ adjustOpen: true });
|
||||||
}
|
}
|
||||||
@@ -54,6 +84,22 @@ export default class ArticleViewStock extends PureComponent {
|
|||||||
const warehouses = rawWarehouses.filter((wh) => (Number(wh.stock) || 0) > 0);
|
const warehouses = rawWarehouses.filter((wh) => (Number(wh.stock) || 0) > 0);
|
||||||
const hasWarehouses = warehouses.length > 0;
|
const hasWarehouses = warehouses.length > 0;
|
||||||
|
|
||||||
|
let totalVariantsStock = 0;
|
||||||
|
if (isParent) {
|
||||||
|
const children = Array.isArray(articleData?.children) ? articleData.children : [];
|
||||||
|
for (const child of children) {
|
||||||
|
const fullChild = getArticle(child.sku) || child;
|
||||||
|
if (Array.isArray(fullChild.warehouses) && fullChild.warehouses.length > 0) {
|
||||||
|
totalVariantsStock += fullChild.warehouses.reduce(
|
||||||
|
(sum, wh) => sum + (Number(wh.stock) || 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
} else if (fullChild.stock != null) {
|
||||||
|
totalVariantsStock += Number(fullChild.stock) || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const totalStock = rawWarehouses.length > 0
|
const totalStock = rawWarehouses.length > 0
|
||||||
? rawWarehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0)
|
? rawWarehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0)
|
||||||
: articleData?.stock != null
|
: articleData?.stock != null
|
||||||
@@ -74,7 +120,7 @@ export default class ArticleViewStock extends PureComponent {
|
|||||||
<PanelTile
|
<PanelTile
|
||||||
wide={1}
|
wide={1}
|
||||||
minHeight={SECOND_ROW_MIN_HEIGHT}
|
minHeight={SECOND_ROW_MIN_HEIGHT}
|
||||||
color={isParent ? BORDER : isStueckliste ? PETROL_PANEL : GREEN_PANEL}
|
color={isStueckliste ? PETROL_PANEL : GREEN_PANEL}
|
||||||
action={
|
action={
|
||||||
isParent || isStueckliste
|
isParent || isStueckliste
|
||||||
? null
|
? null
|
||||||
@@ -99,13 +145,13 @@ export default class ArticleViewStock extends PureComponent {
|
|||||||
variant="caption"
|
variant="caption"
|
||||||
color="text.secondary"
|
color="text.secondary"
|
||||||
sx={{
|
sx={{
|
||||||
textTransform: 'uppercase',
|
...(isParent ? {} : { textTransform: 'uppercase' }),
|
||||||
letterSpacing: 0.6,
|
letterSpacing: 0.6,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
display: 'block',
|
display: 'block',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Bestand
|
{isParent ? 'Bestand auf Variantenebene pflegen' : 'Bestand'}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{isParent ? (
|
{isParent ? (
|
||||||
@@ -116,17 +162,10 @@ export default class ArticleViewStock extends PureComponent {
|
|||||||
fontSize: '1.25rem',
|
fontSize: '1.25rem',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
lineHeight: 1.2,
|
lineHeight: 1.2,
|
||||||
color: 'text.secondary',
|
color: 'text.primary',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Kein Bestand
|
Alle Varianten zusammen: {formatStock(totalVariantsStock)}
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
color="text.secondary"
|
|
||||||
sx={{ display: 'block', mt: 0.25 }}
|
|
||||||
>
|
|
||||||
Bestand wird auf Variantenebene geführt
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
) : isStueckliste ? (
|
) : isStueckliste ? (
|
||||||
|
|||||||
@@ -319,27 +319,6 @@ export default class BrandImageSearchDialog extends PureComponent {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* User helper tip */}
|
|
||||||
{images.length > 0 && !error && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 1,
|
|
||||||
mb: 1.5,
|
|
||||||
px: 1,
|
|
||||||
py: 0.5,
|
|
||||||
borderRadius: 1,
|
|
||||||
bgcolor: 'action.hover',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AutoFixHighIcon color="primary" sx={{ fontSize: 17 }} />
|
|
||||||
<Typography variant="caption" color="text.secondary">
|
|
||||||
Klicken Sie auf ein Logo: Es wird heruntergeladen, automatisch freigestellt (Weiß- & Transparenz-Ränder entfernt), in AVIF konvertiert und als Markenbild hinterlegt.
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Progress / Status banner */}
|
{/* Progress / Status banner */}
|
||||||
{status === 'loading' && <LinearProgress sx={{ mb: 2, borderRadius: 1 }} />}
|
{status === 'loading' && <LinearProgress sx={{ mb: 2, borderRadius: 1 }} />}
|
||||||
|
|
||||||
|
|||||||
@@ -16,52 +16,6 @@ body {
|
|||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
|
||||||
--df-zoom: 1;
|
|
||||||
--df-vh: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ────────────────────────────────────────────────────────────────────────────
|
|
||||||
* RESPONSIVE VIEWPORT ZOOM
|
|
||||||
*
|
|
||||||
* Automatically scales the UI down on narrower viewports:
|
|
||||||
* - < 1700px: 80% zoom
|
|
||||||
* - < 1400px: 65% zoom
|
|
||||||
* - < 1000px: 50% zoom
|
|
||||||
*
|
|
||||||
* `--df-vh` scales inversely with zoom so that elements with full-screen
|
|
||||||
* height (like AppSidebar and full-height page wrappers) always reach the
|
|
||||||
* true bottom of the viewport instead of shrinking.
|
|
||||||
* ──────────────────────────────────────────────────────────────────────────── */
|
|
||||||
@media (max-width: 1699.98px) {
|
|
||||||
:root {
|
|
||||||
--df-zoom: 0.8;
|
|
||||||
--df-vh: calc(100vh / 0.8);
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
zoom: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1399.98px) {
|
|
||||||
:root {
|
|
||||||
--df-zoom: 0.65;
|
|
||||||
--df-vh: calc(100vh / 0.65);
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
zoom: 0.65;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 999.98px) {
|
|
||||||
:root {
|
|
||||||
--df-zoom: 0.5;
|
|
||||||
--df-vh: calc(100vh / 0.5);
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
zoom: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ────────────────────────────────────────────────────────────────────────────
|
/* ────────────────────────────────────────────────────────────────────────────
|
||||||
* SYSTEM-WIDE SPELLCHECK OFF
|
* SYSTEM-WIDE SPELLCHECK OFF
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ createRoot(document.getElementById('root')).render(
|
|||||||
<AuthBootstrap>
|
<AuthBootstrap>
|
||||||
{/* Flex row so the dev inspector docks beside the app instead of
|
{/* Flex row so the dev inspector docks beside the app instead of
|
||||||
overlaying it; the app content shrinks while the panel is open. */}
|
overlaying it; the app content shrinks while the panel is open. */}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', minHeight: 'var(--df-vh, 100vh)' }}>
|
<Box sx={{ display: 'flex', alignItems: 'flex-start', minHeight: '100vh' }}>
|
||||||
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
|
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
|
||||||
<App />
|
<App />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user