Compare commits
4 Commits
d729423249
...
bea427f3df
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bea427f3df | ||
|
|
bd16df64a1 | ||
|
|
6df833b362 | ||
|
|
e63abfe2c0 |
@@ -67,7 +67,7 @@ export function createManifestDraftAutomat({ kind, name }) {
|
|||||||
|
|
||||||
/** @param {Array<object>} lines */
|
/** @param {Array<object>} lines */
|
||||||
setLines(lines) {
|
setLines(lines) {
|
||||||
const list = Array.isArray(lines) && lines.length > 0 ? lines : [emptyLine()];
|
const list = Array.isArray(lines) ? lines : [];
|
||||||
automat.setState({ lines: list });
|
automat.setState({ lines: list });
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -100,11 +100,11 @@ export function createManifestDraftAutomat({ kind, name }) {
|
|||||||
removeLine(key) {
|
removeLine(key) {
|
||||||
const current = automat.state.lines || [];
|
const current = automat.state.lines || [];
|
||||||
const next = current.filter((l) => l.key !== key);
|
const next = current.filter((l) => l.key !== key);
|
||||||
automat.setState({ lines: next.length > 0 ? next : [emptyLine()] });
|
automat.setState({ lines: next });
|
||||||
},
|
},
|
||||||
|
|
||||||
removeAllLines() {
|
removeAllLines() {
|
||||||
automat.setState({ lines: [emptyLine()] });
|
automat.setState({ lines: [] });
|
||||||
},
|
},
|
||||||
|
|
||||||
/** @param {Array<object>} attachments */
|
/** @param {Array<object>} attachments */
|
||||||
@@ -423,7 +423,7 @@ export function createManifestEditAutomat({ kind, name }) {
|
|||||||
const partyId = doc.partyId ?? null;
|
const partyId = doc.partyId ?? null;
|
||||||
const number = doc.number ?? '';
|
const number = doc.number ?? '';
|
||||||
const externalNumber = doc.externalNumber != null ? String(doc.externalNumber) : '';
|
const externalNumber = doc.externalNumber != null ? String(doc.externalNumber) : '';
|
||||||
const lines = Array.isArray(doc.lines) && doc.lines.length > 0 ? doc.lines : [emptyLine()];
|
const lines = Array.isArray(doc.lines) ? doc.lines : [];
|
||||||
const attachments = Array.isArray(doc.attachments) ? doc.attachments : [];
|
const attachments = Array.isArray(doc.attachments) ? doc.attachments : [];
|
||||||
|
|
||||||
const baseline = {
|
const baseline = {
|
||||||
@@ -508,7 +508,7 @@ export function createManifestEditAutomat({ kind, name }) {
|
|||||||
const partyId = savedDoc.partyId ?? null;
|
const partyId = savedDoc.partyId ?? null;
|
||||||
const number = savedDoc.number ?? automat.state.number;
|
const number = savedDoc.number ?? automat.state.number;
|
||||||
const externalNumber = savedDoc.externalNumber != null ? String(savedDoc.externalNumber) : '';
|
const externalNumber = savedDoc.externalNumber != null ? String(savedDoc.externalNumber) : '';
|
||||||
const lines = Array.isArray(savedDoc.lines) && savedDoc.lines.length > 0 ? savedDoc.lines : [emptyLine()];
|
const lines = Array.isArray(savedDoc.lines) ? savedDoc.lines : [];
|
||||||
const attachments = Array.isArray(savedDoc.attachments) ? savedDoc.attachments : automat.state.attachments;
|
const attachments = Array.isArray(savedDoc.attachments) ? savedDoc.attachments : automat.state.attachments;
|
||||||
|
|
||||||
const baseline = {
|
const baseline = {
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export class PriceUnitCorner extends Component {
|
|||||||
size="small"
|
size="small"
|
||||||
checked={Boolean(hasUnit)}
|
checked={Boolean(hasUnit)}
|
||||||
onChange={(event) => onToggleHasUnit(event.target.checked)}
|
onChange={(event) => onToggleHasUnit(event.target.checked)}
|
||||||
inputProps={{ 'aria-label': 'Einheit' }}
|
slotProps={{ input: { 'aria-label': 'Einheit' } }}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import supplierAutomat from '../../automats/supplierAutomat';
|
|||||||
import orderAutomat from '../../automats/orderAutomat';
|
import orderAutomat from '../../automats/orderAutomat';
|
||||||
import companyAutomat, { selectCompanyTaxrate } from '../../automats/companyAutomat';
|
import companyAutomat, { selectCompanyTaxrate } from '../../automats/companyAutomat';
|
||||||
import { netFromGross, grossFromNet, getArticleGross } from '../../utils/price';
|
import { netFromGross, grossFromNet, getArticleGross } from '../../utils/price';
|
||||||
|
import { resolveSalesPrice } from '../../utils/articleDetail';
|
||||||
import {
|
import {
|
||||||
buildManifestBody,
|
buildManifestBody,
|
||||||
restoreAttachmentsFromApi,
|
restoreAttachmentsFromApi,
|
||||||
@@ -81,7 +82,7 @@ class ManifestForm extends PureComponent {
|
|||||||
partyId: initialDoc?.partyId ?? null,
|
partyId: initialDoc?.partyId ?? null,
|
||||||
number: initialDoc?.number || '',
|
number: initialDoc?.number || '',
|
||||||
externalNumber: initialDoc?.externalNumber || '',
|
externalNumber: initialDoc?.externalNumber || '',
|
||||||
lines: initialDoc?.lines?.length > 0 ? initialDoc.lines : [emptyLine()],
|
lines: Array.isArray(initialDoc?.lines) ? initialDoc.lines : (props.mode === 'create' ? [emptyLine()] : []),
|
||||||
attachments: initialDoc?.attachments || [],
|
attachments: initialDoc?.attachments || [],
|
||||||
shipments: initialEdit?.shipments || [],
|
shipments: initialEdit?.shipments || [],
|
||||||
payments: initialEdit?.payments || [],
|
payments: initialEdit?.payments || [],
|
||||||
@@ -119,6 +120,8 @@ class ManifestForm extends PureComponent {
|
|||||||
this.handleExternalNumberChange.bind(this);
|
this.handleExternalNumberChange.bind(this);
|
||||||
this.handleLineChange = this.handleLineChange.bind(this);
|
this.handleLineChange = this.handleLineChange.bind(this);
|
||||||
this.handleSkuBlur = this.handleSkuBlur.bind(this);
|
this.handleSkuBlur = this.handleSkuBlur.bind(this);
|
||||||
|
this.getCustomerGroupId = this.getCustomerGroupId.bind(this);
|
||||||
|
this.getOrFetchArticle = this.getOrFetchArticle.bind(this);
|
||||||
this.handleAddLine = this.handleAddLine.bind(this);
|
this.handleAddLine = this.handleAddLine.bind(this);
|
||||||
this.handleAddArticleLine = this.handleAddArticleLine.bind(this);
|
this.handleAddArticleLine = this.handleAddArticleLine.bind(this);
|
||||||
this.handleOpenArticleSearchForLine = this.handleOpenArticleSearchForLine.bind(this);
|
this.handleOpenArticleSearchForLine = this.handleOpenArticleSearchForLine.bind(this);
|
||||||
@@ -194,7 +197,7 @@ class ManifestForm extends PureComponent {
|
|||||||
date: state.date || '',
|
date: state.date || '',
|
||||||
partyId: state.partyId,
|
partyId: state.partyId,
|
||||||
externalNumber: state.externalNumber || '',
|
externalNumber: state.externalNumber || '',
|
||||||
lines: state.lines?.length > 0 ? state.lines : [emptyLine()],
|
lines: state.lines || [],
|
||||||
attachments: state.attachments || [],
|
attachments: state.attachments || [],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -206,7 +209,7 @@ class ManifestForm extends PureComponent {
|
|||||||
date: state.date || '',
|
date: state.date || '',
|
||||||
partyId: state.partyId,
|
partyId: state.partyId,
|
||||||
externalNumber: state.externalNumber || '',
|
externalNumber: state.externalNumber || '',
|
||||||
lines: state.lines?.length > 0 ? state.lines : [emptyLine()],
|
lines: state.lines || [],
|
||||||
attachments: state.attachments || [],
|
attachments: state.attachments || [],
|
||||||
shipments: state.shipments || [],
|
shipments: state.shipments || [],
|
||||||
payments: state.payments || [],
|
payments: state.payments || [],
|
||||||
@@ -368,8 +371,7 @@ class ManifestForm extends PureComponent {
|
|||||||
requireObject(docRes[cfg.docField], cfg.docField);
|
requireObject(docRes[cfg.docField], cfg.docField);
|
||||||
const doc = docRes[cfg.docField];
|
const doc = docRes[cfg.docField];
|
||||||
const apiLines = requireArray(doc.lines, `${cfg.docField}.lines`);
|
const apiLines = requireArray(doc.lines, `${cfg.docField}.lines`);
|
||||||
const lines =
|
const lines = apiLines.map(lineFromApi);
|
||||||
apiLines.length > 0 ? apiLines.map(lineFromApi) : [emptyLine()];
|
|
||||||
|
|
||||||
// Restore attachments if available
|
// Restore attachments if available
|
||||||
const apiFiles = doc.files ?? [];
|
const apiFiles = doc.files ?? [];
|
||||||
@@ -453,7 +455,7 @@ class ManifestForm extends PureComponent {
|
|||||||
partyId: state.partyId,
|
partyId: state.partyId,
|
||||||
number: state.number || '',
|
number: state.number || '',
|
||||||
externalNumber: state.externalNumber || '',
|
externalNumber: state.externalNumber || '',
|
||||||
lines: state.lines?.length > 0 ? state.lines : [emptyLine()],
|
lines: state.lines || [],
|
||||||
attachments: state.attachments || [],
|
attachments: state.attachments || [],
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
dirtyFields: state.dirtyFields,
|
dirtyFields: state.dirtyFields,
|
||||||
@@ -574,48 +576,51 @@ class ManifestForm extends PureComponent {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCustomerGroupId() {
|
||||||
|
const { partyId, parties } = this.state;
|
||||||
|
if (!partyId || !Array.isArray(parties)) return null;
|
||||||
|
const entity = parties.find((p) => String(p.id) === String(partyId));
|
||||||
|
return entity?.customergroupId ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrFetchArticle(articleOrSku) {
|
||||||
|
const sku = typeof articleOrSku === 'string' ? articleOrSku : articleOrSku?.sku;
|
||||||
|
const trimmed = String(sku ?? '').trim();
|
||||||
|
if (!trimmed) return typeof articleOrSku === 'object' ? articleOrSku : null;
|
||||||
|
let art = getArticle(trimmed);
|
||||||
|
if (!art) {
|
||||||
|
try {
|
||||||
|
art = await artikelAutomat.actions.loadArticle(trimmed);
|
||||||
|
} catch {
|
||||||
|
// fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (art) return art;
|
||||||
|
return typeof articleOrSku === 'object' ? articleOrSku : null;
|
||||||
|
}
|
||||||
|
|
||||||
async handleSkuBlur(key, sku) {
|
async handleSkuBlur(key, sku) {
|
||||||
const trimmed = String(sku ?? '').trim();
|
const trimmed = String(sku ?? '').trim();
|
||||||
if (!trimmed) return;
|
if (!trimmed) return;
|
||||||
try {
|
try {
|
||||||
const art = getArticle(trimmed) || (await artikelAutomat.actions.loadArticle(trimmed));
|
const art = await this.getOrFetchArticle(trimmed);
|
||||||
if (art && !this._cancelled) {
|
if (art && !this._cancelled) {
|
||||||
|
const normTax = selectCompanyTaxrate() != null ? Number(selectCompanyTaxrate()) : 19;
|
||||||
|
const customerGroupId = this.getCustomerGroupId();
|
||||||
|
|
||||||
this.setState((prev) => ({
|
this.setState((prev) => ({
|
||||||
lines: prev.lines.map((l) => {
|
lines: prev.lines.map((l) => {
|
||||||
if (l.key !== key) return l;
|
if (l.key !== key) return l;
|
||||||
const netVal =
|
|
||||||
art.price != null
|
|
||||||
? String(art.price)
|
|
||||||
: art.vknetto != null
|
|
||||||
? String(art.vknetto)
|
|
||||||
: art.net != null
|
|
||||||
? String(art.net)
|
|
||||||
: '';
|
|
||||||
const taxVal =
|
|
||||||
art.taxrate != null
|
|
||||||
? String(art.taxrate)
|
|
||||||
: art.tax != null
|
|
||||||
? String(art.tax)
|
|
||||||
: '';
|
|
||||||
const netNum = parseNum(netVal);
|
|
||||||
const taxNum = parseNum(taxVal);
|
|
||||||
const rebateNum = parseNum(l.rebate) ?? 0;
|
const rebateNum = parseNum(l.rebate) ?? 0;
|
||||||
const artGross = getArticleGross(art, taxNum ?? 0);
|
const { net, gross, taxRate } = resolveSalesPrice(art, {
|
||||||
let grossVal = '';
|
customerGroupId,
|
||||||
if (artGross != null && rebateNum === 0) {
|
defaultTaxRate: normTax,
|
||||||
grossVal = String(artGross);
|
rebate: rebateNum,
|
||||||
} else if (netNum != null) {
|
});
|
||||||
grossVal = String(grossFromNet(netNum, taxNum ?? 0, rebateNum));
|
const netVal = net != null ? String(net) : '';
|
||||||
} else if (artGross != null) {
|
const grossVal = gross != null ? String(gross) : '';
|
||||||
grossVal = String(Math.round((artGross * (1 - rebateNum / 100) + Number.EPSILON) * 100) / 100);
|
const finalTax = taxRate != null ? String(taxRate) : l.tax || String(normTax);
|
||||||
}
|
|
||||||
const normTax = selectCompanyTaxrate() != null ? String(selectCompanyTaxrate()) : '19';
|
|
||||||
const finalTax =
|
|
||||||
art.taxrate != null
|
|
||||||
? String(art.taxrate)
|
|
||||||
: art.tax != null
|
|
||||||
? String(art.tax)
|
|
||||||
: l.tax || normTax;
|
|
||||||
return {
|
return {
|
||||||
...l,
|
...l,
|
||||||
amount: l.amount || '1',
|
amount: l.amount || '1',
|
||||||
@@ -624,9 +629,9 @@ class ManifestForm extends PureComponent {
|
|||||||
tax: finalTax,
|
tax: finalTax,
|
||||||
taxrate: finalTax,
|
taxrate: finalTax,
|
||||||
rebate: l.rebate || '0',
|
rebate: l.rebate || '0',
|
||||||
net: l.net || netVal,
|
net: netVal || l.net,
|
||||||
netprice: l.netprice || netVal,
|
netprice: netVal || l.netprice,
|
||||||
gross: l.gross || grossVal,
|
gross: grossVal || l.gross,
|
||||||
unit: art.unit || l.unit || 'Stück',
|
unit: art.unit || l.unit || 'Stück',
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@@ -641,39 +646,27 @@ class ManifestForm extends PureComponent {
|
|||||||
this.setState((prev) => ({ lines: [...prev.lines, emptyLine()] }));
|
this.setState((prev) => ({ lines: [...prev.lines, emptyLine()] }));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddArticleLine(article) {
|
async handleAddArticleLine(article) {
|
||||||
if (!article) return;
|
if (!article) return;
|
||||||
const normTax = selectCompanyTaxrate() != null ? String(selectCompanyTaxrate()) : '19';
|
const fullArticle = (await this.getOrFetchArticle(article)) || article;
|
||||||
const netVal =
|
const normTax = selectCompanyTaxrate() != null ? Number(selectCompanyTaxrate()) : 19;
|
||||||
article.price != null
|
const customerGroupId = this.getCustomerGroupId();
|
||||||
? String(article.price)
|
|
||||||
: article.vknetto != null
|
const { net, gross, taxRate } = resolveSalesPrice(fullArticle, {
|
||||||
? String(article.vknetto)
|
customerGroupId,
|
||||||
: article.net != null
|
defaultTaxRate: normTax,
|
||||||
? String(article.net)
|
rebate: 0,
|
||||||
: '';
|
});
|
||||||
const taxVal =
|
|
||||||
article.taxrate != null
|
const netVal = net != null ? String(net) : '';
|
||||||
? String(article.taxrate)
|
const grossVal = gross != null ? String(gross) : '';
|
||||||
: article.tax != null
|
const taxVal = taxRate != null ? String(taxRate) : String(normTax);
|
||||||
? String(article.tax)
|
|
||||||
: normTax;
|
|
||||||
const netNum = parseNum(netVal);
|
|
||||||
const taxNum = parseNum(taxVal);
|
|
||||||
const artGross = getArticleGross(article, taxNum ?? 0);
|
|
||||||
let grossVal = '';
|
|
||||||
if (artGross != null) {
|
|
||||||
grossVal = String(artGross);
|
|
||||||
} else if (netNum != null) {
|
|
||||||
const g = Math.round((netNum * (1 + (taxNum ?? 0) / 100) + Number.EPSILON) * 100) / 100;
|
|
||||||
grossVal = String(g);
|
|
||||||
}
|
|
||||||
|
|
||||||
const newLine = {
|
const newLine = {
|
||||||
key: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
key: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||||
amount: '1',
|
amount: '1',
|
||||||
sku: article.sku || '',
|
sku: fullArticle.sku || article.sku || '',
|
||||||
name: article.name || '',
|
name: fullArticle.name || article.name || '',
|
||||||
tax: taxVal,
|
tax: taxVal,
|
||||||
taxrate: taxVal,
|
taxrate: taxVal,
|
||||||
net: netVal,
|
net: netVal,
|
||||||
@@ -681,7 +674,7 @@ class ManifestForm extends PureComponent {
|
|||||||
gross: grossVal,
|
gross: grossVal,
|
||||||
rebate: '0',
|
rebate: '0',
|
||||||
pu: '1',
|
pu: '1',
|
||||||
unit: article.unit || 'Stück',
|
unit: fullArticle.unit || article.unit || 'Stück',
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setState((prev) => {
|
this.setState((prev) => {
|
||||||
@@ -706,43 +699,31 @@ class ManifestForm extends PureComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAssignArticleForLine(article, targetLine) {
|
async handleAssignArticleForLine(article, targetLine) {
|
||||||
if (!article) return;
|
if (!article) return;
|
||||||
if (!targetLine) {
|
if (!targetLine) {
|
||||||
this.handleAddArticleLine(article);
|
await this.handleAddArticleLine(article);
|
||||||
this.setState({
|
this.setState({
|
||||||
articleSearchDialogOpen: false,
|
articleSearchDialogOpen: false,
|
||||||
articleSearchLine: null,
|
articleSearchLine: null,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const normTax = selectCompanyTaxrate() != null ? String(selectCompanyTaxrate()) : '19';
|
|
||||||
const netVal =
|
const fullArticle = (await this.getOrFetchArticle(article)) || article;
|
||||||
article.price != null
|
const normTax = selectCompanyTaxrate() != null ? Number(selectCompanyTaxrate()) : 19;
|
||||||
? String(article.price)
|
const customerGroupId = this.getCustomerGroupId();
|
||||||
: article.vknetto != null
|
|
||||||
? String(article.vknetto)
|
|
||||||
: article.net != null
|
|
||||||
? String(article.net)
|
|
||||||
: '';
|
|
||||||
const taxVal =
|
|
||||||
article.taxrate != null
|
|
||||||
? String(article.taxrate)
|
|
||||||
: article.tax != null
|
|
||||||
? String(article.tax)
|
|
||||||
: normTax;
|
|
||||||
const netNum = parseNum(netVal);
|
|
||||||
const taxNum = parseNum(taxVal);
|
|
||||||
const rebateNum = parseNum(targetLine.rebate) ?? 0;
|
const rebateNum = parseNum(targetLine.rebate) ?? 0;
|
||||||
const artGross = getArticleGross(article, taxNum ?? 0);
|
|
||||||
let grossVal = '';
|
const { net, gross, taxRate } = resolveSalesPrice(fullArticle, {
|
||||||
if (artGross != null && rebateNum === 0) {
|
customerGroupId,
|
||||||
grossVal = String(artGross);
|
defaultTaxRate: normTax,
|
||||||
} else if (netNum != null) {
|
rebate: rebateNum,
|
||||||
grossVal = String(grossFromNet(netNum, taxNum ?? 0, rebateNum));
|
});
|
||||||
} else if (artGross != null) {
|
|
||||||
grossVal = String(Math.round((artGross * (1 - rebateNum / 100) + Number.EPSILON) * 100) / 100);
|
const netVal = net != null ? String(net) : '';
|
||||||
}
|
const grossVal = gross != null ? String(gross) : '';
|
||||||
|
const taxVal = taxRate != null ? String(taxRate) : String(normTax);
|
||||||
|
|
||||||
this.setState((prev) => ({
|
this.setState((prev) => ({
|
||||||
lines: prev.lines.map((l) => {
|
lines: prev.lines.map((l) => {
|
||||||
@@ -750,15 +731,15 @@ class ManifestForm extends PureComponent {
|
|||||||
return {
|
return {
|
||||||
...l,
|
...l,
|
||||||
amount: l.amount || '1',
|
amount: l.amount || '1',
|
||||||
sku: article.sku || l.sku,
|
sku: fullArticle.sku || article.sku || l.sku,
|
||||||
name: article.name || l.name,
|
name: fullArticle.name || article.name || l.name,
|
||||||
tax: taxVal || l.tax || normTax,
|
tax: taxVal || l.tax || String(normTax),
|
||||||
taxrate: taxVal || l.taxrate || normTax,
|
taxrate: taxVal || l.taxrate || String(normTax),
|
||||||
rebate: l.rebate || '0',
|
rebate: l.rebate || '0',
|
||||||
net: netVal || l.net,
|
net: netVal || l.net,
|
||||||
netprice: netVal || l.netprice,
|
netprice: netVal || l.netprice,
|
||||||
gross: grossVal || l.gross,
|
gross: grossVal || l.gross,
|
||||||
unit: article.unit || l.unit || 'Stück',
|
unit: fullArticle.unit || article.unit || l.unit || 'Stück',
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
articleSearchDialogOpen: false,
|
articleSearchDialogOpen: false,
|
||||||
@@ -805,7 +786,7 @@ class ManifestForm extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const next = prev.lines.filter((line) => line.key !== key);
|
const next = prev.lines.filter((line) => line.key !== key);
|
||||||
return { lines: next.length > 0 ? next : [emptyLine()], isDirty: true };
|
return { lines: next, isDirty: true };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -841,10 +822,10 @@ class ManifestForm extends PureComponent {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
enqueueSnackbar('Nicht ausgelieferte Positionen wurden entfernt.', { variant: 'info' });
|
enqueueSnackbar('Nicht ausgelieferte Positionen wurden entfernt.', { variant: 'info' });
|
||||||
return { lines: remaining.length > 0 ? remaining : [emptyLine()], isDirty: true };
|
return { lines: remaining, isDirty: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { lines: [emptyLine()], isDirty: true };
|
return { lines: [], isDirty: true };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1115,7 +1096,7 @@ class ManifestForm extends PureComponent {
|
|||||||
if (this.state.saving) return false;
|
if (this.state.saving) return false;
|
||||||
if (mode === 'edit' && docId == null) return false;
|
if (mode === 'edit' && docId == null) return false;
|
||||||
|
|
||||||
const requireAtLeastOne = mode === 'create';
|
const isEdit = mode === 'edit';
|
||||||
let body;
|
let body;
|
||||||
try {
|
try {
|
||||||
body = await buildManifestBody(
|
body = await buildManifestBody(
|
||||||
@@ -1130,8 +1111,10 @@ class ManifestForm extends PureComponent {
|
|||||||
{
|
{
|
||||||
kind: cfg.kind,
|
kind: cfg.kind,
|
||||||
partyField: cfg.partyField,
|
partyField: cfg.partyField,
|
||||||
requireAtLeastOne,
|
requireAtLeastOne: true,
|
||||||
externalNumber: cfg.externalNumber,
|
externalNumber: cfg.externalNumber,
|
||||||
|
isUpdate: isEdit,
|
||||||
|
mode,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1169,8 +1152,7 @@ class ManifestForm extends PureComponent {
|
|||||||
number: doc.number ?? '',
|
number: doc.number ?? '',
|
||||||
externalNumber:
|
externalNumber:
|
||||||
doc.externalNumber != null ? String(doc.externalNumber) : '',
|
doc.externalNumber != null ? String(doc.externalNumber) : '',
|
||||||
lines:
|
lines: apiLines.map(lineFromApi),
|
||||||
apiLines.length > 0 ? apiLines.map(lineFromApi) : [emptyLine()],
|
|
||||||
attachments: this.state.attachments,
|
attachments: this.state.attachments,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -584,12 +584,28 @@ class ManifestFormLine extends PureComponent {
|
|||||||
const primaryEan = Array.isArray(eans) ? eans[0] : eans;
|
const primaryEan = Array.isArray(eans) ? eans[0] : eans;
|
||||||
|
|
||||||
const vkPrice = primaryPrice(cachedArticle);
|
const vkPrice = primaryPrice(cachedArticle);
|
||||||
const vkNetto = vkPrice?.net;
|
const vkNetto =
|
||||||
|
vkPrice?.net != null
|
||||||
|
? Number(vkPrice.net)
|
||||||
|
: cachedArticle.net != null
|
||||||
|
? Number(cachedArticle.net)
|
||||||
|
: cachedArticle.price != null
|
||||||
|
? Number(cachedArticle.price)
|
||||||
|
: null;
|
||||||
|
const vkTaxRate =
|
||||||
|
vkPrice?.taxRate ??
|
||||||
|
vkPrice?.taxrate ??
|
||||||
|
cachedArticle.taxrate ??
|
||||||
|
cachedArticle.taxRate ??
|
||||||
|
selectCompanyTaxrate() ??
|
||||||
|
19;
|
||||||
const vkBrutto = vkPrice
|
const vkBrutto = vkPrice
|
||||||
? priceBrutto(vkPrice) ??
|
? priceBrutto(vkPrice) ??
|
||||||
(vkPrice.net != null && vkPrice.taxrate != null
|
(vkNetto != null && vkTaxRate != null
|
||||||
? vkPrice.net * (1 + vkPrice.taxrate / 100)
|
? Math.round((vkNetto * (1 + Number(vkTaxRate) / 100) + Number.EPSILON) * 100) / 100
|
||||||
: null)
|
: null)
|
||||||
|
: vkNetto != null
|
||||||
|
? Math.round((vkNetto * (1 + Number(vkTaxRate) / 100) + Number.EPSILON) * 100) / 100
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const suppliers =
|
const suppliers =
|
||||||
@@ -625,7 +641,7 @@ class ManifestFormLine extends PureComponent {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
const hasPriceInfo =
|
const hasPriceInfo =
|
||||||
vkNetto != null || vkBrutto != null || ekNetto != null || ekBrutto != null || kind === 'quote';
|
vkNetto != null || vkBrutto != null || ekNetto != null || ekBrutto != null || kind === 'quote' || kind === 'order';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -733,7 +749,7 @@ class ManifestFormLine extends PureComponent {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* EK Netto, EK Brutto, VK Netto, VK Brutto */}
|
{/* EK Netto, EK Brutto, VK Netto, VK Brutto */}
|
||||||
{!isOrder && hasPriceInfo && (
|
{hasPriceInfo && (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -784,26 +800,28 @@ class ManifestFormLine extends PureComponent {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* VK Netto */}
|
{/* VK Netto */}
|
||||||
{vkNetto != null && (
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
fontWeight={600}
|
fontWeight={600}
|
||||||
sx={{ color: 'text.secondary', fontSize: '0.72rem' }}
|
sx={{
|
||||||
|
color: vkNetto != null ? 'text.secondary' : 'text.disabled',
|
||||||
|
fontSize: '0.72rem',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
VK Netto: {formatPrice(vkNetto)}
|
VK Netto: {vkNetto != null ? formatPrice(vkNetto) : '—'}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* VK Brutto */}
|
{/* VK Brutto */}
|
||||||
{vkBrutto != null && (
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
fontWeight={600}
|
fontWeight={600}
|
||||||
sx={{ color: 'primary.dark', fontSize: '0.72rem' }}
|
sx={{
|
||||||
|
color: vkBrutto != null ? 'primary.dark' : 'text.disabled',
|
||||||
|
fontSize: '0.72rem',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
VK Brutto: {formatPrice(vkBrutto)}
|
VK Brutto: {vkBrutto != null ? formatPrice(vkBrutto) : '—'}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import Chip from '@mui/material/Chip';
|
|||||||
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
||||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||||
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
|
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
|
||||||
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
|
|
||||||
import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined';
|
import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined';
|
||||||
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
|
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
|
||||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||||
@@ -251,8 +250,6 @@ class ManifestHeader extends PureComponent {
|
|||||||
onRestore,
|
onRestore,
|
||||||
orderCreating,
|
orderCreating,
|
||||||
onCreateOrder,
|
onCreateOrder,
|
||||||
onOpenCreateShipment,
|
|
||||||
deliverDisabled,
|
|
||||||
actionsColor,
|
actionsColor,
|
||||||
partyError,
|
partyError,
|
||||||
docId,
|
docId,
|
||||||
@@ -570,28 +567,6 @@ class ManifestHeader extends PureComponent {
|
|||||||
{orderCreating ? 'Wird erstellt…' : 'Bestellung erstellen'}
|
{orderCreating ? 'Wird erstellt…' : 'Bestellung erstellen'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{kind === 'order' && docId != null && onOpenCreateShipment && (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outlined"
|
|
||||||
color="primary"
|
|
||||||
startIcon={<LocalShippingOutlinedIcon />}
|
|
||||||
disabled={saving || pdfCreating || orderCreating || deliverDisabled}
|
|
||||||
onClick={onOpenCreateShipment}
|
|
||||||
title={deliverDisabled ? 'Alle Positionen bereits ausgeliefert' : 'Lieferschein erstellen'}
|
|
||||||
sx={{
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
height: 40,
|
|
||||||
width: '100%',
|
|
||||||
px: 1.5,
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: '0.875rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Liefern
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{(kind === 'quote' || kind === 'order') && onPdfCreate && docId != null && Boolean(number) && (
|
{(kind === 'quote' || kind === 'order') && onPdfCreate && docId != null && Boolean(number) && (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -114,8 +114,21 @@ class ManifestLines extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.isArray(lines) &&
|
if (!lines || lines.length === 0) {
|
||||||
lines.map((line, index) => {
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={columns.length + 1}
|
||||||
|
align="center"
|
||||||
|
sx={{ py: 3, color: 'text.secondary', fontSize: '0.82rem' }}
|
||||||
|
>
|
||||||
|
Keine Positionen vorhanden
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.map((line, index) => {
|
||||||
const lineKey = line.key ?? index;
|
const lineKey = line.key ?? index;
|
||||||
const rawSku = line.sku != null ? String(line.sku).trim() : '';
|
const rawSku = line.sku != null ? String(line.sku).trim() : '';
|
||||||
const isOnShipment = kind === 'order' && Boolean(rawSku && shippedSkus.has(rawSku.toLowerCase()));
|
const isOnShipment = kind === 'order' && Boolean(rawSku && shippedSkus.has(rawSku.toLowerCase()));
|
||||||
|
|||||||
@@ -156,31 +156,7 @@ export class OrderDocumentsTiles extends PureComponent {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Stack>
|
</Stack>
|
||||||
) : (
|
) : null}
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
p: 1.5,
|
|
||||||
textAlign: 'center',
|
|
||||||
bgcolor: 'rgba(255,255,255,0.6)',
|
|
||||||
borderRadius: 1,
|
|
||||||
border: '1px dashed',
|
|
||||||
borderColor: 'divider',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 1 }}>
|
|
||||||
Noch keine Lieferscheine erstellt.
|
|
||||||
</Typography>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
variant="contained"
|
|
||||||
startIcon={<LocalShippingOutlinedIcon />}
|
|
||||||
onClick={this.props.onOpenCreateShipment || (() => navigate?.('/auftrag/ausliefern'))}
|
|
||||||
sx={{ textTransform: 'none', fontWeight: 600, fontSize: '0.75rem' }}
|
|
||||||
>
|
|
||||||
Lieferschein anlegen
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</PanelTile>
|
</PanelTile>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -298,22 +274,7 @@ export class OrderDocumentsTiles extends PureComponent {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Stack>
|
</Stack>
|
||||||
) : (
|
) : null}
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
p: 1.5,
|
|
||||||
textAlign: 'center',
|
|
||||||
bgcolor: 'rgba(255,255,255,0.6)',
|
|
||||||
borderRadius: 1,
|
|
||||||
border: '1px dashed',
|
|
||||||
borderColor: 'divider',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="caption" color="text.secondary">
|
|
||||||
Noch keine Zahlungen erfasst.
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</PanelTile>
|
</PanelTile>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ class OrderRow extends PureComponent {
|
|||||||
color="success"
|
color="success"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onChange={(e) => onTogglePaid?.(order, e.target.checked)}
|
onChange={(e) => onTogglePaid?.(order, e.target.checked)}
|
||||||
inputProps={{ 'aria-label': `Auftrag ${order.number} bezahlt` }}
|
slotProps={{ input: { 'aria-label': `Auftrag ${order.number} bezahlt` } }}
|
||||||
sx={{ p: 0.5 }}
|
sx={{ p: 0.5 }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -238,7 +238,7 @@ class OrderRow extends PureComponent {
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onChange={(e) => onToggleDelivered?.(order, e.target.checked)}
|
onChange={(e) => onToggleDelivered?.(order, e.target.checked)}
|
||||||
inputProps={{ 'aria-label': `Auftrag ${order.number} geliefert` }}
|
slotProps={{ input: { 'aria-label': `Auftrag ${order.number} geliefert` } }}
|
||||||
sx={{ p: 0.5 }}
|
sx={{ p: 0.5 }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ export default class OrderShipmentDialog extends PureComponent {
|
|||||||
</Typography>
|
</Typography>
|
||||||
{orderNumber ? (
|
{orderNumber ? (
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
Zu Auftrag / Bestellung: <strong>{orderNumber}</strong>
|
Zu: <strong>{orderNumber}</strong>
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -307,8 +307,7 @@ export default class OrderShipmentDialog extends PureComponent {
|
|||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
label="Lieferschein-Nr. / Externe Referenz (optional)"
|
label="Referenz"
|
||||||
placeholder="z.B. LS-2026-0042"
|
|
||||||
value={externalNumber}
|
value={externalNumber}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
onChange={(e) => this.setState({ externalNumber: e.target.value })}
|
onChange={(e) => this.setState({ externalNumber: e.target.value })}
|
||||||
@@ -316,24 +315,6 @@ export default class OrderShipmentDialog extends PureComponent {
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{/* Items selection section */}
|
|
||||||
<Box sx={{ mb: 1, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
||||||
<Typography variant="subtitle2" sx={{ fontWeight: 700 }}>
|
|
||||||
Zu liefernde Artikel ({selectedCount} von {openItems.length} ausgewählt)
|
|
||||||
</Typography>
|
|
||||||
{openItems.length > 0 && (
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
variant="text"
|
|
||||||
disabled={saving}
|
|
||||||
onClick={() => this.handleToggleAll(openItems)}
|
|
||||||
sx={{ textTransform: 'none', fontWeight: 600, fontSize: '0.8rem' }}
|
|
||||||
>
|
|
||||||
{isAllSelected ? 'Auswahl aufheben' : 'Alle offenen auswählen'}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<TableContainer sx={{ border: '1px solid', borderColor: 'divider', borderRadius: 1 }}>
|
<TableContainer sx={{ border: '1px solid', borderColor: 'divider', borderRadius: 1 }}>
|
||||||
<Table size="small">
|
<Table size="small">
|
||||||
<TableHead sx={{ bgcolor: 'action.hover' }}>
|
<TableHead sx={{ bgcolor: 'action.hover' }}>
|
||||||
@@ -430,23 +411,7 @@ export default class OrderShipmentDialog extends PureComponent {
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions sx={{ p: 2, justifyContent: 'space-between' }}>
|
<DialogActions sx={{ p: 2, justifyContent: 'flex-end' }}>
|
||||||
<Button variant="outlined" onClick={onClose} disabled={saving}>
|
|
||||||
Abbrechen
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Stack direction="row" spacing={1.5}>
|
|
||||||
{!allShipped && openItems.length > 0 && (
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
color="secondary"
|
|
||||||
disabled={saving}
|
|
||||||
onClick={this.handleShipAll}
|
|
||||||
sx={{ textTransform: 'none', fontWeight: 600 }}
|
|
||||||
>
|
|
||||||
Alles liefern
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -457,7 +422,6 @@ export default class OrderShipmentDialog extends PureComponent {
|
|||||||
>
|
>
|
||||||
{saving ? 'Wird erstellt…' : 'Lieferschein erstellen'}
|
{saving ? 'Wird erstellt…' : 'Lieferschein erstellen'}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</AppDialog>
|
</AppDialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -209,9 +209,8 @@ class NewShipmentModal extends PureComponent {
|
|||||||
sx={{ width: 180 }}
|
sx={{ width: 180 }}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Externe Referenz / Lieferschein-Nr."
|
label="Referenz"
|
||||||
size="small"
|
size="small"
|
||||||
placeholder="z.B. LS-2026-99"
|
|
||||||
value={externalNumber}
|
value={externalNumber}
|
||||||
onChange={(e) => this.setState({ externalNumber: e.target.value })}
|
onChange={(e) => this.setState({ externalNumber: e.target.value })}
|
||||||
sx={{ flex: 1 }}
|
sx={{ flex: 1 }}
|
||||||
|
|||||||
@@ -454,12 +454,14 @@ export function collectFilesForUpload(attachments = [], { onlyNew = false } = {}
|
|||||||
*/
|
*/
|
||||||
export async function buildManifestBody(
|
export async function buildManifestBody(
|
||||||
form,
|
form,
|
||||||
{ partyField, requireAtLeastOne, externalNumber: useExternalNumber, kind },
|
{ partyField, requireAtLeastOne, externalNumber: useExternalNumber, kind, isUpdate, mode },
|
||||||
) {
|
) {
|
||||||
const date = form.date;
|
const date = form.date;
|
||||||
if (!date) throw new Error('Datum fehlt.');
|
if (!date) throw new Error('Datum fehlt.');
|
||||||
|
|
||||||
const externalNumber = String(form.externalNumber ?? '').trim();
|
const externalNumber = String(form.externalNumber ?? '').trim();
|
||||||
|
const isEdit = isUpdate != null ? Boolean(isUpdate) : (mode === 'edit' || !requireAtLeastOne);
|
||||||
|
const shouldRequireLine = requireAtLeastOne !== false;
|
||||||
|
|
||||||
let body;
|
let body;
|
||||||
if (kind === 'delivery') {
|
if (kind === 'delivery') {
|
||||||
@@ -468,7 +470,7 @@ export async function buildManifestBody(
|
|||||||
body = {
|
body = {
|
||||||
externalNumber: externalNumber || null,
|
externalNumber: externalNumber || null,
|
||||||
date,
|
date,
|
||||||
lines: buildLines(form.lines, { requireAtLeastOne }),
|
lines: buildLines(form.lines, { requireAtLeastOne: shouldRequireLine }),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
let number = String(form.number ?? '').trim();
|
let number = String(form.number ?? '').trim();
|
||||||
@@ -479,7 +481,7 @@ export async function buildManifestBody(
|
|||||||
body = {
|
body = {
|
||||||
number,
|
number,
|
||||||
date,
|
date,
|
||||||
lines: buildLines(form.lines, { requireAtLeastOne }),
|
lines: buildLines(form.lines, { requireAtLeastOne: shouldRequireLine }),
|
||||||
};
|
};
|
||||||
if (useExternalNumber || form.externalNumber != null) {
|
if (useExternalNumber || form.externalNumber != null) {
|
||||||
body.externalNumber = externalNumber || null;
|
body.externalNumber = externalNumber || null;
|
||||||
@@ -490,14 +492,13 @@ export async function buildManifestBody(
|
|||||||
if (!Number.isNaN(pid)) {
|
if (!Number.isNaN(pid)) {
|
||||||
body[partyField] = pid;
|
body[partyField] = pid;
|
||||||
}
|
}
|
||||||
} else if (!requireAtLeastOne) {
|
} else if (isEdit) {
|
||||||
body[partyField] = null;
|
body[partyField] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add files if attachments exist
|
// Add files if attachments exist
|
||||||
if (form.attachments && form.attachments.length > 0) {
|
if (form.attachments && form.attachments.length > 0) {
|
||||||
const isUpdate = !requireAtLeastOne;
|
const filesToUpload = collectFilesForUpload(form.attachments, { onlyNew: isEdit });
|
||||||
const filesToUpload = collectFilesForUpload(form.attachments, { onlyNew: isUpdate });
|
|
||||||
if (filesToUpload.length > 0) {
|
if (filesToUpload.length > 0) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
body.filesMeta = filesToUpload.map((item) => ({
|
body.filesMeta = filesToUpload.map((item) => ({
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
formatPriceAmount,
|
formatPriceAmount,
|
||||||
|
grossFromNet,
|
||||||
netFromGross,
|
netFromGross,
|
||||||
parsePriceInput,
|
parsePriceInput,
|
||||||
} from './price';
|
} from './price';
|
||||||
@@ -156,3 +157,87 @@ export function formatPhotoSizeKb(fullSize) {
|
|||||||
export function formatPhotoDimensions(fullW, fullH) {
|
export function formatPhotoDimensions(fullW, fullH) {
|
||||||
return `${fullW}×${fullH}`;
|
return `${fullW}×${fullH}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the sales price (VK-Preis) and tax rate for an article.
|
||||||
|
* Inspects `article.prices` (matching optional `customerGroupId`, falling back to base price),
|
||||||
|
* top-level `article.net` / `article.price` / `article.vknetto`, and calculates gross with optional rebate.
|
||||||
|
*
|
||||||
|
* @param {object|null} article
|
||||||
|
* @param {object} [options]
|
||||||
|
* @param {number|string|null} [options.customerGroupId]
|
||||||
|
* @param {number|string} [options.defaultTaxRate=19]
|
||||||
|
* @param {number|string} [options.rebate=0]
|
||||||
|
* @returns {{ net: number|null, gross: number|null, taxRate: number|null }}
|
||||||
|
*/
|
||||||
|
export function resolveSalesPrice(article, { customerGroupId, defaultTaxRate = 19, rebate = 0 } = {}) {
|
||||||
|
const normTax = defaultTaxRate != null && Number.isFinite(Number(defaultTaxRate)) ? Number(defaultTaxRate) : 19;
|
||||||
|
if (!article) {
|
||||||
|
return { net: null, gross: null, taxRate: normTax };
|
||||||
|
}
|
||||||
|
|
||||||
|
const r = Number.isFinite(Number(rebate)) ? Number(rebate) : 0;
|
||||||
|
|
||||||
|
let matchedPrice = null;
|
||||||
|
if (Array.isArray(article.prices) && article.prices.length > 0) {
|
||||||
|
if (customerGroupId != null) {
|
||||||
|
matchedPrice =
|
||||||
|
article.prices.find(
|
||||||
|
(p) => String(p.customergroupId) === String(customerGroupId) && (p.minmultiplier === 1 || p.minmultiplier == null)
|
||||||
|
) ||
|
||||||
|
article.prices.find((p) => String(p.customergroupId) === String(customerGroupId));
|
||||||
|
}
|
||||||
|
if (!matchedPrice) {
|
||||||
|
matchedPrice = primaryPrice(article);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let net = null;
|
||||||
|
let taxRate = null;
|
||||||
|
let gross = null;
|
||||||
|
|
||||||
|
if (matchedPrice) {
|
||||||
|
if (matchedPrice.net != null && Number.isFinite(Number(matchedPrice.net))) {
|
||||||
|
net = Number(matchedPrice.net);
|
||||||
|
}
|
||||||
|
const rawTax = matchedPrice.taxRate ?? matchedPrice.tax;
|
||||||
|
if (rawTax != null && Number.isFinite(Number(rawTax))) {
|
||||||
|
taxRate = Number(rawTax);
|
||||||
|
}
|
||||||
|
if (matchedPrice.gross != null && Number.isFinite(Number(matchedPrice.gross))) {
|
||||||
|
gross = Number(matchedPrice.gross);
|
||||||
|
} else if (net != null && taxRate != null) {
|
||||||
|
gross = priceBrutto(matchedPrice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (net == null) {
|
||||||
|
const rawNet = article.price ?? article.net ?? article.vknetto;
|
||||||
|
if (rawNet != null && Number.isFinite(Number(rawNet))) {
|
||||||
|
net = Number(rawNet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taxRate == null) {
|
||||||
|
const rawTax = article.taxRate ?? article.taxrate ?? article.tax;
|
||||||
|
taxRate = rawTax != null && Number.isFinite(Number(rawTax)) ? Number(rawTax) : normTax;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gross == null && article.gross != null && Number.isFinite(Number(article.gross))) {
|
||||||
|
gross = Number(article.gross);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (net == null && gross != null && taxRate != null) {
|
||||||
|
net = netFromGross(gross, taxRate, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (net != null && taxRate != null) {
|
||||||
|
if (r !== 0) {
|
||||||
|
gross = grossFromNet(net, taxRate, r);
|
||||||
|
} else if (gross == null) {
|
||||||
|
gross = Math.round((net * (1 + taxRate / 100) + Number.EPSILON) * 100) / 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { net, gross, taxRate };
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user