This commit is contained in:
seb
2026-09-21 07:43:15 +02:00
parent 1b6e3ec7e2
commit 98080f0514
4 changed files with 123 additions and 1 deletions

View File

@@ -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;

View File

@@ -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>
);
}

View File

@@ -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}
/>
);
})}

View File

@@ -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}
/>
</>
)}