u
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Automat } from 'react-automat';
|
||||
import { supplier as supplierApi } from '../api';
|
||||
import { article as articleApi, supplier as supplierApi } from '../api';
|
||||
|
||||
/**
|
||||
* @typedef {Object} DeliveryLine
|
||||
@@ -43,6 +43,7 @@ import { supplier as supplierApi } from '../api';
|
||||
* @property {Object<string, SupplierArticle|null>} supplierArticlesByLineKey Matched article per line.
|
||||
* @property {boolean} supplierArticlesLoading Whether any supplier article request is pending.
|
||||
* @property {Object<string, boolean>} loadingLineKeys Pending lookup state per line.
|
||||
* @property {Object<string, boolean>} busySupplierArticleLineKeys Lines with a pending unlink request.
|
||||
* @property {string|null} supplierArticlesError Last supplier article error.
|
||||
* @property {string|null} unmatchedSupplierName Parsed supplier name without a matching entity.
|
||||
*/
|
||||
@@ -130,6 +131,7 @@ export const initialDelivery = {
|
||||
supplierArticlesByLineKey: {},
|
||||
supplierArticlesLoading: false,
|
||||
loadingLineKeys: {},
|
||||
busySupplierArticleLineKeys: {},
|
||||
supplierArticlesError: null,
|
||||
unmatchedSupplierName: null,
|
||||
};
|
||||
@@ -482,6 +484,70 @@ export function createDeliveryEditorAutomat(options = {}) {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the supplier-article assignment behind a matched line by
|
||||
* `DELETE /api/article/{sku}/supplier/{id}`, then re-resolves the line.
|
||||
*
|
||||
* @param {string|number} internalSku Internal article SKU.
|
||||
* @param {string|number} assignmentId Article-supplier assignment ID.
|
||||
* @param {string|number} [lineKey] Editor line key showing the match.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async unlinkSupplierArticle(internalSku, assignmentId, lineKey) {
|
||||
const entityId = automat.state.deliveryEntity ?? automat.state.partyId;
|
||||
const sku = internalSku != null ? String(internalSku).trim() : '';
|
||||
if (!sku || assignmentId == null) {
|
||||
throw new Error('Artikelsku und Zuordnungs-ID sind erforderlich.');
|
||||
}
|
||||
|
||||
if (lineKey != null) {
|
||||
automat.setState({
|
||||
busySupplierArticleLineKeys: {
|
||||
...(automat.state.busySupplierArticleLineKeys || {}),
|
||||
[lineKey]: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await articleApi.deleteSupplier(sku, assignmentId);
|
||||
|
||||
const clearedMap = { ...(automat.state.supplierArticlesMap || {}) };
|
||||
for (const [mapKey, item] of Object.entries(clearedMap)) {
|
||||
if (item != null && String(item.id) === String(assignmentId)) {
|
||||
delete clearedMap[mapKey];
|
||||
}
|
||||
}
|
||||
|
||||
const nextList = (automat.state.supplierArticles || []).filter(
|
||||
(item) => item != null && String(item.id) !== String(assignmentId),
|
||||
);
|
||||
|
||||
const byLineKey = { ...(automat.state.supplierArticlesByLineKey || {}) };
|
||||
if (lineKey != null) byLineKey[lineKey] = null;
|
||||
|
||||
automat.setState({
|
||||
supplierArticles: nextList,
|
||||
supplierArticlesMap: clearedMap,
|
||||
supplierArticlesByLineKey: byLineKey,
|
||||
});
|
||||
|
||||
if (lineKey != null && entityId) {
|
||||
const line = (automat.state.lines || []).find((l) => l.key === lineKey);
|
||||
const rawSku = line?.sku != null ? String(line.sku).trim() : '';
|
||||
if (rawSku) {
|
||||
await automat.actions.fetchSupplierArticleForSku(rawSku, lineKey);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (lineKey != null) {
|
||||
const nextBusy = { ...(automat.state.busySupplierArticleLineKeys || {}) };
|
||||
delete nextBusy[lineKey];
|
||||
automat.setState({ busySupplierArticleLineKeys: nextBusy });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** @param {Object} attachment @returns {void} */
|
||||
addAttachment(attachment) {
|
||||
const id = attachment.id ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
@@ -594,6 +660,7 @@ export const {
|
||||
removeLine,
|
||||
fetchSupplierArticles,
|
||||
fetchSupplierArticleForSku,
|
||||
unlinkSupplierArticle,
|
||||
addAttachment,
|
||||
setAttachments,
|
||||
updateAttachment,
|
||||
@@ -637,5 +704,8 @@ export const selectSupplierArticlesByLineKey = (state = deliveryEditorAutomat.st
|
||||
export const selectSupplierArticlesLoading = (state = deliveryEditorAutomat.state) => state.supplierArticlesLoading;
|
||||
/** @param {DeliveryEditorState} [state] @returns {Object<string, boolean>} */
|
||||
export const selectLoadingLineKeys = (state = deliveryEditorAutomat.state) => state.loadingLineKeys;
|
||||
/** @param {DeliveryEditorState} [state] @returns {Object<string, boolean>} */
|
||||
export const selectBusySupplierArticleLineKeys = (state = deliveryEditorAutomat.state) =>
|
||||
state.busySupplierArticleLineKeys;
|
||||
|
||||
export default deliveryEditorAutomat;
|
||||
|
||||
@@ -8,6 +8,7 @@ import IconButton from '@mui/material/IconButton';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
||||
import HelpOutlineIcon from '@mui/icons-material/HelpOutlineOutlined';
|
||||
@@ -15,6 +16,7 @@ import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined';
|
||||
import CheckCircleOutlinedIcon from '@mui/icons-material/CheckCircleOutlined';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import LinkOffOutlinedIcon from '@mui/icons-material/LinkOffOutlined';
|
||||
import { LINE_COLUMNS } from './manifestConfig';
|
||||
import ArticlePhoto from '../article/ArticlePhoto';
|
||||
import { formatPrice } from '../../utils/price';
|
||||
@@ -27,6 +29,7 @@ class ManifestFormLine extends PureComponent {
|
||||
this.handleBlur = this.handleBlur.bind(this);
|
||||
this.handleCreateArticle = this.handleCreateArticle.bind(this);
|
||||
this.handleSearchArticle = this.handleSearchArticle.bind(this);
|
||||
this.handleRemoveSupplierArticle = this.handleRemoveSupplierArticle.bind(this);
|
||||
}
|
||||
|
||||
handleRemove() {
|
||||
@@ -53,6 +56,13 @@ class ManifestFormLine extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleRemoveSupplierArticle() {
|
||||
const { line, supplierArticle, onRemoveSupplierArticle } = this.props;
|
||||
if (onRemoveSupplierArticle) {
|
||||
onRemoveSupplierArticle(line, supplierArticle);
|
||||
}
|
||||
}
|
||||
|
||||
renderField(column) {
|
||||
const { id, align, type } = column;
|
||||
const { line, saving, onChange } = this.props;
|
||||
@@ -226,6 +236,21 @@ class ManifestFormLine extends PureComponent {
|
||||
{taxRate != null ? ` (${taxRate}% MwSt)` : ''}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* Remove supplier-article connection */}
|
||||
{this.props.onRemoveSupplierArticle && supplierArticle.id != null && (
|
||||
<span style={{ marginLeft: priceNet != null ? 0 : 'auto' }}>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="error"
|
||||
aria-label="Lieferanten-Verknüpfung entfernen"
|
||||
disabled={this.props.saving || this.props.supplierArticleBusy}
|
||||
onClick={this.handleRemoveSupplierArticle}
|
||||
>
|
||||
<LinkOffOutlinedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</span>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ class ManifestLines extends PureComponent {
|
||||
onSkuBlur,
|
||||
onCreateArticle,
|
||||
onSearchArticle,
|
||||
onRemoveSupplierArticle,
|
||||
busySupplierArticleLineKeys = {},
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -75,11 +77,13 @@ class ManifestLines extends PureComponent {
|
||||
saving={saving}
|
||||
supplierArticle={matched}
|
||||
loading={Boolean(isLineLoading)}
|
||||
supplierArticleBusy={Boolean(busySupplierArticleLineKeys[lineKey])}
|
||||
onChange={onLineChange}
|
||||
onRemove={onRemoveLine}
|
||||
onSkuBlur={onSkuBlur}
|
||||
onCreateArticle={onCreateArticle}
|
||||
onSearchArticle={onSearchArticle}
|
||||
onRemoveSupplierArticle={onRemoveSupplierArticle}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -85,6 +85,7 @@ class LieferungNeuPage extends PureComponent {
|
||||
this.handleOpenArticleSearchForLine = this.handleOpenArticleSearchForLine.bind(this);
|
||||
this.handleCloseArticleSearch = this.handleCloseArticleSearch.bind(this);
|
||||
this.handleAssignArticleForLine = this.handleAssignArticleForLine.bind(this);
|
||||
this.handleRemoveSupplierArticle = this.handleRemoveSupplierArticle.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -403,6 +404,26 @@ class LieferungNeuPage extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async handleRemoveSupplierArticle(line, supplierArticle) {
|
||||
const internalSku = supplierArticle?.article?.sku;
|
||||
const assignmentId = supplierArticle?.id;
|
||||
if (!internalSku || assignmentId == null) {
|
||||
enqueueSnackbar('Verknüpfung konnte nicht ermittelt werden.', { variant: 'warning' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.automat.actions.unlinkSupplierArticle(internalSku, assignmentId, line?.key);
|
||||
enqueueSnackbar(
|
||||
`Verknüpfung zu Artikel "${internalSku}" wurde entfernt.`,
|
||||
{ variant: 'success' },
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Lösen der Lieferanten-Verknüpfung:', err);
|
||||
notifyError(err);
|
||||
}
|
||||
}
|
||||
|
||||
handleAddLine() {
|
||||
this.automat.actions.addLineitem();
|
||||
}
|
||||
@@ -1111,6 +1132,8 @@ class LieferungNeuPage extends PureComponent {
|
||||
onSave={() => void this.handleSave()}
|
||||
onCreateArticle={this.handleCreateArticleForLine}
|
||||
onSearchArticle={this.handleOpenArticleSearchForLine}
|
||||
onRemoveSupplierArticle={this.handleRemoveSupplierArticle}
|
||||
busySupplierArticleLineKeys={this.state.busySupplierArticleLineKeys}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user