This commit is contained in:
seb
2026-09-19 06:37:42 +02:00
parent 02e9a555d6
commit abe078ff05
14 changed files with 1197 additions and 560 deletions

View File

@@ -1,4 +1,4 @@
import { Automat } from 'automat';
import { Automat } from 'react-automat';
import { supplier as supplierApi } from '../api';
export function createEmptyLineitem(line = {}) {
@@ -34,7 +34,7 @@ export function buildSupplierArticlesMap(articles = [], existingMap = {}) {
map[artSku.toLowerCase()] = item;
}
}
// Map by EANs
// Map by Eans
const eans = item.article?.eans ?? item.article?.articleean ?? [];
for (const ean of eans) {
if (ean != null) {
@@ -77,336 +77,367 @@ export const initialState = {
...initialDelivery,
};
export const deliveryEditorAutomat = new Automat(initialState, {
setId(id) {
deliveryEditorAutomat.setState({ id, deliveryId: id });
},
export function createDeliveryEditorAutomat(options = {}) {
const name = typeof options === 'string' ? options : options?.name ?? null;
if (name) {
const existing = Automat.get(name);
if (existing) return existing;
}
setDeliveryEntities(deliveryEntities) {
deliveryEditorAutomat.setState({ deliveryEntities: deliveryEntities ?? [] });
},
let automat;
const actions = {
setId(id) {
automat.setState({ id, deliveryId: id });
},
setDelivery(delivery = {}) {
const rawLines = delivery.lines ?? delivery.lineitems ?? [];
const lines = Array.isArray(rawLines) ? rawLines : [];
const partyId = delivery.partyId ?? delivery.deliveryEntity ?? null;
const extNum = delivery.externalNumber ?? delivery.external_number ?? null;
const id = delivery.id ?? delivery.deliveryId ?? null;
const notes = Array.isArray(delivery.notes)
? delivery.notes
: delivery.notes ? [delivery.notes] : [];
const rawAttachments = delivery.attachments ?? delivery.files ?? [];
const attachments = Array.isArray(rawAttachments) ? rawAttachments : [];
setDeliveryEntities(deliveryEntities) {
automat.setState({ deliveryEntities: deliveryEntities ?? [] });
},
deliveryEditorAutomat.setState({
...initialDelivery,
...delivery,
id,
deliveryId: id,
partyId,
deliveryEntity: partyId,
externalNumber: extNum,
external_number: extNum,
lines,
lineitems: lines,
notes,
attachments,
files: attachments,
});
setDelivery(delivery = {}) {
const rawLines = delivery.lines ?? delivery.lineitems ?? [];
const lines = Array.isArray(rawLines) ? rawLines : [];
const partyId = delivery.partyId ?? delivery.deliveryEntity ?? null;
const extNum = delivery.externalNumber ?? delivery.external_number ?? null;
const id = delivery.id ?? delivery.deliveryId ?? null;
const notes = Array.isArray(delivery.notes)
? delivery.notes
: delivery.notes ? [delivery.notes] : [];
const rawAttachments = delivery.attachments ?? delivery.files ?? [];
const attachments = Array.isArray(rawAttachments) ? rawAttachments : [];
if (partyId && lines.length > 0) {
deliveryEditorAutomat.actions.fetchSupplierArticles(partyId, lines);
}
},
automat.setState({
...initialDelivery,
...delivery,
id,
deliveryId: id,
partyId,
deliveryEntity: partyId,
externalNumber: extNum,
external_number: extNum,
lines,
lineitems: lines,
notes,
attachments,
files: attachments,
});
setDate(date) {
deliveryEditorAutomat.setState({ date });
},
if (partyId && lines.length > 0) {
automat.actions.fetchSupplierArticles(partyId, lines);
}
},
setDeliveryEntity(entityId) {
const prevId = deliveryEditorAutomat.state.deliveryEntity ?? deliveryEditorAutomat.state.partyId;
const idChanged = prevId !== entityId;
deliveryEditorAutomat.setState({
deliveryEntity: entityId,
partyId: entityId,
...(idChanged
? {
supplierArticles: [],
supplierArticlesMap: {},
supplierArticlesByLineKey: {},
loadingLineKeys: {},
setDate(date) {
automat.setState({ date });
},
setDeliveryEntity(entityId) {
const prevId = automat.state.deliveryEntity ?? automat.state.partyId;
const idChanged = prevId !== entityId;
automat.setState({
deliveryEntity: entityId,
partyId: entityId,
...(idChanged
? {
supplierArticles: [],
supplierArticlesMap: {},
supplierArticlesByLineKey: {},
loadingLineKeys: {},
}
: {}),
});
if (entityId && idChanged) {
automat.actions.fetchSupplierArticles(entityId);
}
},
setPartyId(partyId) {
automat.actions.setDeliveryEntity(partyId);
},
setExternalNumber(externalNumber) {
automat.setState({
externalNumber,
external_number: externalNumber,
});
},
setNumber(number) {
automat.setState({ number });
},
setType(type) {
automat.setState({ type });
},
setLines(lines) {
const cleanLines = lines ?? [];
automat.setState({ lines: cleanLines, lineitems: cleanLines });
},
setLineitems(lines) {
const cleanLines = lines ?? [];
automat.setState({ lines: cleanLines, lineitems: cleanLines });
},
setNotes(notes) {
const cleanNotes = Array.isArray(notes)
? notes
: notes ? [notes] : [];
automat.setState({ notes: cleanNotes });
},
addLineitem(line = {}) {
const item = createEmptyLineitem(line);
const nextLines = [...(automat.state.lines ?? []), item];
automat.setState({ lines: nextLines, lineitems: nextLines });
},
addLine(line = {}) {
automat.actions.addLineitem(line);
},
updateLineitem(payload) {
const { index, key, ...fields } = payload || {};
const currentLines = automat.state.lines ?? [];
let targetIndex = typeof index === 'number' && !Number.isNaN(index) ? index : -1;
if (targetIndex === -1 && key != null) {
targetIndex = currentLines.findIndex((l) => l.key === key);
}
if (targetIndex >= 0 && targetIndex < currentLines.length) {
const nextLines = [...currentLines];
nextLines[targetIndex] = {
...nextLines[targetIndex],
...fields,
};
automat.setState({ lines: nextLines, lineitems: nextLines });
}
},
updateLine(payload) {
automat.actions.updateLineitem(payload);
},
removeLineitem(payload) {
const currentLines = automat.state.lines ?? [];
let index = typeof payload === 'number' && !Number.isNaN(payload) ? payload : -1;
let targetKey = null;
if (index === -1 && payload != null) {
const key = typeof payload === 'string' ? payload : payload?.key;
if (key != null) {
index = currentLines.findIndex((l) => l.key === key);
targetKey = key;
}
}
if (index >= 0 && index < currentLines.length) {
targetKey = targetKey ?? currentLines[index]?.key;
const nextLines = currentLines.filter((_, i) => i !== index);
const byLineKey = { ...(automat.state.supplierArticlesByLineKey || {}) };
if (targetKey != null) delete byLineKey[targetKey];
automat.setState({
lines: nextLines,
lineitems: nextLines,
supplierArticlesByLineKey: byLineKey,
});
}
},
removeLine(payload) {
automat.actions.removeLineitem(payload);
},
async fetchSupplierArticles(targetEntityId, targetLines) {
const entityId = targetEntityId ?? automat.state.deliveryEntity ?? automat.state.partyId;
if (!entityId) {
automat.setState({
supplierArticles: [],
supplierArticlesMap: {},
supplierArticlesByLineKey: {},
supplierArticlesLoading: false,
loadingLineKeys: {},
});
return;
}
const lines = targetLines ?? automat.state.lines ?? [];
const skus = Array.from(
new Set(
lines
.map((l) => (l.sku != null ? String(l.sku).trim() : ''))
.filter(Boolean),
),
);
automat.setState({
supplierArticlesLoading: true,
supplierArticlesError: null,
});
try {
const res = await supplierApi.getArticles(entityId, skus);
const articles = Array.isArray(res?.articles) ? res.articles : [];
const map = buildSupplierArticlesMap(articles);
const byLineKey = {};
for (const line of lines) {
if (line.key != null) {
const rawSku = line.sku != null ? String(line.sku).trim() : '';
byLineKey[line.key] = rawSku ? (map[rawSku.toLowerCase()] || map[rawSku] || null) : null;
}
: {}),
});
if (entityId && idChanged) {
deliveryEditorAutomat.actions.fetchSupplierArticles(entityId);
}
},
setPartyId(partyId) {
deliveryEditorAutomat.actions.setDeliveryEntity(partyId);
},
setExternalNumber(externalNumber) {
deliveryEditorAutomat.setState({
externalNumber,
external_number: externalNumber,
});
},
setNumber(number) {
deliveryEditorAutomat.setState({ number });
},
setType(type) {
deliveryEditorAutomat.setState({ type });
},
setLines(lines) {
const cleanLines = lines ?? [];
deliveryEditorAutomat.setState({ lines: cleanLines, lineitems: cleanLines });
},
setLineitems(lines) {
const cleanLines = lines ?? [];
deliveryEditorAutomat.setState({ lines: cleanLines, lineitems: cleanLines });
},
setNotes(notes) {
const cleanNotes = Array.isArray(notes)
? notes
: notes ? [notes] : [];
deliveryEditorAutomat.setState({ notes: cleanNotes });
},
addLineitem(line = {}) {
const item = createEmptyLineitem(line);
const nextLines = [...(deliveryEditorAutomat.state.lines ?? []), item];
deliveryEditorAutomat.setState({ lines: nextLines, lineitems: nextLines });
},
addLine(line = {}) {
deliveryEditorAutomat.actions.addLineitem(line);
},
updateLineitem(payload) {
const { index, key, ...fields } = payload || {};
const currentLines = deliveryEditorAutomat.state.lines ?? [];
let targetIndex = typeof index === 'number' && !Number.isNaN(index) ? index : -1;
if (targetIndex === -1 && key != null) {
targetIndex = currentLines.findIndex((l) => l.key === key);
}
if (targetIndex >= 0 && targetIndex < currentLines.length) {
const nextLines = [...currentLines];
nextLines[targetIndex] = {
...nextLines[targetIndex],
...fields,
};
deliveryEditorAutomat.setState({ lines: nextLines, lineitems: nextLines });
}
},
updateLine(payload) {
deliveryEditorAutomat.actions.updateLineitem(payload);
},
removeLineitem(payload) {
const currentLines = deliveryEditorAutomat.state.lines ?? [];
let index = typeof payload === 'number' && !Number.isNaN(payload) ? payload : -1;
let targetKey = null;
if (index === -1 && payload != null) {
const key = typeof payload === 'string' ? payload : payload?.key;
if (key != null) {
index = currentLines.findIndex((l) => l.key === key);
targetKey = key;
}
}
if (index >= 0 && index < currentLines.length) {
targetKey = targetKey ?? currentLines[index]?.key;
const nextLines = currentLines.filter((_, i) => i !== index);
const byLineKey = { ...(deliveryEditorAutomat.state.supplierArticlesByLineKey || {}) };
if (targetKey != null) delete byLineKey[targetKey];
deliveryEditorAutomat.setState({
lines: nextLines,
lineitems: nextLines,
supplierArticlesByLineKey: byLineKey,
});
}
},
removeLine(payload) {
deliveryEditorAutomat.actions.removeLineitem(payload);
},
async fetchSupplierArticles(targetEntityId, targetLines) {
const entityId = targetEntityId ?? deliveryEditorAutomat.state.deliveryEntity ?? deliveryEditorAutomat.state.partyId;
if (!entityId) {
deliveryEditorAutomat.setState({
supplierArticles: [],
supplierArticlesMap: {},
supplierArticlesByLineKey: {},
supplierArticlesLoading: false,
loadingLineKeys: {},
});
return;
}
const lines = targetLines ?? deliveryEditorAutomat.state.lines ?? [];
const skus = Array.from(
new Set(
lines
.map((l) => (l.sku != null ? String(l.sku).trim() : ''))
.filter(Boolean),
),
);
deliveryEditorAutomat.setState({
supplierArticlesLoading: true,
supplierArticlesError: null,
});
try {
const res = await supplierApi.getArticles(entityId, skus);
const articles = Array.isArray(res?.articles) ? res.articles : [];
const map = buildSupplierArticlesMap(articles);
const byLineKey = {};
for (const line of lines) {
if (line.key != null) {
const rawSku = line.sku != null ? String(line.sku).trim() : '';
byLineKey[line.key] = rawSku ? (map[rawSku.toLowerCase()] || map[rawSku] || null) : null;
}
automat.setState({
supplierArticles: articles,
supplierArticlesMap: map,
supplierArticlesByLineKey: byLineKey,
supplierArticlesLoading: false,
loadingLineKeys: {},
});
} catch (err) {
console.error('Fehler beim Abrufen der Lieferanten-Artikel:', err);
automat.setState({
supplierArticlesLoading: false,
supplierArticlesError: err?.message || 'Fehler beim Laden der Artikel',
});
}
},
deliveryEditorAutomat.setState({
supplierArticles: articles,
supplierArticlesMap: map,
supplierArticlesByLineKey: byLineKey,
supplierArticlesLoading: false,
loadingLineKeys: {},
});
} catch (err) {
console.error('Fehler beim Abrufen der Lieferanten-Artikel:', err);
deliveryEditorAutomat.setState({
supplierArticlesLoading: false,
supplierArticlesError: err?.message || 'Fehler beim Laden der Artikel',
});
}
},
async fetchSupplierArticleForSku(sku, lineKey) {
const entityId = automat.state.deliveryEntity ?? automat.state.partyId;
const trimmed = sku != null ? String(sku).trim() : '';
async fetchSupplierArticleForSku(sku, lineKey) {
const entityId = deliveryEditorAutomat.state.deliveryEntity ?? deliveryEditorAutomat.state.partyId;
const trimmed = sku != null ? String(sku).trim() : '';
if (!entityId || !trimmed) {
if (lineKey != null) {
const byLineKey = { ...(deliveryEditorAutomat.state.supplierArticlesByLineKey || {}) };
byLineKey[lineKey] = null;
deliveryEditorAutomat.setState({ supplierArticlesByLineKey: byLineKey });
}
return;
}
const prevLoading = deliveryEditorAutomat.state.loadingLineKeys || {};
deliveryEditorAutomat.setState({
loadingLineKeys: lineKey != null ? { ...prevLoading, [lineKey]: true } : prevLoading,
});
try {
const res = await supplierApi.getArticles(entityId, [trimmed]);
const articles = Array.isArray(res?.articles) ? res.articles : [];
const nextMap = buildSupplierArticlesMap(articles, deliveryEditorAutomat.state.supplierArticlesMap);
const matched = nextMap[trimmed.toLowerCase()] || nextMap[trimmed] || articles[0] || null;
const byLineKey = { ...(deliveryEditorAutomat.state.supplierArticlesByLineKey || {}) };
if (lineKey != null) {
byLineKey[lineKey] = matched;
}
const currentList = deliveryEditorAutomat.state.supplierArticles || [];
const mergedList = [...currentList];
for (const a of articles) {
if (!mergedList.some((existing) => existing.id === a.id)) {
mergedList.push(a);
if (!entityId || !trimmed) {
if (lineKey != null) {
const byLineKey = { ...(automat.state.supplierArticlesByLineKey || {}) };
byLineKey[lineKey] = null;
automat.setState({ supplierArticlesByLineKey: byLineKey });
}
return;
}
const nextLoading = { ...(deliveryEditorAutomat.state.loadingLineKeys || {}) };
if (lineKey != null) delete nextLoading[lineKey];
deliveryEditorAutomat.setState({
supplierArticles: mergedList,
supplierArticlesMap: nextMap,
supplierArticlesByLineKey: byLineKey,
loadingLineKeys: nextLoading,
supplierArticlesLoading: Object.keys(nextLoading).length > 0,
const prevLoading = automat.state.loadingLineKeys || {};
automat.setState({
loadingLineKeys: lineKey != null ? { ...prevLoading, [lineKey]: true } : prevLoading,
});
} catch (err) {
console.error('Fehler beim Abrufen des Artikels für SKU:', err);
const nextLoading = { ...(deliveryEditorAutomat.state.loadingLineKeys || {}) };
if (lineKey != null) delete nextLoading[lineKey];
deliveryEditorAutomat.setState({
loadingLineKeys: nextLoading,
supplierArticlesLoading: Object.keys(nextLoading).length > 0,
try {
const res = await supplierApi.getArticles(entityId, [trimmed]);
const articles = Array.isArray(res?.articles) ? res.articles : [];
const nextMap = buildSupplierArticlesMap(articles, automat.state.supplierArticlesMap);
const matched = nextMap[trimmed.toLowerCase()] || nextMap[trimmed] || articles[0] || null;
const byLineKey = { ...(automat.state.supplierArticlesByLineKey || {}) };
if (lineKey != null) {
byLineKey[lineKey] = matched;
}
const currentList = automat.state.supplierArticles || [];
const mergedList = [...currentList];
for (const a of articles) {
if (!mergedList.some((existing) => existing.id === a.id)) {
mergedList.push(a);
}
}
const nextLoading = { ...(automat.state.loadingLineKeys || {}) };
if (lineKey != null) delete nextLoading[lineKey];
automat.setState({
supplierArticles: mergedList,
supplierArticlesMap: nextMap,
supplierArticlesByLineKey: byLineKey,
loadingLineKeys: nextLoading,
supplierArticlesLoading: Object.keys(nextLoading).length > 0,
});
} catch (err) {
console.error('Fehler beim Abrufen des Artikels für SKU:', err);
const nextLoading = { ...(automat.state.loadingLineKeys || {}) };
if (lineKey != null) delete nextLoading[lineKey];
automat.setState({
loadingLineKeys: nextLoading,
supplierArticlesLoading: Object.keys(nextLoading).length > 0,
});
}
},
addAttachment(attachment) {
const id = attachment.id ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const item = { ...attachment, id };
const next = [...(automat.state.attachments ?? []), item];
automat.setState({ attachments: next, files: next });
},
setAttachments(attachments) {
const clean = Array.isArray(attachments) ? attachments : [];
automat.setState({ attachments: clean, files: clean });
},
updateAttachment(payload) {
const { id, ...fields } = payload || {};
const current = automat.state.attachments ?? [];
const idx = current.findIndex((a) => a.id === id || String(a.id) === String(id));
if (idx !== -1) {
const next = [...current];
next[idx] = { ...next[idx], ...fields };
automat.setState({ attachments: next, files: next });
}
},
removeAttachment(idOrPayload) {
const current = automat.state.attachments ?? [];
const targetId =
typeof idOrPayload === 'object' && idOrPayload !== null
? (idOrPayload.id ?? idOrPayload.fileId)
: idOrPayload;
const hasMatch = current.some((a) => a.id === targetId || String(a.id) === String(targetId));
let next;
if (hasMatch) {
next = current.filter((a) => a.id !== targetId && String(a.id) !== String(targetId));
} else if (typeof idOrPayload === 'number' && idOrPayload >= 0 && idOrPayload < current.length) {
next = current.filter((_, idx) => idx !== idOrPayload);
} else {
next = current.filter((a) => a.id !== targetId && String(a.id) !== String(targetId));
}
automat.setState({ attachments: next, files: next });
},
resetDelivery() {
automat.setState({
...initialDelivery,
});
}
},
},
addAttachment(attachment) {
const id = attachment.id ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const item = { ...attachment, id };
const next = [...(deliveryEditorAutomat.state.attachments ?? []), item];
deliveryEditorAutomat.setState({ attachments: next, files: next });
},
resetDeliveryEditor() {
automat.setState({
...initialState,
});
},
};
setAttachments(attachments) {
const clean = Array.isArray(attachments) ? attachments : [];
deliveryEditorAutomat.setState({ attachments: clean, files: clean });
},
automat = new Automat(initialState, actions, {
name,
persist: false,
});
updateAttachment(payload) {
const { id, ...fields } = payload || {};
const current = deliveryEditorAutomat.state.attachments ?? [];
const idx = current.findIndex((a) => a.id === id || String(a.id) === String(id));
if (idx !== -1) {
const next = [...current];
next[idx] = { ...next[idx], ...fields };
deliveryEditorAutomat.setState({ attachments: next, files: next });
}
},
return automat;
}
removeAttachment(idOrPayload) {
const current = deliveryEditorAutomat.state.attachments ?? [];
const targetId =
typeof idOrPayload === 'object' && idOrPayload !== null
? (idOrPayload.id ?? idOrPayload.fileId)
: idOrPayload;
const hasMatch = current.some((a) => a.id === targetId || String(a.id) === String(targetId));
let next;
if (hasMatch) {
next = current.filter((a) => a.id !== targetId && String(a.id) !== String(targetId));
} else if (typeof idOrPayload === 'number' && idOrPayload >= 0 && idOrPayload < current.length) {
next = current.filter((_, idx) => idx !== idOrPayload);
} else {
next = current.filter((a) => a.id !== targetId && String(a.id) !== String(targetId));
}
deliveryEditorAutomat.setState({ attachments: next, files: next });
},
export const deliveryDraftAutomat = createDeliveryEditorAutomat({ name: 'delivery-draft' });
export const deliveryEditorAutomat = deliveryDraftAutomat;
resetDelivery() {
deliveryEditorAutomat.setState({
...initialDelivery,
});
},
resetDeliveryEditor() {
deliveryEditorAutomat.setState({
...initialState,
});
},
});
export function getDeliveryEditorAutomat(docId) {
if (docId == null) {
return deliveryDraftAutomat;
}
const name = `delivery-${docId}`;
let automat = Automat.get(name);
if (!automat) {
automat = createDeliveryEditorAutomat({ name });
}
return automat;
}
export const {
setId,