u
This commit is contained in:
@@ -87,8 +87,8 @@ class AppSidebar extends PureComponent {
|
|||||||
disablePadding
|
disablePadding
|
||||||
sx={{
|
sx={{
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
display: 'flex',
|
display: { xs: 'flex', lg: 'block' },
|
||||||
flexDirection: { xs: 'row', lg: 'column' },
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
height: { xs: '100%', lg: 'auto' },
|
height: { xs: '100%', lg: 'auto' },
|
||||||
overflowX: { xs: 'auto', lg: 'visible' },
|
overflowX: { xs: 'auto', lg: 'visible' },
|
||||||
@@ -104,6 +104,8 @@ class AppSidebar extends PureComponent {
|
|||||||
<ListItemButton
|
<ListItemButton
|
||||||
key={path}
|
key={path}
|
||||||
type="button"
|
type="button"
|
||||||
|
aria-label={label}
|
||||||
|
title={label}
|
||||||
selected={active}
|
selected={active}
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -131,7 +133,7 @@ class AppSidebar extends PureComponent {
|
|||||||
borderRadius: { xs: 1, lg: 0 },
|
borderRadius: { xs: 1, lg: 0 },
|
||||||
width: { xs: 'auto', lg: '100%' },
|
width: { xs: 'auto', lg: '100%' },
|
||||||
height: { xs: '100%', lg: 'auto' },
|
height: { xs: '100%', lg: 'auto' },
|
||||||
px: { xs: 1.25, sm: 1.5 },
|
px: { xs: active ? 1.25 : 1, sm: active ? 1.5 : 1.25, md: 1.5 },
|
||||||
py: { xs: 0.75, sm: 1, lg: 1.25 },
|
py: { xs: 0.75, sm: 1, lg: 1.25 },
|
||||||
borderLeft: { xs: 'none', lg: '3px solid transparent' },
|
borderLeft: { xs: 'none', lg: '3px solid transparent' },
|
||||||
borderBottom: { xs: '3px solid transparent', lg: 'none' },
|
borderBottom: { xs: '3px solid transparent', lg: 'none' },
|
||||||
@@ -141,7 +143,8 @@ class AppSidebar extends PureComponent {
|
|||||||
'& .MuiListItemIcon-root': {
|
'& .MuiListItemIcon-root': {
|
||||||
color: 'inherit',
|
color: 'inherit',
|
||||||
minWidth: { xs: 'auto', lg: 36 },
|
minWidth: { xs: 'auto', lg: 36 },
|
||||||
mr: { xs: 0.75, lg: 0 },
|
mr: active ? { xs: 0.75, lg: 0 } : { xs: 0, md: 0.75, lg: 0 },
|
||||||
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
bgcolor: theme.nav.itemHover,
|
bgcolor: theme.nav.itemHover,
|
||||||
@@ -172,7 +175,10 @@ class AppSidebar extends PureComponent {
|
|||||||
fontWeight: active ? 600 : 500,
|
fontWeight: active ? 600 : 500,
|
||||||
noWrap: true,
|
noWrap: true,
|
||||||
}}
|
}}
|
||||||
sx={{ m: 0 }}
|
sx={{
|
||||||
|
m: { xs: 0, lg: 'initial' },
|
||||||
|
display: active ? 'block' : { xs: 'none', md: 'block' },
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
);
|
);
|
||||||
@@ -239,25 +245,34 @@ class AppSubNav extends PureComponent {
|
|||||||
<Box
|
<Box
|
||||||
component="nav"
|
component="nav"
|
||||||
aria-label="Unternavigation"
|
aria-label="Unternavigation"
|
||||||
sx={{
|
sx={(theme) => ({
|
||||||
width: { xs: 60, sm: 72, md: SUB_NAV_WIDTH },
|
width: { xs: '100%', sm: 72, md: SUB_NAV_WIDTH },
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: { xs: 'row', sm: 'column' },
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 1.5,
|
gap: { xs: 1, sm: 1.5 },
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: { xs: 52, sm: 56, lg: 0 },
|
top: { xs: 52, sm: 56, lg: 0 },
|
||||||
|
zIndex: { xs: 1090, sm: 'auto' },
|
||||||
alignSelf: 'flex-start',
|
alignSelf: 'flex-start',
|
||||||
height: {
|
height: {
|
||||||
xs: 'calc(var(--df-vh, 100vh) - 52px)',
|
xs: 'auto',
|
||||||
sm: 'calc(var(--df-vh, 100vh) - 56px)',
|
sm: 'calc(var(--df-vh, 100vh) - 56px)',
|
||||||
lg: 'var(--df-vh, 100vh)',
|
lg: 'var(--df-vh, 100vh)',
|
||||||
},
|
},
|
||||||
py: { xs: 2, lg: 4 },
|
py: { xs: 0.75, sm: 2, lg: 4 },
|
||||||
ml: { xs: 1, sm: 2, lg: 3 },
|
px: { xs: 1.5, sm: 0 },
|
||||||
mr: { xs: 1, sm: 1.5, lg: 2 },
|
ml: { xs: 0, sm: 2, lg: 3 },
|
||||||
}}
|
mr: { xs: 0, sm: 1.5, lg: 2 },
|
||||||
|
overflowX: { xs: 'auto', sm: 'visible' },
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
'&::-webkit-scrollbar': { display: 'none' },
|
||||||
|
bgcolor: { xs: theme.nav?.mainBackground || 'background.default', sm: 'transparent' },
|
||||||
|
borderBottom: { xs: '1px solid', sm: 'none' },
|
||||||
|
borderColor: { xs: 'divider', sm: 'transparent' },
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{subNav.map(({ segment, label, title, help, icon: Icon }) => {
|
{subNav.map(({ segment, label, title, help, icon: Icon }) => {
|
||||||
const active = segment === activeSub;
|
const active = segment === activeSub;
|
||||||
@@ -266,6 +281,7 @@ class AppSubNav extends PureComponent {
|
|||||||
<ButtonBase
|
<ButtonBase
|
||||||
key={segment || 'index'}
|
key={segment || 'index'}
|
||||||
type="button"
|
type="button"
|
||||||
|
title={label}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
aria-current={active ? 'page' : undefined}
|
aria-current={active ? 'page' : undefined}
|
||||||
disableRipple
|
disableRipple
|
||||||
@@ -290,15 +306,17 @@ class AppSubNav extends PureComponent {
|
|||||||
navigate(segment ? `${activeNav}/${segment}` : activeNav);
|
navigate(segment ? `${activeNav}/${segment}` : activeNav);
|
||||||
}}
|
}}
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
width: { xs: 60, sm: 72, md: SUB_NAV_WIDTH },
|
width: { xs: 'auto', sm: 72, md: SUB_NAV_WIDTH },
|
||||||
py: 1,
|
py: { xs: 0.5, sm: 1 },
|
||||||
px: 0.5,
|
px: { xs: active ? 1.25 : 0.75, sm: 0.5 },
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: { xs: 'row', sm: 'column' },
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
gap: 0.5,
|
gap: { xs: active ? 0.75 : 0, sm: 0.5 },
|
||||||
|
flexShrink: 0,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
color: active
|
color: active
|
||||||
? theme.nav?.itemActiveAccent || NAV_ITEM_ACTIVE_ACCENT
|
? theme.nav?.itemActiveAccent || NAV_ITEM_ACTIVE_ACCENT
|
||||||
: theme.palette.text.secondary,
|
: theme.palette.text.secondary,
|
||||||
@@ -314,18 +332,19 @@ class AppSubNav extends PureComponent {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<HelpBadge title={title || label} help={help} placement="right">
|
<HelpBadge title={title || label} help={help} placement="right">
|
||||||
<Icon sx={{ fontSize: { xs: 24, sm: 28, md: 32 } }} />
|
<Icon sx={{ fontSize: { xs: 20, sm: 28, md: 32 } }} />
|
||||||
</HelpBadge>
|
</HelpBadge>
|
||||||
{label ? (
|
{label ? (
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
noWrap
|
noWrap
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: { xs: '0.62rem', sm: '0.68rem' },
|
fontSize: { xs: '0.75rem', sm: '0.68rem' },
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
lineHeight: 1.2,
|
lineHeight: 1.2,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
|
display: active ? 'block' : { xs: 'none', sm: 'block' },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
@@ -358,7 +377,7 @@ export default function AppShell() {
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: { xs: 'column', sm: 'row' },
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import artikelAutomat, { getArticle } from '../../automats/artikelAutomat';
|
|||||||
import entityAutomat from '../../automats/entityAutomat';
|
import entityAutomat from '../../automats/entityAutomat';
|
||||||
import supplierAutomat from '../../automats/supplierAutomat';
|
import supplierAutomat from '../../automats/supplierAutomat';
|
||||||
import companyAutomat, { selectCompanyTaxrate } from '../../automats/companyAutomat';
|
import companyAutomat, { selectCompanyTaxrate } from '../../automats/companyAutomat';
|
||||||
import { netFromGross, getArticleGross } from '../../utils/price';
|
import { netFromGross, grossFromNet, getArticleGross } from '../../utils/price';
|
||||||
import {
|
import {
|
||||||
buildManifestBody,
|
buildManifestBody,
|
||||||
restoreAttachmentsFromApi,
|
restoreAttachmentsFromApi,
|
||||||
@@ -421,10 +421,11 @@ class ManifestForm extends PureComponent {
|
|||||||
|
|
||||||
if (field === 'gross') {
|
if (field === 'gross') {
|
||||||
const taxVal = parseNum(line.tax ?? line.taxrate) ?? 0;
|
const taxVal = parseNum(line.tax ?? line.taxrate) ?? 0;
|
||||||
|
const rebateVal = parseNum(line.rebate) ?? 0;
|
||||||
const grossNum = parseNum(value);
|
const grossNum = parseNum(value);
|
||||||
let updatedNet = line.net;
|
let updatedNet = line.net;
|
||||||
if (grossNum != null) {
|
if (grossNum != null) {
|
||||||
const netNum = netFromGross(grossNum, taxVal);
|
const netNum = netFromGross(grossNum, taxVal, rebateVal);
|
||||||
if (netNum != null) {
|
if (netNum != null) {
|
||||||
updatedNet = String(netNum);
|
updatedNet = String(netNum);
|
||||||
}
|
}
|
||||||
@@ -441,11 +442,12 @@ class ManifestForm extends PureComponent {
|
|||||||
|
|
||||||
if (field === 'net' || field === 'netprice') {
|
if (field === 'net' || field === 'netprice') {
|
||||||
const taxVal = parseNum(line.tax ?? line.taxrate) ?? 0;
|
const taxVal = parseNum(line.tax ?? line.taxrate) ?? 0;
|
||||||
|
const rebateVal = parseNum(line.rebate) ?? 0;
|
||||||
const netNum = parseNum(value);
|
const netNum = parseNum(value);
|
||||||
let updatedGross = line.gross;
|
let updatedGross = line.gross;
|
||||||
if (netNum != null) {
|
if (netNum != null) {
|
||||||
const grossNum = Math.round((netNum * (1 + taxVal / 100) + Number.EPSILON) * 100) / 100;
|
const grossNum = grossFromNet(netNum, taxVal, rebateVal);
|
||||||
updatedGross = String(grossNum);
|
updatedGross = grossNum != null ? String(grossNum) : '';
|
||||||
} else if (value === '') {
|
} else if (value === '') {
|
||||||
updatedGross = '';
|
updatedGross = '';
|
||||||
}
|
}
|
||||||
@@ -460,11 +462,12 @@ class ManifestForm extends PureComponent {
|
|||||||
|
|
||||||
if (field === 'tax' || field === 'taxrate') {
|
if (field === 'tax' || field === 'taxrate') {
|
||||||
const taxNum = parseNum(value) ?? 0;
|
const taxNum = parseNum(value) ?? 0;
|
||||||
|
const rebateVal = parseNum(line.rebate) ?? 0;
|
||||||
const netNum = parseNum(line.net ?? line.netprice);
|
const netNum = parseNum(line.net ?? line.netprice);
|
||||||
let updatedGross = line.gross;
|
let updatedGross = line.gross;
|
||||||
if (netNum != null) {
|
if (netNum != null) {
|
||||||
const grossNum = Math.round((netNum * (1 + taxNum / 100) + Number.EPSILON) * 100) / 100;
|
const grossNum = grossFromNet(netNum, taxNum, rebateVal);
|
||||||
updatedGross = String(grossNum);
|
updatedGross = grossNum != null ? String(grossNum) : '';
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...line,
|
...line,
|
||||||
@@ -475,6 +478,22 @@ class ManifestForm extends PureComponent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (field === 'rebate') {
|
||||||
|
const rebateNum = parseNum(value) ?? 0;
|
||||||
|
const taxVal = parseNum(line.tax ?? line.taxrate) ?? 0;
|
||||||
|
const netNum = parseNum(line.net ?? line.netprice);
|
||||||
|
let updatedGross = line.gross;
|
||||||
|
if (netNum != null) {
|
||||||
|
const grossNum = grossFromNet(netNum, taxVal, rebateNum);
|
||||||
|
updatedGross = grossNum != null ? String(grossNum) : '';
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...line,
|
||||||
|
rebate: value,
|
||||||
|
gross: updatedGross,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...line,
|
...line,
|
||||||
[field]: value,
|
[field]: value,
|
||||||
@@ -508,13 +527,15 @@ class ManifestForm extends PureComponent {
|
|||||||
: '';
|
: '';
|
||||||
const netNum = parseNum(netVal);
|
const netNum = parseNum(netVal);
|
||||||
const taxNum = parseNum(taxVal);
|
const taxNum = parseNum(taxVal);
|
||||||
|
const rebateNum = parseNum(l.rebate) ?? 0;
|
||||||
const artGross = getArticleGross(art, taxNum ?? 0);
|
const artGross = getArticleGross(art, taxNum ?? 0);
|
||||||
let grossVal = '';
|
let grossVal = '';
|
||||||
if (artGross != null) {
|
if (artGross != null && rebateNum === 0) {
|
||||||
grossVal = String(artGross);
|
grossVal = String(artGross);
|
||||||
} else if (netNum != null) {
|
} else if (netNum != null) {
|
||||||
const g = Math.round((netNum * (1 + (taxNum ?? 0) / 100) + Number.EPSILON) * 100) / 100;
|
grossVal = String(grossFromNet(netNum, taxNum ?? 0, rebateNum));
|
||||||
grossVal = String(g);
|
} else if (artGross != null) {
|
||||||
|
grossVal = String(Math.round((artGross * (1 - rebateNum / 100) + Number.EPSILON) * 100) / 100);
|
||||||
}
|
}
|
||||||
const normTax = selectCompanyTaxrate() != null ? String(selectCompanyTaxrate()) : '19';
|
const normTax = selectCompanyTaxrate() != null ? String(selectCompanyTaxrate()) : '19';
|
||||||
const finalTax =
|
const finalTax =
|
||||||
@@ -640,13 +661,15 @@ class ManifestForm extends PureComponent {
|
|||||||
: normTax;
|
: normTax;
|
||||||
const netNum = parseNum(netVal);
|
const netNum = parseNum(netVal);
|
||||||
const taxNum = parseNum(taxVal);
|
const taxNum = parseNum(taxVal);
|
||||||
|
const rebateNum = parseNum(targetLine.rebate) ?? 0;
|
||||||
const artGross = getArticleGross(article, taxNum ?? 0);
|
const artGross = getArticleGross(article, taxNum ?? 0);
|
||||||
let grossVal = '';
|
let grossVal = '';
|
||||||
if (artGross != null) {
|
if (artGross != null && rebateNum === 0) {
|
||||||
grossVal = String(artGross);
|
grossVal = String(artGross);
|
||||||
} else if (netNum != null) {
|
} else if (netNum != null) {
|
||||||
const g = Math.round((netNum * (1 + (taxNum ?? 0) / 100) + Number.EPSILON) * 100) / 100;
|
grossVal = String(grossFromNet(netNum, taxNum ?? 0, rebateNum));
|
||||||
grossVal = String(g);
|
} else if (artGross != null) {
|
||||||
|
grossVal = String(Math.round((artGross * (1 - rebateNum / 100) + Number.EPSILON) * 100) / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState((prev) => ({
|
this.setState((prev) => ({
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ import SearchIcon from '@mui/icons-material/Search';
|
|||||||
import LinkOffOutlinedIcon from '@mui/icons-material/LinkOffOutlined';
|
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 } from '../../utils/price';
|
import { formatPrice, grossFromNet } from '../../utils/price';
|
||||||
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';
|
||||||
import { BrandLabel } from '../brand';
|
import { BrandLabel } from '../brand';
|
||||||
|
import { articleSupplierAutomat } from '../../automats/articleDetailAutomat';
|
||||||
|
import { selectCompanyTaxrate } from '../../automats/companyAutomat';
|
||||||
import {
|
import {
|
||||||
BORDER,
|
BORDER,
|
||||||
} from '../../colors';
|
} from '../../colors';
|
||||||
@@ -35,6 +37,7 @@ class ManifestFormLine extends PureComponent {
|
|||||||
this.state = {
|
this.state = {
|
||||||
cachedArticle: sku ? getArticle(sku) : null,
|
cachedArticle: sku ? getArticle(sku) : null,
|
||||||
loadingArticle: false,
|
loadingArticle: false,
|
||||||
|
fallbackSuppliers: [],
|
||||||
};
|
};
|
||||||
this.handleRemove = this.handleRemove.bind(this);
|
this.handleRemove = this.handleRemove.bind(this);
|
||||||
this.handleBlur = this.handleBlur.bind(this);
|
this.handleBlur = this.handleBlur.bind(this);
|
||||||
@@ -64,14 +67,18 @@ class ManifestFormLine extends PureComponent {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.unsubArticle?.();
|
this.unsubArticle?.();
|
||||||
|
this.unsubSupplier?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeArticle(sku) {
|
subscribeArticle(sku) {
|
||||||
this.unsubArticle?.();
|
this.unsubArticle?.();
|
||||||
this.unsubArticle = null;
|
this.unsubArticle = null;
|
||||||
|
this.unsubSupplier?.();
|
||||||
|
this.unsubSupplier = null;
|
||||||
|
|
||||||
if (!sku) {
|
if (!sku) {
|
||||||
if (this.state.cachedArticle || this.state.loadingArticle) {
|
if (this.state.cachedArticle || this.state.loadingArticle || this.state.fallbackSuppliers?.length) {
|
||||||
this.setState({ cachedArticle: null, loadingArticle: false });
|
this.setState({ cachedArticle: null, loadingArticle: false, fallbackSuppliers: [] });
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -99,6 +106,15 @@ class ManifestFormLine extends PureComponent {
|
|||||||
...(art ? { loadingArticle: false } : {}),
|
...(art ? { loadingArticle: false } : {}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fallback: subscribe to articleSupplierAutomat if cachedArticle doesn't have suppliers
|
||||||
|
if (!current?.suppliers?.length) {
|
||||||
|
const supKey = articleSupplierAutomat.key(sku);
|
||||||
|
this.unsubSupplier = supKey.subscribe(this, (item) => ({
|
||||||
|
fallbackSuppliers: Array.isArray(item?.data) ? item.data : [],
|
||||||
|
}));
|
||||||
|
void articleSupplierAutomat.load(sku).catch(() => {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRemove() {
|
handleRemove() {
|
||||||
@@ -235,8 +251,9 @@ class ManifestFormLine extends PureComponent {
|
|||||||
} else if (id === 'gross') {
|
} else if (id === 'gross') {
|
||||||
const netNum = parseNum(line.netprice ?? line.net);
|
const netNum = parseNum(line.netprice ?? line.net);
|
||||||
const taxNum = parseNum(line.taxrate ?? line.tax) ?? 0;
|
const taxNum = parseNum(line.taxrate ?? line.tax) ?? 0;
|
||||||
|
const rebateNum = parseNum(line.rebate) ?? 0;
|
||||||
if (netNum != null) {
|
if (netNum != null) {
|
||||||
val = String(Math.round((netNum * (1 + taxNum / 100) + Number.EPSILON) * 100) / 100);
|
val = String(grossFromNet(netNum, taxNum, rebateNum));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -375,6 +392,41 @@ class ManifestFormLine extends PureComponent {
|
|||||||
: null)
|
: null)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const suppliers =
|
||||||
|
Array.isArray(cachedArticle.suppliers) && cachedArticle.suppliers.length > 0
|
||||||
|
? cachedArticle.suppliers
|
||||||
|
: (this.state.fallbackSuppliers ?? []);
|
||||||
|
|
||||||
|
const topSupplier =
|
||||||
|
suppliers.find((s) => s.sort === 0) ??
|
||||||
|
(suppliers.length > 0
|
||||||
|
? [...suppliers].sort((a, b) => (Number(a.sort) || 0) - (Number(b.sort) || 0))[0]
|
||||||
|
: null);
|
||||||
|
|
||||||
|
const ekNetto =
|
||||||
|
topSupplier?.price?.net != null
|
||||||
|
? Number(topSupplier.price.net)
|
||||||
|
: topSupplier?.netPrice != null
|
||||||
|
? Number(topSupplier.netPrice)
|
||||||
|
: topSupplier?.net != null
|
||||||
|
? Number(topSupplier.net)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const ekTaxRate =
|
||||||
|
topSupplier?.price?.taxRate != null
|
||||||
|
? Number(topSupplier.price.taxRate)
|
||||||
|
: topSupplier?.taxRate != null
|
||||||
|
? Number(topSupplier.taxRate)
|
||||||
|
: parseNum(this.props.line?.taxrate ?? this.props.line?.tax) ?? selectCompanyTaxrate() ?? 19;
|
||||||
|
|
||||||
|
const ekBrutto =
|
||||||
|
ekNetto != null
|
||||||
|
? Math.round((ekNetto * (1 + Number(ekTaxRate) / 100) + Number.EPSILON) * 100) / 100
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const hasPriceInfo =
|
||||||
|
vkNetto != null || vkBrutto != null || ekNetto != null || ekBrutto != null || kind === 'quote';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -480,8 +532,8 @@ class ManifestFormLine extends PureComponent {
|
|||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* VK Netto & VK Brutto: omit end on 2nd line for order */}
|
{/* EK Netto, EK Brutto, VK Netto, VK Brutto */}
|
||||||
{!isOrder && (vkNetto != null || vkBrutto != null) && (
|
{!isOrder && hasPriceInfo && (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -491,6 +543,47 @@ class ManifestFormLine extends PureComponent {
|
|||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* EK Netto */}
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
topSupplier?.supplierName
|
||||||
|
? `Lieferant: ${topSupplier.supplierName}${topSupplier.sku ? ` (Lief.-Art.-Nr.: ${topSupplier.sku})` : ''} (Sort: ${topSupplier.sort ?? 0})`
|
||||||
|
: 'Kein Lieferant mit EK-Preis hinterlegt'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
fontWeight={600}
|
||||||
|
sx={{
|
||||||
|
color: ekNetto != null ? 'text.secondary' : 'text.disabled',
|
||||||
|
fontSize: '0.72rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
EK Netto: {ekNetto != null ? formatPrice(ekNetto) : '—'}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{/* EK Brutto */}
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
topSupplier?.supplierName
|
||||||
|
? `Lieferant: ${topSupplier.supplierName} (inkl. ${ekTaxRate}% MwSt.)`
|
||||||
|
: 'Kein Lieferant mit EK-Preis hinterlegt'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
fontWeight={600}
|
||||||
|
sx={{
|
||||||
|
color: ekBrutto != null ? 'text.secondary' : 'text.disabled',
|
||||||
|
fontSize: '0.72rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
EK Brutto: {ekBrutto != null ? formatPrice(ekBrutto) : '—'}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{/* VK Netto */}
|
||||||
{vkNetto != null && (
|
{vkNetto != null && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
@@ -500,6 +593,8 @@ class ManifestFormLine extends PureComponent {
|
|||||||
VK Netto: {formatPrice(vkNetto)}
|
VK Netto: {formatPrice(vkNetto)}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* VK Brutto */}
|
||||||
{vkBrutto != null && (
|
{vkBrutto != null && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
|
|||||||
@@ -107,12 +107,13 @@ export function lineFromApi(row) {
|
|||||||
const taxVal = row.taxrate != null ? String(row.taxrate) : row.tax != null ? String(row.tax) : '';
|
const taxVal = row.taxrate != null ? String(row.taxrate) : row.tax != null ? String(row.tax) : '';
|
||||||
const netNum = parseNum(netVal);
|
const netNum = parseNum(netVal);
|
||||||
const taxNum = parseNum(taxVal);
|
const taxNum = parseNum(taxVal);
|
||||||
|
const rebateNum = parseNum(row.rebate) ?? 0;
|
||||||
let grossVal = '';
|
let grossVal = '';
|
||||||
if (row.gross != null && row.gross !== '') {
|
if (row.gross != null && row.gross !== '') {
|
||||||
grossVal = String(row.gross);
|
grossVal = String(row.gross);
|
||||||
} else if (netNum != null) {
|
} else if (netNum != null) {
|
||||||
const rate = taxNum ?? 0;
|
const rate = taxNum ?? 0;
|
||||||
const g = Math.round((netNum * (1 + rate / 100) + Number.EPSILON) * 100) / 100;
|
const g = Math.round((netNum * (1 + rate / 100) * (1 - rebateNum / 100) + Number.EPSILON) * 100) / 100;
|
||||||
grossVal = String(g);
|
grossVal = String(g);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -175,10 +176,10 @@ export function buildLines(lines, { requireAtLeastOne }) {
|
|||||||
const tax = parseNum(rawTax);
|
const tax = parseNum(rawTax);
|
||||||
const rawGross = getFirstVal(line.gross);
|
const rawGross = getFirstVal(line.gross);
|
||||||
const gross = parseNum(rawGross);
|
const gross = parseNum(rawGross);
|
||||||
if (net == null && gross != null && tax != null) {
|
|
||||||
net = netFromGross(gross, tax);
|
|
||||||
}
|
|
||||||
const rebate = parseNum(getFirstVal(line.rebate));
|
const rebate = parseNum(getFirstVal(line.rebate));
|
||||||
|
if (net == null && gross != null && tax != null) {
|
||||||
|
net = netFromGross(gross, tax, rebate ?? 0);
|
||||||
|
}
|
||||||
const rawPu = getFirstVal(line.pu, line.vpe);
|
const rawPu = getFirstVal(line.pu, line.vpe);
|
||||||
const pu = parseNum(rawPu);
|
const pu = parseNum(rawPu);
|
||||||
const blank = !sku && !name && net == null && gross == null;
|
const blank = !sku && !name && net == null && gross == null;
|
||||||
|
|||||||
@@ -45,12 +45,26 @@ export function parsePriceInput(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Convert gross (brutto) → net for PUT /prices. Rounded to 4 decimal places. */
|
/** Convert gross (brutto) → net for PUT /prices. Rounded to 4 decimal places. */
|
||||||
export function netFromGross(gross, taxRate) {
|
export function netFromGross(gross, taxRate, rebate = 0) {
|
||||||
if (gross == null || taxRate == null) return null;
|
if (gross == null || taxRate == null) return null;
|
||||||
if (!Number.isFinite(gross) || !Number.isFinite(taxRate) || taxRate < 0) return null;
|
if (!Number.isFinite(gross) || !Number.isFinite(taxRate) || taxRate < 0) return null;
|
||||||
const factor = 1 + taxRate / 100;
|
const taxFactor = 1 + taxRate / 100;
|
||||||
if (factor <= 0) return null;
|
if (taxFactor <= 0) return null;
|
||||||
return Math.round((gross / factor) * 10000) / 10000;
|
const r = Number.isFinite(rebate) ? rebate : 0;
|
||||||
|
const rebateFactor = 1 - r / 100;
|
||||||
|
if (rebateFactor <= 0) return 0;
|
||||||
|
return Math.round((gross / (taxFactor * rebateFactor)) * 10000) / 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert net → gross taking taxRate and optional rebate into account. Rounded to 2 decimal places. */
|
||||||
|
export function grossFromNet(net, taxRate = 0, rebate = 0) {
|
||||||
|
if (net == null) return null;
|
||||||
|
const netNum = Number(net);
|
||||||
|
if (!Number.isFinite(netNum)) return null;
|
||||||
|
const taxNum = Number(taxRate) || 0;
|
||||||
|
const rebateNum = Number(rebate) || 0;
|
||||||
|
const factor = (1 + taxNum / 100) * (1 - rebateNum / 100);
|
||||||
|
return Math.round((netNum * factor + Number.EPSILON) * 100) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user