This commit is contained in:
seb
2026-10-18 19:10:43 +02:00
parent 596d0dc6f1
commit 199bf869a6
7 changed files with 143 additions and 18 deletions

View File

@@ -17,6 +17,7 @@ import OrderDocumentsTiles from './OrderDocumentsTiles';
import OrderShipmentDialog from './OrderShipmentDialog'; import OrderShipmentDialog from './OrderShipmentDialog';
import artikelAutomat, { getArticle } from '../../automats/artikelAutomat'; import artikelAutomat, { getArticle } from '../../automats/artikelAutomat';
import entityAutomat from '../../automats/entityAutomat'; import entityAutomat from '../../automats/entityAutomat';
import customergroupAutomat from '../../automats/customergroupAutomat';
import supplierAutomat from '../../automats/supplierAutomat'; 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';
@@ -33,6 +34,7 @@ import {
manifestConfig, manifestConfig,
apiFor, apiFor,
parseNum, parseNum,
filterNonSupplierEntities,
} from './manifestConfig'; } from './manifestConfig';
import { ensureDocumentNumber, getNextDocumentNumber } from '../../utils/documentNumber'; import { ensureDocumentNumber, getNextDocumentNumber } from '../../utils/documentNumber';
import { import {
@@ -44,12 +46,15 @@ class ManifestForm extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.cfg = manifestConfig(props.kind); this.cfg = manifestConfig(props.kind);
const groups = customergroupAutomat.state.items ?? [];
const initialParties = props.kind === 'delivery' const initialParties = props.kind === 'delivery'
? (supplierAutomat.state.status === 'ready' ? (supplierAutomat.state.suppliers ?? []) : []) ? (supplierAutomat.state.status === 'ready' ? (supplierAutomat.state.suppliers ?? []) : [])
: (entityAutomat.state.status === 'ready' ? (entityAutomat.state.items ?? []) : []); : (entityAutomat.state.status === 'ready'
? filterNonSupplierEntities(entityAutomat.state.items ?? [], groups)
: []);
const partiesReady = props.kind === 'delivery' const partiesReady = props.kind === 'delivery'
? supplierAutomat.state.status === 'ready' ? supplierAutomat.state.status === 'ready'
: entityAutomat.state.status === 'ready'; : (entityAutomat.state.status === 'ready' && customergroupAutomat.state.status === 'ready');
this.draftAutomat = props.mode === 'create' ? getManifestDraftAutomat(props.kind) : null; this.draftAutomat = props.mode === 'create' ? getManifestDraftAutomat(props.kind) : null;
this.editAutomat = props.mode === 'edit' ? getManifestEditAutomat(props.kind) : null; this.editAutomat = props.mode === 'edit' ? getManifestEditAutomat(props.kind) : null;
@@ -150,10 +155,25 @@ class ManifestForm extends PureComponent {
partiesLoading: state.status === 'loading', partiesLoading: state.status === 'loading',
})); }));
} else { } else {
this.unsubParties = entityAutomat.subscribe(this, (state) => ({ const updateParties = () => {
parties: state.items ?? [], const entities = entityAutomat.state.items ?? [];
partiesLoading: state.status === 'loading', const groups = customergroupAutomat.state.items ?? [];
})); this.setState({
parties: filterNonSupplierEntities(entities, groups),
partiesLoading:
entityAutomat.state.status === 'loading' ||
customergroupAutomat.state.status === 'loading',
});
};
this.unsubParties = entityAutomat.subscribe(this, () => {
updateParties();
return null;
});
this.unsubGroups = customergroupAutomat.subscribe(this, () => {
updateParties();
return null;
});
void customergroupAutomat.actions.ensureLoaded();
} }
if (companyAutomat.state.status === 'idle') { if (companyAutomat.state.status === 'idle') {
@@ -231,6 +251,38 @@ class ManifestForm extends PureComponent {
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
if (prevProps.kind !== this.props.kind) {
this.cfg = manifestConfig(this.props.kind);
this.unsubParties?.();
this.unsubGroups?.();
if (this.props.kind === 'delivery') {
this.unsubParties = supplierAutomat.subscribe(this, (state) => ({
parties: state.status === 'ready' ? (state.suppliers ?? []) : [],
partiesLoading: state.status === 'loading',
}));
} else {
const updateParties = () => {
const entities = entityAutomat.state.items ?? [];
const groups = customergroupAutomat.state.items ?? [];
this.setState({
parties: filterNonSupplierEntities(entities, groups),
partiesLoading:
entityAutomat.state.status === 'loading' ||
customergroupAutomat.state.status === 'loading',
});
};
this.unsubParties = entityAutomat.subscribe(this, () => {
updateParties();
return null;
});
this.unsubGroups = customergroupAutomat.subscribe(this, () => {
updateParties();
return null;
});
void customergroupAutomat.actions.ensureLoaded();
}
}
if ( if (
this.props.mode === 'edit' && this.props.mode === 'edit' &&
prevProps.docId !== this.props.docId prevProps.docId !== this.props.docId
@@ -291,6 +343,7 @@ class ManifestForm extends PureComponent {
this.unsubDraft?.(); this.unsubDraft?.();
this.unsubEdit?.(); this.unsubEdit?.();
this.unsubParties?.(); this.unsubParties?.();
this.unsubGroups?.();
this.unsubCompany?.(); this.unsubCompany?.();
if (this._parseAbortController) { if (this._parseAbortController) {
this._parseAbortController.abort(); this._parseAbortController.abort();

View File

@@ -16,7 +16,12 @@ import { enqueueSnackbar } from 'notistack';
import RestoreIcon from '@mui/icons-material/Restore'; import RestoreIcon from '@mui/icons-material/Restore';
import FileDropzone from '../FileDropzone'; import FileDropzone from '../FileDropzone';
import AttachmentList from './AttachmentList'; import AttachmentList from './AttachmentList';
import { hasArticleLines, manifestConfig, partyDisplayName } from './manifestConfig'; import {
hasArticleLines,
manifestConfig,
partyDisplayName,
getLowestSortNonSupplierCustomerGroup,
} from './manifestConfig';
import { notifyError, notifyErrorMessage } from '../../utils/notify'; import { notifyError, notifyErrorMessage } from '../../utils/notify';
import entityAutomat from '../../automats/entityAutomat'; import entityAutomat from '../../automats/entityAutomat';
import customergroupAutomat from '../../automats/customergroupAutomat'; import customergroupAutomat from '../../automats/customergroupAutomat';
@@ -29,7 +34,11 @@ class ManifestHeader extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
const selected = const selected =
(props.parties || []).find((row) => row.id === props.partyId) ?? null; (props.parties || []).find((row) => row.id === props.partyId) ??
(props.kind === 'delivery'
? (supplierAutomat.state.suppliers || []).find((row) => row.id === props.partyId)
: (entityAutomat.state.items || []).find((row) => row.id === props.partyId)) ??
null;
this.state = { this.state = {
partyInput: selected ? partyDisplayName(props.kind, selected) : '', partyInput: selected ? partyDisplayName(props.kind, selected) : '',
creatingParty: false, creatingParty: false,
@@ -48,10 +57,13 @@ class ManifestHeader extends PureComponent {
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (prevProps.partyId !== this.props.partyId) { if (prevProps.partyId !== this.props.partyId || prevProps.parties !== this.props.parties) {
const selected = const selected =
(this.props.parties || []).find((row) => row.id === this.props.partyId) ?? (this.props.parties || []).find((row) => row.id === this.props.partyId) ??
(this.state.createdParty?.id === this.props.partyId ? this.state.createdParty : null); (this.state.createdParty?.id === this.props.partyId ? this.state.createdParty : null) ??
(this.props.kind === 'delivery'
? (supplierAutomat.state.suppliers || []).find((row) => row.id === this.props.partyId)
: (entityAutomat.state.items || []).find((row) => row.id === this.props.partyId));
if (selected) { if (selected) {
this.setState({ partyInput: partyDisplayName(this.props.kind, selected) }); this.setState({ partyInput: partyDisplayName(this.props.kind, selected) });
} else if (this.props.partyId == null) { } else if (this.props.partyId == null) {
@@ -137,8 +149,8 @@ class ManifestHeader extends PureComponent {
} else { } else {
await customergroupAutomat.actions.ensureLoaded(); await customergroupAutomat.actions.ensureLoaded();
const groups = customergroupAutomat.state.items ?? []; const groups = customergroupAutomat.state.items ?? [];
const defaultGroup = groups.find((g) => g.editable !== false) ?? groups[0]; const targetGroup = getLowestSortNonSupplierCustomerGroup(groups) ?? groups[0];
if (!defaultGroup) { if (!targetGroup) {
throw new Error('Keine Kundengruppe vorhanden.'); throw new Error('Keine Kundengruppe vorhanden.');
} }
@@ -156,7 +168,7 @@ class ManifestHeader extends PureComponent {
} }
const body = { const body = {
customergroupId: defaultGroup.id, customergroupId: targetGroup.id,
name, name,
}; };
if (firstName) body.firstName = firstName; if (firstName) body.firstName = firstName;

View File

@@ -33,4 +33,6 @@ export {
restoreAttachmentsFromApi, restoreAttachmentsFromApi,
dateInputValue, dateInputValue,
todayInputValue, todayInputValue,
filterNonSupplierEntities,
getLowestSortNonSupplierCustomerGroup,
} from './manifestConfig'; } from './manifestConfig';

View File

@@ -23,6 +23,10 @@ import {
/** Groups that are not sales customers (filter client-side). */ /** Groups that are not sales customers (filter client-side). */
export const EXCLUDED_CUSTOMER_GROUP_NAMES = new Set(['Lieferanten', 'POS']); export const EXCLUDED_CUSTOMER_GROUP_NAMES = new Set(['Lieferanten', 'POS']);
export function isSupplierCustomerGroup(group) {
return String(group?.name ?? '').trim().toLowerCase() === 'lieferanten';
}
export function entityDisplayName(entity) { export function entityDisplayName(entity) {
if (!entity) return ''; if (!entity) return '';
const fullName = [entity.firstName, entity.name].filter(Boolean).join(' '); const fullName = [entity.firstName, entity.name].filter(Boolean).join(' ');
@@ -37,6 +41,31 @@ export function sortEntities(entities) {
}); });
} }
/**
* Customers = entities whose customergroup is not Lieferanten.
*/
export function filterNonSupplierEntities(entities, groups) {
const supplierIds = new Set(
(groups || [])
.filter(isSupplierCustomerGroup)
.map((g) => g.id),
);
return sortEntities(
(entities || []).filter((e) => !supplierIds.has(e.customergroupId)),
);
}
/**
* Returns the customer group with the lowest sort number that is not Lieferanten.
*/
export function getLowestSortNonSupplierCustomerGroup(groups) {
const nonSuppliers = (groups || []).filter((g) => !isSupplierCustomerGroup(g));
if (nonSuppliers.length === 0) return null;
return [...nonSuppliers].sort(
(a, b) => (Number(a.sort) || 0) - (Number(b.sort) || 0),
)[0];
}
/** /**
* Customers = entities whose customergroup.name is not Lieferanten/POS. * Customers = entities whose customergroup.name is not Lieferanten/POS.
*/ */
@@ -588,8 +617,13 @@ export async function loadParties(cfgOrKind) {
const list = await supplierAutomat.actions.load(); const list = await supplierAutomat.actions.load();
return sortSuppliers(list); return sortSuppliers(list);
} }
const entities = await entityAutomat.actions.ensureLoaded(); const [entities, groups] = await Promise.all([
return entities ?? entityAutomat.state.items ?? []; entityAutomat.actions.ensureLoaded(),
customergroupAutomat.actions.ensureLoaded(),
]);
const entityList = entities ?? entityAutomat.state.items ?? [];
const groupList = groups ?? customergroupAutomat.state.items ?? [];
return filterNonSupplierEntities(entityList, groupList);
} }
export function partyDisplayName(cfgOrKind, party) { export function partyDisplayName(cfgOrKind, party) {

View File

@@ -14,6 +14,7 @@ import { backendErrorFrom } from '../utils/backendError';
import { notifyError } from '../utils/notify'; import { notifyError } from '../utils/notify';
import entityAutomat from '../automats/entityAutomat'; import entityAutomat from '../automats/entityAutomat';
import customergroupAutomat from '../automats/customergroupAutomat'; import customergroupAutomat from '../automats/customergroupAutomat';
import { getLowestSortNonSupplierCustomerGroup } from '../components/manifest/manifestConfig';
import { Kunde } from '../components/kunde'; import { Kunde } from '../components/kunde';
import { BLUE_PANEL } from '../colors'; import { BLUE_PANEL } from '../colors';
@@ -70,7 +71,7 @@ class KundeSearchField extends PureComponent {
try { try {
await customergroupAutomat.actions.ensureLoaded(); await customergroupAutomat.actions.ensureLoaded();
const groups = customergroupAutomat.state.items ?? []; const groups = customergroupAutomat.state.items ?? [];
const defaultGroup = groups.find((g) => g.editable !== false) ?? groups[0]; const defaultGroup = getLowestSortNonSupplierCustomerGroup(groups) ?? groups[0];
if (!defaultGroup) { if (!defaultGroup) {
throw new Error('Keine Kundengruppe vorhanden.'); throw new Error('Keine Kundengruppe vorhanden.');
} }

View File

@@ -11,6 +11,7 @@ import { PanelTile, PanelTileGrid } from 'dashgui/panels';
import { entity as entityApi } from '../../api'; import { entity as entityApi } from '../../api';
import entityAutomat from '../../automats/entityAutomat'; import entityAutomat from '../../automats/entityAutomat';
import customergroupAutomat from '../../automats/customergroupAutomat'; import customergroupAutomat from '../../automats/customergroupAutomat';
import { getLowestSortNonSupplierCustomerGroup } from '../../components/manifest/manifestConfig';
import { router } from '../../appRouter'; import { router } from '../../appRouter';
import { requireDefined, requireObject } from '../../utils/apiShape'; import { requireDefined, requireObject } from '../../utils/apiShape';
import { notifyError } from '../../utils/notify'; import { notifyError } from '../../utils/notify';
@@ -45,9 +46,10 @@ class KundeNeuPage extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
const groups = customergroupAutomat.state.items ?? []; const groups = customergroupAutomat.state.items ?? [];
const defaultGroup = getLowestSortNonSupplierCustomerGroup(groups) ?? groups[0];
const prefill = getInitialPrefill(props); const prefill = getInitialPrefill(props);
this.state = { this.state = {
customergroupId: groups.length > 0 ? groups[0].id : null, customergroupId: defaultGroup ? defaultGroup.id : null,
number: '', number: '',
name: prefill.name, name: prefill.name,
firstName: prefill.firstName, firstName: prefill.firstName,
@@ -59,6 +61,14 @@ class KundeNeuPage extends PureComponent {
componentDidMount() { componentDidMount() {
void this.loadNumber(); void this.loadNumber();
if (this.state.customergroupId == null) {
void customergroupAutomat.actions.ensureLoaded().then((groups) => {
const defaultGroup = getLowestSortNonSupplierCustomerGroup(groups) ?? groups?.[0];
if (defaultGroup && this.state.customergroupId == null) {
this.setState({ customergroupId: defaultGroup.id });
}
});
}
} }
async loadNumber() { async loadNumber() {

View File

@@ -37,7 +37,8 @@ import {
ArticleSearchDialog, ArticleSearchDialog,
BookDeliveryDialog, BookDeliveryDialog,
} from '../../components/manifest'; } from '../../components/manifest';
import { loadParties, apiFor, resolveLineSkus } from '../../components/manifest/manifestConfig'; import { loadParties, apiFor, resolveLineSkus, sortSuppliers } from '../../components/manifest/manifestConfig';
import supplierAutomat from '../../automats/supplierAutomat';
import warehouseAutomat from '../../automats/warehouseAutomat'; import warehouseAutomat from '../../automats/warehouseAutomat';
import { article as articleApi, delivery as deliveryApi } from '../../api'; import { article as articleApi, delivery as deliveryApi } from '../../api';
import { getCategoryState } from '../../automats/categoryAutomat'; import { getCategoryState } from '../../automats/categoryAutomat';
@@ -108,6 +109,12 @@ class LieferungNeuPage extends PureComponent {
super(props); super(props);
this.cfg = manifestConfig('delivery'); this.cfg = manifestConfig('delivery');
this.automat = getDeliveryEditorAutomat(props.docId); this.automat = getDeliveryEditorAutomat(props.docId);
if (
supplierAutomat.state.status === 'ready' &&
(!this.automat.state.deliveryEntities || this.automat.state.deliveryEntities.length === 0)
) {
this.automat.actions.setDeliveryEntities(sortSuppliers(supplierAutomat.state.suppliers));
}
const initialDate = this.automat.state.date || (props.docId == null ? todayInputValue() : null); const initialDate = this.automat.state.date || (props.docId == null ? todayInputValue() : null);
if (props.docId == null && !this.automat.state.date) { if (props.docId == null && !this.automat.state.date) {
this.automat.actions.setDate(initialDate); this.automat.actions.setDate(initialDate);
@@ -181,6 +188,11 @@ class LieferungNeuPage extends PureComponent {
this._warehouseUnsub = warehouseAutomat.subscribe(this, (state) => ({ this._warehouseUnsub = warehouseAutomat.subscribe(this, (state) => ({
warehouses: state.warehouses, warehouses: state.warehouses,
})); }));
this._supplierUnsub = supplierAutomat.subscribe(this, (state) => {
if (state.status === 'ready' && Array.isArray(state.suppliers)) {
this.automat.actions.setDeliveryEntities(sortSuppliers(state.suppliers));
}
});
void warehouseAutomat.actions.load().catch(() => { }); void warehouseAutomat.actions.load().catch(() => { });
this._cancelled = false; this._cancelled = false;
if (this.props.docId != null) { if (this.props.docId != null) {
@@ -225,6 +237,7 @@ class LieferungNeuPage extends PureComponent {
this.unsubscribe?.(); this.unsubscribe?.();
this._settingsUnsub?.(); this._settingsUnsub?.();
this._warehouseUnsub?.(); this._warehouseUnsub?.();
this._supplierUnsub?.();
} }
async loadPartiesOnly() { async loadPartiesOnly() {