u
This commit is contained in:
@@ -8,7 +8,7 @@ import ArticlePriceTiersDialog from '../ArticlePriceTiersDialog';
|
||||
import ArticleUnitEditorDialog from '../ArticleUnitEditorDialog';
|
||||
import unitAutomat from '../../../automats/unitAutomat';
|
||||
import companyAutomat from '../../../automats/companyAutomat';
|
||||
import artikelAutomat from '../../../automats/artikelAutomat';
|
||||
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||
import { requireDefined, requireObject } from '../../../utils/apiShape';
|
||||
import {
|
||||
buildPosPricesPutBody,
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from '../../../utils/articleDetail';
|
||||
import {
|
||||
formatPriceAmount,
|
||||
getArticleGross,
|
||||
netFromGross,
|
||||
parsePriceInput,
|
||||
} from '../../../utils/price';
|
||||
@@ -33,7 +34,6 @@ import TierPricePills from './TierPricePills';
|
||||
import { PriceUnitRow, PriceUnitCorner } from './PriceUnitRow';
|
||||
import {
|
||||
BLUE_PANEL,
|
||||
BORDER,
|
||||
} from '../../../colors';
|
||||
|
||||
const SECOND_ROW_MIN_HEIGHT = 220;
|
||||
@@ -387,6 +387,20 @@ export default class ArticleViewPrice extends PureComponent {
|
||||
this.unsubCompany = companyAutomat.subscribe(this, (state) => ({
|
||||
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() {
|
||||
@@ -395,6 +409,17 @@ export default class ArticleViewPrice extends PureComponent {
|
||||
this.unsubUnits = null;
|
||||
this.unsubCompany?.();
|
||||
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) => {
|
||||
@@ -481,11 +506,33 @@ export default class ArticleViewPrice extends PureComponent {
|
||||
const { article: articleData } = this.props;
|
||||
|
||||
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 (
|
||||
<PanelTile
|
||||
wide={1}
|
||||
minHeight={SECOND_ROW_MIN_HEIGHT}
|
||||
color={BORDER}
|
||||
color={BLUE_PANEL}
|
||||
contentAlignItems="flex-start"
|
||||
>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', width: '100%' }}>
|
||||
@@ -493,32 +540,25 @@ export default class ArticleViewPrice extends PureComponent {
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.6,
|
||||
fontWeight: 600,
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
Preis
|
||||
Preis auf Variantenebene pflegen
|
||||
</Typography>
|
||||
<Box sx={{ my: 0.75 }}>
|
||||
<Typography
|
||||
component="div"
|
||||
sx={{
|
||||
fontSize: '1.25rem',
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 700,
|
||||
lineHeight: 1.2,
|
||||
color: 'text.secondary',
|
||||
letterSpacing: '-0.02em',
|
||||
color: 'text.primary',
|
||||
}}
|
||||
>
|
||||
Kein Preis
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{ display: 'block', mt: 0.25 }}
|
||||
>
|
||||
Preise werden auf Variantenebene gepflegt
|
||||
{priceText}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -7,8 +7,8 @@ import Tooltip from '@mui/material/Tooltip';
|
||||
import { PanelTile } from 'dashgui/panels';
|
||||
import { formatStock } from '../searchFormat';
|
||||
import ArticleStockAdjustDialog from '../ArticleStockAdjustDialog';
|
||||
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||
import {
|
||||
BORDER,
|
||||
GREEN_PANEL,
|
||||
PETROL_PANEL,
|
||||
SORT_GHOST_BG,
|
||||
@@ -28,6 +28,36 @@ export default class ArticleViewStock extends PureComponent {
|
||||
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() {
|
||||
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 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
|
||||
? rawWarehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0)
|
||||
: articleData?.stock != null
|
||||
@@ -74,7 +120,7 @@ export default class ArticleViewStock extends PureComponent {
|
||||
<PanelTile
|
||||
wide={1}
|
||||
minHeight={SECOND_ROW_MIN_HEIGHT}
|
||||
color={isParent ? BORDER : isStueckliste ? PETROL_PANEL : GREEN_PANEL}
|
||||
color={isStueckliste ? PETROL_PANEL : GREEN_PANEL}
|
||||
action={
|
||||
isParent || isStueckliste
|
||||
? null
|
||||
@@ -99,13 +145,13 @@ export default class ArticleViewStock extends PureComponent {
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{
|
||||
textTransform: 'uppercase',
|
||||
...(isParent ? {} : { textTransform: 'uppercase' }),
|
||||
letterSpacing: 0.6,
|
||||
fontWeight: 600,
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
Bestand
|
||||
{isParent ? 'Bestand auf Variantenebene pflegen' : 'Bestand'}
|
||||
</Typography>
|
||||
|
||||
{isParent ? (
|
||||
@@ -116,17 +162,10 @@ export default class ArticleViewStock extends PureComponent {
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 700,
|
||||
lineHeight: 1.2,
|
||||
color: 'text.secondary',
|
||||
color: 'text.primary',
|
||||
}}
|
||||
>
|
||||
Kein Bestand
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{ display: 'block', mt: 0.25 }}
|
||||
>
|
||||
Bestand wird auf Variantenebene geführt
|
||||
Alle Varianten zusammen: {formatStock(totalVariantsStock)}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : isStueckliste ? (
|
||||
|
||||
@@ -319,27 +319,6 @@ export default class BrandImageSearchDialog extends PureComponent {
|
||||
</Button>
|
||||
</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 */}
|
||||
{status === 'loading' && <LinearProgress sx={{ mb: 2, borderRadius: 1 }} />}
|
||||
|
||||
|
||||
@@ -16,52 +16,6 @@ body {
|
||||
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
|
||||
|
||||
@@ -53,7 +53,7 @@ createRoot(document.getElementById('root')).render(
|
||||
<AuthBootstrap>
|
||||
{/* Flex row so the dev inspector docks beside the app instead of
|
||||
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 }}>
|
||||
<App />
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user