u
This commit is contained in:
@@ -12,7 +12,10 @@ import HelpBadge from './HelpBadge';
|
||||
import { NAV_ITEMS, SUB_NAV, resolveActiveNav, resolveActiveSub } from '../nav';
|
||||
import { artikelQueryPath, buildSearchUrl } from './article/artikelQuery';
|
||||
import { defaultCategoryPath } from './category/categoryRoute';
|
||||
import { navigationAutomat } from '../automats';
|
||||
import companyAutomat from '../automats/companyAutomat';
|
||||
import navigationAutomat from '../automats/navigationAutomat';
|
||||
import { initPwa } from '../automats/pwaAutomat';
|
||||
import PwaUpdateDialog from './PwaUpdateDialog';
|
||||
import {
|
||||
NAV_ITEM_ACTIVE_ACCENT,
|
||||
NAV_SUB_ACTIVE,
|
||||
@@ -22,9 +25,16 @@ import {
|
||||
import { ChatFab, ChatDrawer } from './chat';
|
||||
import { PinnedAppletFabs, PinnedAppletDialog } from './applet';
|
||||
|
||||
// Ensure companyAutomat is retained as part of the 1st-level navigation shell
|
||||
void companyAutomat;
|
||||
initPwa();
|
||||
|
||||
const BreakpointOverlay = 0 && import.meta.env.DEV
|
||||
? lazy(() => import('./BreakpointOverlay'))
|
||||
: null;
|
||||
const ChunkCacheMonitor = import.meta.env.DEV
|
||||
? lazy(() => import('../devtools/ChunkCacheMonitor'))
|
||||
: null;
|
||||
|
||||
|
||||
const SIDEBAR_WIDTH = 144;
|
||||
@@ -403,13 +413,21 @@ export default function AppShell() {
|
||||
minWidth: 0,
|
||||
})}
|
||||
>
|
||||
<Outlet />
|
||||
<Suspense fallback={null}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</Box>
|
||||
</Box>
|
||||
<ChatFab />
|
||||
<ChatDrawer />
|
||||
<PinnedAppletFabs pathname={pathname} />
|
||||
<PinnedAppletDialog />
|
||||
<PwaUpdateDialog />
|
||||
{ChunkCacheMonitor ? (
|
||||
<Suspense fallback={null}>
|
||||
<ChunkCacheMonitor />
|
||||
</Suspense>
|
||||
) : null}
|
||||
{BreakpointOverlay && (
|
||||
<Suspense fallback={null}>
|
||||
<BreakpointOverlay />
|
||||
|
||||
@@ -35,10 +35,8 @@ import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
|
||||
import RequestQuoteIcon from '@mui/icons-material/RequestQuote';
|
||||
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
|
||||
import BackendError from './BackendError';
|
||||
import { company, number as numberApi } from '../api';
|
||||
import { company } from '../api';
|
||||
import authAutomat, { bootstrapAuthMe } from '../automats/authAutomat';
|
||||
import companyAutomat from '../automats/companyAutomat';
|
||||
import numberAutomat from '../automats/numberAutomat';
|
||||
import {
|
||||
defaultIso639,
|
||||
ISO639_OPTIONS,
|
||||
@@ -213,30 +211,10 @@ class CompanySetupDialog extends Component {
|
||||
iso639,
|
||||
taxrate: parsedTaxrate,
|
||||
reducedtaxrate: parsedReducedtaxrate,
|
||||
numbers,
|
||||
});
|
||||
|
||||
// Apply initial number sequences if set
|
||||
if (numbers) {
|
||||
try {
|
||||
const listRes = await numberApi.list();
|
||||
const serverNumbers = listRes?.numbers ?? [];
|
||||
for (const serverRow of serverNumbers) {
|
||||
const userConfig = numbers[serverRow.type];
|
||||
if (userConfig) {
|
||||
const prefix = String(userConfig.prefix ?? '');
|
||||
const padding = Math.max(0, Number(userConfig.padding) || 0);
|
||||
const next = Math.max(1, Number(userConfig.next) || 1);
|
||||
await numberApi.update(serverRow.id, { prefix, padding, next });
|
||||
}
|
||||
}
|
||||
} catch (numErr) {
|
||||
console.warn('Initial number sequences could not be saved:', numErr);
|
||||
}
|
||||
}
|
||||
|
||||
await bootstrapAuthMe();
|
||||
await companyAutomat.actions.load();
|
||||
await numberAutomat.actions.reload().catch(() => { });
|
||||
} catch (err) {
|
||||
this.setState({ error: backendErrorFrom(err) });
|
||||
} finally {
|
||||
|
||||
@@ -7,7 +7,7 @@ import Paper from '@mui/material/Paper';
|
||||
import Popper from '@mui/material/Popper';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { helpAutomat } from '../automats';
|
||||
import helpAutomat from '../automats/helpAutomat';
|
||||
import {
|
||||
RED,
|
||||
RED_HOVER,
|
||||
|
||||
@@ -22,7 +22,7 @@ import { enqueueSnackbar } from 'notistack';
|
||||
import AppDialog from './AppDialog';
|
||||
import { thumb, article as articleApi } from '../api';
|
||||
import artikelAutomat from '../automats/artikelAutomat';
|
||||
import brandAutomat from '../automats/brandAutomat';
|
||||
import companyAutomat from '../automats/companyAutomat';
|
||||
import categoryAutomat from '../automats/categoryAutomat';
|
||||
import chatAutomat from '../automats/chatAutomat';
|
||||
import { requireArray, requireObject } from '../utils/apiShape';
|
||||
@@ -238,7 +238,7 @@ export default class PicturesDialog extends PureComponent {
|
||||
if (onUpload) {
|
||||
await onUpload(file);
|
||||
} else if (brand?.id) {
|
||||
await brandAutomat.actions.addImage(brand.id, file);
|
||||
await companyAutomat.actions.addBrandImage(brand.id, file);
|
||||
enqueueSnackbar(`Markenlogo für „${brand.name || ''}“ gespeichert.`, { variant: 'success' });
|
||||
} else if (article?.sku) {
|
||||
const uploadStarted = performance.now();
|
||||
@@ -318,7 +318,7 @@ export default class PicturesDialog extends PureComponent {
|
||||
if (onDelete) {
|
||||
await onDelete(pic, 0);
|
||||
} else if (brand?.id) {
|
||||
await brandAutomat.actions.deleteImage(brand.id);
|
||||
await companyAutomat.actions.deleteBrandImage(brand.id);
|
||||
enqueueSnackbar('Markenlogo gelöscht.', { variant: 'success' });
|
||||
} else if (category?.id) {
|
||||
await categoryAutomat.actions.deleteCategoryImage(category.id);
|
||||
|
||||
@@ -14,7 +14,8 @@ import SystemUpdateAltIcon from '@mui/icons-material/SystemUpdateAlt';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import authAutomat, { bootstrapAuthMe } from '../automats/authAutomat';
|
||||
import { helpAutomat, toggleHelp, pwaAutomat, openPwaDialog } from '../automats';
|
||||
import helpAutomat, { toggleHelp } from '../automats/helpAutomat';
|
||||
import pwaAutomat, { openPwaDialog } from '../automats/pwaAutomat';
|
||||
import SettingsDialog from './settings/SettingsDialog';
|
||||
import { notifyError } from '../utils/notify';
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ import { stock } from '../../api';
|
||||
import { formatStock } from './searchFormat';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import artikelAutomat from '../../automats/artikelAutomat';
|
||||
import warehouseAutomat from '../../automats/warehouseAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import {
|
||||
BORDER,
|
||||
GREEN_BG,
|
||||
@@ -64,7 +64,7 @@ export default class ArticleStockAdjustDialog extends PureComponent {
|
||||
reason: '',
|
||||
submitting: false,
|
||||
error: null,
|
||||
warehouses: warehouseAutomat.state.warehouses || [],
|
||||
warehouses: companyAutomat.state.warehouses || [],
|
||||
trackStock: isTrackStock,
|
||||
initialTrackStock: isTrackStock,
|
||||
};
|
||||
@@ -79,14 +79,14 @@ export default class ArticleStockAdjustDialog extends PureComponent {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsubWarehouse = warehouseAutomat.subscribe((state) => {
|
||||
this.unsubWarehouse = companyAutomat.subscribe((state) => {
|
||||
const warehouses = state.warehouses || [];
|
||||
this.setState((prev) => ({
|
||||
warehouses,
|
||||
warehouseId: prev.warehouseId || (warehouses.length > 0 ? warehouses[0].id : ''),
|
||||
}));
|
||||
});
|
||||
void warehouseAutomat.actions.load().catch(() => {});
|
||||
}, ['warehouses']);
|
||||
void companyAutomat.actions.loadWarehouses().catch(() => {});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -95,7 +95,7 @@ export default class ArticleStockAdjustDialog extends PureComponent {
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.open && !prevProps.open) {
|
||||
void warehouseAutomat.actions.load().catch(() => {});
|
||||
void companyAutomat.actions.loadWarehouses().catch(() => {});
|
||||
const initialWhId = this.resolveInitialWarehouseId(this.props);
|
||||
const isTrackStock =
|
||||
this.props.article?.track_stock !== false && this.props.article?.track_stock !== 0;
|
||||
@@ -107,7 +107,7 @@ export default class ArticleStockAdjustDialog extends PureComponent {
|
||||
reason: '',
|
||||
submitting: false,
|
||||
error: null,
|
||||
warehouses: warehouseAutomat.state.warehouses || [],
|
||||
warehouses: companyAutomat.state.warehouses || [],
|
||||
trackStock: isTrackStock,
|
||||
initialTrackStock: isTrackStock,
|
||||
});
|
||||
@@ -121,7 +121,7 @@ export default class ArticleStockAdjustDialog extends PureComponent {
|
||||
if (articleWarehouses.length > 0) {
|
||||
return articleWarehouses[0].id;
|
||||
}
|
||||
const allWarehouses = warehouseAutomat.state.warehouses || [];
|
||||
const allWarehouses = companyAutomat.state.warehouses || [];
|
||||
return allWarehouses.length > 0 ? allWarehouses[0].id : '';
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import LayersOutlinedIcon from '@mui/icons-material/LayersOutlined';
|
||||
import AppDialog from '../AppDialog';
|
||||
import ArticlePhoto from './ArticlePhoto';
|
||||
import { article as articleApi } from '../../api';
|
||||
import brandAutomat from '../../automats/brandAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import categoryAutomat from '../../automats/categoryAutomat';
|
||||
import { formatPrice } from '../../utils/price';
|
||||
|
||||
@@ -195,7 +195,7 @@ export default class ArticleUnifyDialog extends Component {
|
||||
|
||||
resolveBrandName(article) {
|
||||
if (!article) return '–';
|
||||
const brands = brandAutomat.state.brands || [];
|
||||
const brands = companyAutomat.state.brands || [];
|
||||
if (article.brandId != null) {
|
||||
const b = brands.find((x) => x.id === article.brandId);
|
||||
if (b?.name) return b.name;
|
||||
|
||||
@@ -15,7 +15,7 @@ import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import TextInput from '../TextInput';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import unitAutomat from '../../automats/unitAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { unit as unitApi } from '../../api';
|
||||
import { requireObject } from '../../utils/apiShape';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
@@ -85,7 +85,7 @@ export default class ArticleUnitEditorDialog extends Component {
|
||||
async loadUnits() {
|
||||
this.setState({ unitsLoading: true });
|
||||
try {
|
||||
const units = await unitAutomat.actions.load();
|
||||
const units = await companyAutomat.actions.loadUnits();
|
||||
this.setState({ units, unitsLoading: false });
|
||||
} catch (err) {
|
||||
this.setState({ unitsLoading: false });
|
||||
@@ -125,7 +125,7 @@ export default class ArticleUnitEditorDialog extends Component {
|
||||
});
|
||||
requireObject(data, 'PUT /api/unit');
|
||||
requireObject(data.unit, 'unit');
|
||||
const units = await unitAutomat.actions.load();
|
||||
const units = await companyAutomat.actions.loadUnits();
|
||||
this.setState({
|
||||
busy: false,
|
||||
units,
|
||||
@@ -145,7 +145,7 @@ export default class ArticleUnitEditorDialog extends Component {
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await unitApi.delete(selectedSi);
|
||||
const units = await unitAutomat.actions.load();
|
||||
const units = await companyAutomat.actions.loadUnits();
|
||||
this.setState({ busy: false, units, form: emptyForm(), selectedSi: null });
|
||||
this.props.onUnitDeleted?.(selectedSi);
|
||||
} catch (err) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import TextInput from '../../TextInput';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Box from '@mui/material/Box';
|
||||
import { EditablePanelTile, PanelTile } from 'dashgui/panels';
|
||||
import brandAutomat from '../../../automats/brandAutomat';
|
||||
import companyAutomat from '../../../automats/companyAutomat';
|
||||
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||
import { requireObject } from '../../../utils/apiShape';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
@@ -119,16 +119,16 @@ export default class ArticleViewBrand extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
brands: brandAutomat.state.brands ?? [],
|
||||
brands: companyAutomat.state.brands ?? [],
|
||||
};
|
||||
this._cancelled = false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._cancelled = false;
|
||||
this.unsub = brandAutomat.subscribe(this, (state) => ({
|
||||
this.unsub = companyAutomat.subscribe(this, (state) => ({
|
||||
brands: state.brands ?? [],
|
||||
}));
|
||||
}), ['brands']);
|
||||
const parentSku = this.props.article?.parentSku || this.props.article?.parent_sku;
|
||||
if (parentSku && !getArticle(parentSku)) {
|
||||
void artikelAutomat.actions.loadArticle(parentSku).catch(() => {});
|
||||
@@ -168,8 +168,8 @@ export default class ArticleViewBrand extends PureComponent {
|
||||
if (existing) {
|
||||
brandId = existing.id;
|
||||
} else {
|
||||
await brandAutomat.actions.create({ name });
|
||||
const nextBrands = await brandAutomat.actions.load();
|
||||
await companyAutomat.actions.createBrand({ name });
|
||||
const nextBrands = await companyAutomat.actions.loadBrands();
|
||||
this.setState({ brands: nextBrands });
|
||||
brandId = nextBrands.find(
|
||||
(row) => String(row.name ?? '').toLowerCase() === name.toLowerCase(),
|
||||
|
||||
@@ -32,9 +32,8 @@ import { PanelTile } from 'dashgui/panels';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { artikelDetailPath } from '../articleRoute';
|
||||
import { article as articleApi } from '../../../api';
|
||||
import attributeAutomat, { selectAttributes } from '../../../automats/attributeAutomat';
|
||||
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||
import companyAutomat from '../../../automats/companyAutomat';
|
||||
import companyAutomat, { selectAttributes } from '../../../automats/companyAutomat';
|
||||
import { formatStock } from '../searchFormat';
|
||||
import { formatPrice, getArticleGross } from '../../../utils/price';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
@@ -159,10 +158,7 @@ export default class ArticleViewLists extends PureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
this._cancelled = false;
|
||||
this.unsubCompany = companyAutomat.subscribe(this, (state) => ({
|
||||
company: state.current,
|
||||
}));
|
||||
this.unsubAttr = attributeAutomat.subscribe((state) => {
|
||||
this.unsubCompany = companyAutomat.subscribe((state) => {
|
||||
if (this._cancelled) return;
|
||||
this.setState((prev) => {
|
||||
const assignedIds = new Set(prev.assignedAttributes.map((a) => a.attributeId));
|
||||
@@ -172,19 +168,20 @@ export default class ArticleViewLists extends PureComponent {
|
||||
nextSelectedId = selectable[0].id;
|
||||
}
|
||||
return {
|
||||
company: state.current,
|
||||
attributes: state.attributes,
|
||||
selectedAttributeId: nextSelectedId,
|
||||
};
|
||||
});
|
||||
});
|
||||
}, ['current', 'attributes']);
|
||||
|
||||
this.unsubArtikel = artikelAutomat.subscribe(() => {
|
||||
if (this._cancelled) return;
|
||||
this.forceUpdate();
|
||||
});
|
||||
|
||||
if (attributeAutomat.state.status === 'idle') {
|
||||
void attributeAutomat.actions.load();
|
||||
if (companyAutomat.state.attributesStatus === 'idle') {
|
||||
void companyAutomat.actions.loadAttributes();
|
||||
}
|
||||
|
||||
if (this.props.article?.sku) {
|
||||
@@ -262,7 +259,6 @@ export default class ArticleViewLists extends PureComponent {
|
||||
componentWillUnmount() {
|
||||
this._cancelled = true;
|
||||
this.unsubCompany?.();
|
||||
this.unsubAttr?.();
|
||||
this.unsubArtikel?.();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import Typography from '@mui/material/Typography';
|
||||
import { EditablePanelTile, PanelTile } from 'dashgui/panels';
|
||||
import ArticlePriceTiersDialog from '../ArticlePriceTiersDialog';
|
||||
import ArticleUnitEditorDialog from '../ArticleUnitEditorDialog';
|
||||
import unitAutomat from '../../../automats/unitAutomat';
|
||||
import companyAutomat from '../../../automats/companyAutomat';
|
||||
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||
import { requireDefined, requireObject } from '../../../utils/apiShape';
|
||||
@@ -333,7 +332,7 @@ export default class ArticleViewPrice extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const company = companyFromStore();
|
||||
const units = unitAutomat.state.units;
|
||||
const units = companyAutomat.state.units;
|
||||
this.state = {
|
||||
tiersDialogOpen: false,
|
||||
unitEditorOpen: false,
|
||||
@@ -345,12 +344,10 @@ export default class ArticleViewPrice extends PureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
this._cancelled = false;
|
||||
this.unsubUnits = unitAutomat.subscribe(this, (state) => ({
|
||||
units: state.units ?? [],
|
||||
}));
|
||||
this.unsubCompany = companyAutomat.subscribe(this, (state) => ({
|
||||
units: state.units ?? [],
|
||||
company: state.current ?? null,
|
||||
}));
|
||||
}), ['units', 'current']);
|
||||
this.unsubArtikel = artikelAutomat.subscribe(this, () => ({}));
|
||||
if (this.props.article?.isParent) {
|
||||
this.ensureChildren(this.props.article.children);
|
||||
@@ -369,8 +366,6 @@ export default class ArticleViewPrice extends PureComponent {
|
||||
|
||||
componentWillUnmount() {
|
||||
this._cancelled = true;
|
||||
this.unsubUnits?.();
|
||||
this.unsubUnits = null;
|
||||
this.unsubCompany?.();
|
||||
this.unsubCompany = null;
|
||||
this.unsubArtikel?.();
|
||||
@@ -462,7 +457,7 @@ export default class ArticleViewPrice extends PureComponent {
|
||||
}
|
||||
|
||||
refreshUnits = async () => {
|
||||
const units = await unitAutomat.actions.load();
|
||||
const units = await companyAutomat.actions.loadUnits();
|
||||
this.setState({ units });
|
||||
};
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ import Tooltip from '@mui/material/Tooltip';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import attributeAutomat, { selectAttributes } from '../../../automats/attributeAutomat';
|
||||
import propertyAutomat, { selectProperties } from '../../../automats/propertyAutomat';
|
||||
import companyAutomat, { selectAttributes, selectProperties } from '../../../automats/companyAutomat';
|
||||
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||
import { article as articleApi } from '../../../api';
|
||||
|
||||
@@ -81,26 +80,27 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
this._cancelled = false;
|
||||
this.unsubAttr = attributeAutomat.subscribe((state) => {
|
||||
this.unsubCompany = companyAutomat.subscribe((state) => {
|
||||
if (this._cancelled) return;
|
||||
this.setState((prev) => {
|
||||
let nextKey = prev.selectedMerkmalKey;
|
||||
let nextItemId = prev.selectedItemId;
|
||||
|
||||
const selectableAttrs = state.attributes;
|
||||
const globalProps = state.properties;
|
||||
|
||||
const isCurrentKeyValid =
|
||||
nextKey &&
|
||||
(selectableAttrs.some((a) => `attr-${a.id}` === nextKey) ||
|
||||
prev.globalProperties.some((p) => `prop-${p.id}` === nextKey));
|
||||
globalProps.some((p) => `prop-${p.id}` === nextKey));
|
||||
|
||||
if (!isCurrentKeyValid) {
|
||||
const first = selectableAttrs[0];
|
||||
if (first) {
|
||||
nextKey = `attr-${first.id}`;
|
||||
nextItemId = first.values?.[0]?.id ?? '';
|
||||
} else if (prev.globalProperties[0]) {
|
||||
nextKey = `prop-${prev.globalProperties[0].id}`;
|
||||
} else if (globalProps[0]) {
|
||||
nextKey = `prop-${globalProps[0].id}`;
|
||||
nextItemId = '';
|
||||
} else {
|
||||
nextKey = '';
|
||||
@@ -108,38 +108,24 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
}
|
||||
}
|
||||
return {
|
||||
globalAttributes: state.attributes,
|
||||
globalAttributes: selectableAttrs,
|
||||
globalProperties: globalProps,
|
||||
selectedMerkmalKey: nextKey,
|
||||
selectedItemId: nextItemId,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
this.unsubProp = propertyAutomat.subscribe((state) => {
|
||||
if (this._cancelled) return;
|
||||
this.setState((prev) => {
|
||||
const selectableAttrs = prev.globalAttributes;
|
||||
let nextKey = prev.selectedMerkmalKey;
|
||||
if (!nextKey && !selectableAttrs.length && state.properties[0]) {
|
||||
nextKey = `prop-${state.properties[0].id}`;
|
||||
}
|
||||
return {
|
||||
globalProperties: state.properties,
|
||||
selectedMerkmalKey: nextKey,
|
||||
};
|
||||
});
|
||||
});
|
||||
}, ['attributes', 'properties']);
|
||||
|
||||
this.unsubArtikel = artikelAutomat.subscribe(() => {
|
||||
if (this._cancelled) return;
|
||||
this.forceUpdate();
|
||||
});
|
||||
|
||||
if (attributeAutomat.state.status === 'idle') {
|
||||
void attributeAutomat.actions.load();
|
||||
if (companyAutomat.state.attributesStatus === 'idle') {
|
||||
void companyAutomat.actions.loadAttributes();
|
||||
}
|
||||
if (propertyAutomat.state.status === 'idle') {
|
||||
void propertyAutomat.actions.load();
|
||||
if (companyAutomat.state.propertiesStatus === 'idle') {
|
||||
void companyAutomat.actions.loadProperties();
|
||||
}
|
||||
|
||||
void this.loadArticleData(this.props.sku);
|
||||
@@ -153,8 +139,7 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
|
||||
componentWillUnmount() {
|
||||
this._cancelled = true;
|
||||
this.unsubAttr?.();
|
||||
this.unsubProp?.();
|
||||
this.unsubCompany?.();
|
||||
this.unsubArtikel?.();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import sseAutomat from '../../../automats/sseAutomat';
|
||||
import shipmentAutomat from '../../../automats/shipmentAutomat';
|
||||
import orderAutomat from '../../../automats/orderAutomat';
|
||||
import entityAutomat from '../../../automats/entityAutomat';
|
||||
import brandAutomat from '../../../automats/brandAutomat';
|
||||
import companyAutomat from '../../../automats/companyAutomat';
|
||||
import { shipment as shipmentApi, order as orderApi } from '../../../api';
|
||||
import { ShipmentDetailDialog } from '../../manifest';
|
||||
import SkeleHashImage from '../../settings/SkeleHashImage';
|
||||
@@ -709,8 +709,8 @@ class ArticleHistoryTab extends PureComponent {
|
||||
if (entityAutomat.state.status === 'idle') {
|
||||
void entityAutomat.actions.load().catch(() => {});
|
||||
}
|
||||
if (brandAutomat.state.status === 'idle') {
|
||||
void brandAutomat.actions.load().catch(() => {});
|
||||
if (companyAutomat.state.brandsStatus === 'idle') {
|
||||
void companyAutomat.actions.loadBrands().catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -868,7 +868,7 @@ class ArticleHistoryTab extends PureComponent {
|
||||
|
||||
handleOpenBrand(brandName) {
|
||||
if (!brandName) return;
|
||||
const match = (brandAutomat.state.brands || []).find(
|
||||
const match = (companyAutomat.state.brands || []).find(
|
||||
(b) => b && (b.name?.toLowerCase() === brandName.toLowerCase() || String(b.id) === String(brandName)),
|
||||
);
|
||||
const brandParam = match?.id != null ? String(match.id) : brandName;
|
||||
|
||||
@@ -9,7 +9,7 @@ import GoogleIcon from '@mui/icons-material/Google';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
|
||||
import { article as articleApi } from '../../../api';
|
||||
import { artikelAutomat, getArticle } from '../../../automats';
|
||||
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
import {
|
||||
articleEans,
|
||||
|
||||
@@ -23,7 +23,6 @@ import { enqueueSnackbar } from 'notistack';
|
||||
import { article as articleApi } from '../../../api';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
import { formatPrice } from '../../../utils/price';
|
||||
import supplierAutomat from '../../../automats/supplierAutomat';
|
||||
import { articleSupplierAutomat } from '../../../automats/articleDetailAutomat';
|
||||
import sseAutomat from '../../../automats/sseAutomat';
|
||||
import companyAutomat from '../../../automats/companyAutomat';
|
||||
@@ -93,7 +92,7 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
|
||||
// 2. Fetch available company suppliers
|
||||
try {
|
||||
const companySuppliers = await supplierAutomat.actions.load();
|
||||
const companySuppliers = await companyAutomat.actions.loadSuppliers();
|
||||
this.setState({ companySuppliers: companySuppliers ?? [] });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
|
||||
@@ -9,7 +9,7 @@ import ImageSearchIcon from '@mui/icons-material/ImageSearch';
|
||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import brandAutomat from '../../automats/brandAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import BrandThumb from './BrandThumb';
|
||||
import BrandImageSearchDialog from './BrandImageSearchDialog';
|
||||
@@ -56,7 +56,7 @@ class ArtikelMarke extends PureComponent {
|
||||
|
||||
async renameBrand(brand, name) {
|
||||
try {
|
||||
await brandAutomat.actions.rename(brand.id, { name });
|
||||
await companyAutomat.actions.renameBrand(brand.id, { name });
|
||||
enqueueSnackbar('Marke gespeichert.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
this.setState({ draft: brand?.name ?? '' });
|
||||
@@ -99,7 +99,7 @@ class ArtikelMarke extends PureComponent {
|
||||
if (!window.confirm(`Marke „${brand.name}" wirklich löschen?`)) return;
|
||||
void (async () => {
|
||||
try {
|
||||
await brandAutomat.actions.remove(brand.id);
|
||||
await companyAutomat.actions.removeBrand(brand.id);
|
||||
enqueueSnackbar('Marke gelöscht.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
|
||||
@@ -3,7 +3,7 @@ import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import brandAutomat from '../../automats/brandAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
|
||||
// Own component so typing a new brand name never rerenders ArtikelMarkenPage
|
||||
@@ -19,7 +19,7 @@ class ArtikelMarkeNew extends PureComponent {
|
||||
const name = this.state.newName.trim();
|
||||
if (!name) return;
|
||||
try {
|
||||
const row = await brandAutomat.actions.create({ name });
|
||||
const row = await companyAutomat.actions.createBrand({ name });
|
||||
enqueueSnackbar(`Marke „${row.name || name}" angelegt.`, { variant: 'success' });
|
||||
this.setState({ newName: '' });
|
||||
} catch (err) {
|
||||
|
||||
@@ -25,7 +25,7 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { serper } from '../../api';
|
||||
import brandAutomat from '../../automats/brandAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { downloadAndPrepareLogoAvif } from '../../utils/cropLogo';
|
||||
import {
|
||||
CHECKERBOARD,
|
||||
@@ -211,8 +211,8 @@ export default class BrandImageSearchDialog extends PureComponent {
|
||||
|
||||
this.setState({ processingStep: 'Logo wird gespeichert...' });
|
||||
|
||||
// 2. Upload as brandpic and update brandAutomat
|
||||
await brandAutomat.actions.addImage(brand.id, avifFile);
|
||||
// 2. Upload as brandpic and update companyAutomat
|
||||
await companyAutomat.actions.addBrandImage(brand.id, avifFile);
|
||||
|
||||
enqueueSnackbar(`Markenlogo für „${brand.name}“ erfolgreich gespeichert.`, {
|
||||
variant: 'success',
|
||||
|
||||
@@ -9,7 +9,7 @@ import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
||||
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
|
||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import carrierAutomat from '../../automats/carrierAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
|
||||
class CarrierItem extends PureComponent {
|
||||
@@ -54,7 +54,7 @@ class CarrierItem extends PureComponent {
|
||||
|
||||
async updateCarrier(item, patch) {
|
||||
try {
|
||||
await carrierAutomat.actions.update(item.id, patch);
|
||||
await companyAutomat.actions.updateCarrier(item.id, patch);
|
||||
enqueueSnackbar('Versandart gespeichert.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
this.setState({ draft: item.name ?? '' });
|
||||
@@ -95,7 +95,7 @@ class CarrierItem extends PureComponent {
|
||||
if (!window.confirm(`Versandart „${item.name}“ wirklich löschen?`)) return;
|
||||
void (async () => {
|
||||
try {
|
||||
await carrierAutomat.actions.remove(item.id);
|
||||
await companyAutomat.actions.removeCarrier(item.id);
|
||||
enqueueSnackbar('Versandart gelöscht.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
|
||||
@@ -40,7 +40,7 @@ import { setting as settingApi } from '../../api';
|
||||
import chatAutomat, {
|
||||
selectPendingAskUser,
|
||||
} from '../../automats/chatAutomat';
|
||||
import userSettingsAutomat from '../../automats/userSettingsAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import vfsAutomat from '../../automats/vfsAutomat';
|
||||
import { VfsEditorPanel, VfsFileIcon } from '../vfs';
|
||||
import ChatFlowList from './ChatFlowList';
|
||||
@@ -130,7 +130,7 @@ export default class ChatDrawer extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const s = chatAutomat.state;
|
||||
const settings = userSettingsAutomat.state;
|
||||
const settings = companyAutomat.state;
|
||||
const vfs = vfsAutomat.state;
|
||||
this.state = {
|
||||
isOpen: s.isOpen,
|
||||
@@ -293,12 +293,12 @@ export default class ChatDrawer extends PureComponent {
|
||||
return patch;
|
||||
});
|
||||
|
||||
this.unsubSettings = userSettingsAutomat.subscribe(this, (state) => {
|
||||
this.unsubSettings = companyAutomat.subscribe(this, (state) => {
|
||||
if (!this.state.isResizing) {
|
||||
return { drawerWidth: state.chatDrawerWidth || DEFAULT_DRAWER_WIDTH };
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}, ['chatDrawerWidth']);
|
||||
|
||||
this.unsubVfs = vfsAutomat.subscribe(this, (state) => ({
|
||||
isVfsOpen: state.isOpen,
|
||||
@@ -465,7 +465,7 @@ export default class ChatDrawer extends PureComponent {
|
||||
|
||||
this.setState({ isResizing: false });
|
||||
vfsAutomat.actions.setVfsWidth(currentVfsWidth);
|
||||
userSettingsAutomat.actions.setChatDrawerWidth(currentChatWidth);
|
||||
companyAutomat.actions.setChatDrawerWidth(currentChatWidth);
|
||||
};
|
||||
|
||||
window.addEventListener('pointermove', onMove, { passive: false });
|
||||
@@ -531,7 +531,7 @@ export default class ChatDrawer extends PureComponent {
|
||||
if (isVfs) {
|
||||
vfsAutomat.actions.setVfsWidth(currentVfsWidth);
|
||||
} else {
|
||||
userSettingsAutomat.actions.setChatDrawerWidth(currentChatWidth);
|
||||
companyAutomat.actions.setChatDrawerWidth(currentChatWidth);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -546,7 +546,7 @@ export default class ChatDrawer extends PureComponent {
|
||||
|
||||
handleResizeDoubleClick = () => {
|
||||
this.setState({ drawerWidth: DEFAULT_DRAWER_WIDTH });
|
||||
userSettingsAutomat.actions.setChatDrawerWidth(DEFAULT_DRAWER_WIDTH);
|
||||
companyAutomat.actions.setChatDrawerWidth(DEFAULT_DRAWER_WIDTH);
|
||||
};
|
||||
|
||||
cleanupResizeListeners = () => {
|
||||
|
||||
@@ -30,7 +30,8 @@ import EditNoteIcon from '@mui/icons-material/EditNote';
|
||||
import PaidRoundedIcon from '@mui/icons-material/PaidRounded';
|
||||
import Button from '@mui/material/Button';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { deliveryEditorAutomat, setLastDeliveryPath } from '../../automats';
|
||||
import deliveryEditorAutomat from '../../automats/deliveryEditorAutomat';
|
||||
import { setLastDeliveryPath } from '../../automats/navigationAutomat';
|
||||
import HealingMarkdown from './HealingMarkdown';
|
||||
import {
|
||||
VfsAttachmentLink,
|
||||
|
||||
@@ -6,7 +6,7 @@ import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
||||
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
|
||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import customergroupAutomat from '../../automats/customergroupAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
|
||||
class KundeGruppe extends PureComponent {
|
||||
@@ -54,7 +54,7 @@ class KundeGruppe extends PureComponent {
|
||||
|
||||
async renameGroup(item, name) {
|
||||
try {
|
||||
await customergroupAutomat.actions.update(item.id, { name });
|
||||
await companyAutomat.actions.updateCustomergroup(item.id, { name });
|
||||
enqueueSnackbar('Kundengruppe gespeichert.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
this.setState({ draft: item.name ?? '' });
|
||||
@@ -87,7 +87,7 @@ class KundeGruppe extends PureComponent {
|
||||
if (!window.confirm(`Kundengruppe „${item.name}“ wirklich löschen?`)) return;
|
||||
void (async () => {
|
||||
try {
|
||||
await customergroupAutomat.actions.remove(item.id);
|
||||
await companyAutomat.actions.removeCustomergroup(item.id);
|
||||
enqueueSnackbar('Kundengruppe gelöscht.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
} from '../../utils/deliveryLedger';
|
||||
import { formatStock } from '../article/searchFormat';
|
||||
import artikelAutomat, { getArticle } from '../../automats/artikelAutomat';
|
||||
import warehouseAutomat from '../../automats/warehouseAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { supplier as supplierApi } from '../../api';
|
||||
import deliveryEditorAutomat, { buildSupplierArticlesMap } from '../../automats/deliveryEditorAutomat';
|
||||
import {
|
||||
@@ -65,7 +65,7 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
const initialWarehouses = warehouseAutomat.state.warehouses || [];
|
||||
const initialWarehouses = companyAutomat.state.warehouses || [];
|
||||
const initialSupplierMap =
|
||||
props.supplierArticlesMap ||
|
||||
(deliveryEditorAutomat?.state?.supplierArticlesMap ?? {});
|
||||
@@ -107,7 +107,7 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsubWarehouse = warehouseAutomat.subscribe((state) => {
|
||||
this.unsubWarehouse = companyAutomat.subscribe((state) => {
|
||||
const warehouses = state.warehouses || [];
|
||||
this.setState((prev) => {
|
||||
const selectedId =
|
||||
@@ -118,11 +118,11 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
selectedWarehouseId: selectedId,
|
||||
};
|
||||
});
|
||||
});
|
||||
}, ['warehouses']);
|
||||
this.unsubArticle = artikelAutomat.subscribe(() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
void warehouseAutomat.actions.load().catch(() => { });
|
||||
void companyAutomat.actions.loadWarehouses().catch(() => { });
|
||||
this.loadSupplierArticlesIfNeeded();
|
||||
|
||||
if (this.props.open) {
|
||||
@@ -205,7 +205,7 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.open && !prevProps.open) {
|
||||
const currentWarehouses = this.state.warehouses || warehouseAutomat.state.warehouses || [];
|
||||
const currentWarehouses = this.state.warehouses || companyAutomat.state.warehouses || [];
|
||||
const currentSupplierMap =
|
||||
this.props.supplierArticlesMap ||
|
||||
(deliveryEditorAutomat?.state?.supplierArticlesMap ?? this.state.supplierArticlesMap ?? {});
|
||||
@@ -225,7 +225,7 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
supplierArticlesMap: currentSupplierMap,
|
||||
},
|
||||
() => {
|
||||
void warehouseAutomat.actions.load().catch(() => { });
|
||||
void companyAutomat.actions.loadWarehouses().catch(() => { });
|
||||
this.loadSupplierArticlesIfNeeded();
|
||||
if (this.props.summary) {
|
||||
this.initAmountsFromSummary(this.props.summary, this.props.prefillLineId);
|
||||
|
||||
@@ -23,7 +23,7 @@ import FillSpace from 'dashgui/FillSpace';
|
||||
import { PanelTile, PanelTileGrid } from 'dashgui/panels';
|
||||
import BackendError from '../BackendError';
|
||||
import deliveryAutomat, { CHUNK_SIZE } from '../../automats/deliveryAutomat';
|
||||
import numberAutomat from '../../automats/numberAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { delivery as deliveryApi } from '../../api';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
@@ -693,11 +693,15 @@ export class DeliveryList extends PureComponent {
|
||||
supplierId: deliveryAutomat.state.supplierId ?? null,
|
||||
});
|
||||
}
|
||||
this.unsubNumber = numberAutomat.subscribe(this, () => {
|
||||
this.headerItem = createDummyTestDelivery();
|
||||
this.forceUpdate();
|
||||
});
|
||||
void numberAutomat.actions.ensureLoaded().catch(() => { });
|
||||
this.unsubNumber = companyAutomat.subscribe(
|
||||
this,
|
||||
() => {
|
||||
this.headerItem = createDummyTestDelivery();
|
||||
this.forceUpdate();
|
||||
},
|
||||
['numbers'],
|
||||
);
|
||||
void companyAutomat.actions.ensureNumbersLoaded().catch(() => { });
|
||||
void this.loadSuppliers();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,9 @@ import OrderDocumentsTiles from './OrderDocumentsTiles';
|
||||
import OrderShipmentDialog from './OrderShipmentDialog';
|
||||
import artikelAutomat, { getArticle } from '../../automats/artikelAutomat';
|
||||
import entityAutomat from '../../automats/entityAutomat';
|
||||
import customergroupAutomat from '../../automats/customergroupAutomat';
|
||||
import supplierAutomat from '../../automats/supplierAutomat';
|
||||
import orderAutomat from '../../automats/orderAutomat';
|
||||
import retoureAutomat from '../../automats/retoureAutomat';
|
||||
import companyAutomat, { selectCompanyTaxrate } from '../../automats/companyAutomat';
|
||||
import warehouseAutomat from '../../automats/warehouseAutomat';
|
||||
import sseAutomat from '../../automats/sseAutomat';
|
||||
import OrderLedgerPanel from './OrderLedgerPanel';
|
||||
import { fetchOrderLedgerEntries } from '../../utils/orderLedger';
|
||||
@@ -67,15 +64,15 @@ class ManifestForm extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.cfg = manifestConfig(props.kind);
|
||||
const groups = customergroupAutomat.state.items ?? [];
|
||||
const groups = companyAutomat.state.customergroups ?? [];
|
||||
const initialParties = props.kind === 'delivery'
|
||||
? (supplierAutomat.state.status === 'ready' ? (supplierAutomat.state.suppliers ?? []) : [])
|
||||
? (companyAutomat.state.suppliersStatus === 'ready' ? (companyAutomat.state.suppliers ?? []) : [])
|
||||
: (entityAutomat.state.status === 'ready'
|
||||
? filterNonSupplierEntities(entityAutomat.state.items ?? [], groups)
|
||||
: []);
|
||||
const partiesReady = props.kind === 'delivery'
|
||||
? supplierAutomat.state.status === 'ready'
|
||||
: (entityAutomat.state.status === 'ready' && customergroupAutomat.state.status === 'ready');
|
||||
? companyAutomat.state.suppliersStatus === 'ready'
|
||||
: (entityAutomat.state.status === 'ready' && companyAutomat.state.customergroupsStatus === 'ready');
|
||||
|
||||
this.draftAutomat = props.mode === 'create' ? getManifestDraftAutomat(props.kind) : null;
|
||||
this.editAutomat = props.mode === 'edit' ? getManifestEditAutomat(props.kind) : null;
|
||||
@@ -184,64 +181,69 @@ class ManifestForm extends PureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
this._cancelled = false;
|
||||
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 syncPartiesFromState = () => {
|
||||
if (this.props.kind === 'delivery') {
|
||||
this.setState({
|
||||
parties: companyAutomat.state.suppliersStatus === 'ready' ? (companyAutomat.state.suppliers ?? []) : [],
|
||||
partiesLoading: companyAutomat.state.suppliersStatus === 'loading',
|
||||
});
|
||||
} else {
|
||||
const entities = entityAutomat.state.items ?? [];
|
||||
const groups = customergroupAutomat.state.items ?? [];
|
||||
const groups = companyAutomat.state.customergroups ?? [];
|
||||
this.setState({
|
||||
parties: filterNonSupplierEntities(entities, groups),
|
||||
partiesLoading:
|
||||
entityAutomat.state.status === 'loading' ||
|
||||
customergroupAutomat.state.status === 'loading',
|
||||
companyAutomat.state.customergroupsStatus === 'loading',
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
if (this.props.kind !== 'delivery') {
|
||||
this.unsubParties = entityAutomat.subscribe(this, () => {
|
||||
updateParties();
|
||||
syncPartiesFromState();
|
||||
return null;
|
||||
});
|
||||
this.unsubGroups = customergroupAutomat.subscribe(this, () => {
|
||||
updateParties();
|
||||
return null;
|
||||
});
|
||||
void customergroupAutomat.actions.ensureLoaded();
|
||||
void companyAutomat.actions.ensureCustomergroupsLoaded();
|
||||
}
|
||||
|
||||
if (companyAutomat.state.status === 'idle') {
|
||||
void companyAutomat.actions.load();
|
||||
}
|
||||
this.unsubCompany = companyAutomat.subscribe(this, (state) => {
|
||||
const taxrate = state.current?.taxrate;
|
||||
if (taxrate != null) {
|
||||
this.setState((prev) => {
|
||||
if (
|
||||
prev.lines.length === 1 &&
|
||||
!prev.lines[0].sku &&
|
||||
!prev.lines[0].name &&
|
||||
!prev.lines[0].net &&
|
||||
prev.lines[0].tax !== String(taxrate)
|
||||
) {
|
||||
return {
|
||||
lines: [
|
||||
{
|
||||
...prev.lines[0],
|
||||
tax: String(taxrate),
|
||||
taxrate: String(taxrate),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
});
|
||||
this.unsubCompany = companyAutomat.subscribe(
|
||||
this,
|
||||
(state) => {
|
||||
syncPartiesFromState();
|
||||
const taxrate = state.current?.taxrate;
|
||||
if (taxrate != null) {
|
||||
this.setState((prev) => {
|
||||
if (
|
||||
prev.lines.length === 1 &&
|
||||
!prev.lines[0].sku &&
|
||||
!prev.lines[0].name &&
|
||||
!prev.lines[0].net &&
|
||||
prev.lines[0].tax !== String(taxrate)
|
||||
) {
|
||||
return {
|
||||
lines: [
|
||||
{
|
||||
...prev.lines[0],
|
||||
tax: String(taxrate),
|
||||
taxrate: String(taxrate),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
},
|
||||
['current', 'suppliers', 'suppliersStatus', 'customergroups', 'customergroupsStatus'],
|
||||
);
|
||||
|
||||
if (warehouseAutomat.state.status === 'idle') {
|
||||
void warehouseAutomat.actions.load();
|
||||
if (companyAutomat.state.warehousesStatus === 'idle') {
|
||||
void companyAutomat.actions.loadWarehouses();
|
||||
}
|
||||
|
||||
if (this.props.mode === 'edit') {
|
||||
@@ -304,32 +306,29 @@ class ManifestForm extends PureComponent {
|
||||
if (prevProps.kind !== this.props.kind) {
|
||||
this.cfg = manifestConfig(this.props.kind);
|
||||
this.unsubParties?.();
|
||||
this.unsubGroups?.();
|
||||
this.unsubParties = null;
|
||||
if (this.props.kind === 'delivery') {
|
||||
this.unsubParties = supplierAutomat.subscribe(this, (state) => ({
|
||||
parties: state.status === 'ready' ? (state.suppliers ?? []) : [],
|
||||
partiesLoading: state.status === 'loading',
|
||||
}));
|
||||
this.setState({
|
||||
parties: companyAutomat.state.suppliersStatus === 'ready' ? (companyAutomat.state.suppliers ?? []) : [],
|
||||
partiesLoading: companyAutomat.state.suppliersStatus === 'loading',
|
||||
});
|
||||
} else {
|
||||
const updateParties = () => {
|
||||
const entities = entityAutomat.state.items ?? [];
|
||||
const groups = customergroupAutomat.state.items ?? [];
|
||||
const groups = companyAutomat.state.customergroups ?? [];
|
||||
this.setState({
|
||||
parties: filterNonSupplierEntities(entities, groups),
|
||||
partiesLoading:
|
||||
entityAutomat.state.status === 'loading' ||
|
||||
customergroupAutomat.state.status === 'loading',
|
||||
companyAutomat.state.customergroupsStatus === 'loading',
|
||||
});
|
||||
};
|
||||
this.unsubParties = entityAutomat.subscribe(this, () => {
|
||||
updateParties();
|
||||
return null;
|
||||
});
|
||||
this.unsubGroups = customergroupAutomat.subscribe(this, () => {
|
||||
updateParties();
|
||||
return null;
|
||||
});
|
||||
void customergroupAutomat.actions.ensureLoaded();
|
||||
updateParties();
|
||||
void companyAutomat.actions.ensureCustomergroupsLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,9 +25,7 @@ import {
|
||||
} from './manifestConfig';
|
||||
import { notifyError, notifyErrorMessage } from '../../utils/notify';
|
||||
import entityAutomat from '../../automats/entityAutomat';
|
||||
import customergroupAutomat from '../../automats/customergroupAutomat';
|
||||
import supplierAutomat from '../../automats/supplierAutomat';
|
||||
import numberAutomat from '../../automats/numberAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import quoteAutomat from '../../automats/quoteAutomat';
|
||||
import orderAutomat from '../../automats/orderAutomat';
|
||||
import deliveryAutomat from '../../automats/deliveryAutomat';
|
||||
@@ -42,7 +40,7 @@ class ManifestHeader extends PureComponent {
|
||||
const selected =
|
||||
(props.parties || []).find((row) => row.id === props.partyId) ??
|
||||
(props.kind === 'delivery'
|
||||
? (supplierAutomat.state.suppliers || []).find((row) => row.id === props.partyId)
|
||||
? (companyAutomat.state.suppliers || []).find((row) => row.id === props.partyId)
|
||||
: (entityAutomat.state.items || []).find((row) => row.id === props.partyId)) ??
|
||||
null;
|
||||
this.state = {
|
||||
@@ -68,7 +66,7 @@ class ManifestHeader extends PureComponent {
|
||||
(this.props.parties || []).find((row) => row.id === this.props.partyId) ??
|
||||
(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)
|
||||
? (companyAutomat.state.suppliers || []).find((row) => row.id === this.props.partyId)
|
||||
: (entityAutomat.state.items || []).find((row) => row.id === this.props.partyId));
|
||||
if (selected) {
|
||||
this.setState({ partyInput: partyDisplayName(this.props.kind, selected) });
|
||||
@@ -147,14 +145,14 @@ class ManifestHeader extends PureComponent {
|
||||
try {
|
||||
const { kind, onPartyChange } = this.props;
|
||||
if (kind === 'delivery') {
|
||||
const row = await supplierAutomat.actions.create({ name: raw });
|
||||
const row = await companyAutomat.actions.createSupplier({ name: raw });
|
||||
const displayName = row?.name || raw;
|
||||
enqueueSnackbar(`Lieferant „${displayName}“ angelegt.`, { variant: 'success' });
|
||||
this.setState({ creatingParty: false, createdParty: row, partyInput: displayName });
|
||||
onPartyChange?.(row.id);
|
||||
} else {
|
||||
await customergroupAutomat.actions.ensureLoaded();
|
||||
const groups = customergroupAutomat.state.items ?? [];
|
||||
await companyAutomat.actions.ensureCustomergroupsLoaded();
|
||||
const groups = companyAutomat.state.customergroups ?? [];
|
||||
const targetGroup = getLowestSortNonSupplierCustomerGroup(groups) ?? groups[0];
|
||||
if (!targetGroup) {
|
||||
throw new Error('Keine Kundengruppe vorhanden.');
|
||||
@@ -257,11 +255,15 @@ class ManifestHeader extends PureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
this._cancelled = false;
|
||||
this.unsubNumber = numberAutomat.subscribe(this, () => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
this.unsubNumber = companyAutomat.subscribe(
|
||||
this,
|
||||
() => {
|
||||
this.forceUpdate();
|
||||
},
|
||||
['numbers'],
|
||||
);
|
||||
this.subscribeListAutomat(this.props.kind);
|
||||
void numberAutomat.actions.ensureLoaded().catch(() => {});
|
||||
void companyAutomat.actions.ensureNumbersLoaded().catch(() => {});
|
||||
if (!this.props.number && this.props.docId == null) {
|
||||
void ensureDocumentNumber(this.props.kind)
|
||||
.then(() => {
|
||||
|
||||
@@ -26,7 +26,7 @@ import orderAutomat, {
|
||||
isOrderPaid,
|
||||
isOrderDelivered,
|
||||
} from '../../automats/orderAutomat';
|
||||
import numberAutomat from '../../automats/numberAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { order as orderApi } from '../../api';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
@@ -595,11 +595,15 @@ export class OrderList extends PureComponent {
|
||||
filterKey: orderAutomat.getFilterKey(state),
|
||||
entityId: state.entityId ?? null,
|
||||
}));
|
||||
this.unsubNumber = numberAutomat.subscribe(this, () => {
|
||||
this.headerItem = createDummyTestOrder();
|
||||
this.forceUpdate();
|
||||
});
|
||||
void numberAutomat.actions.ensureLoaded().catch(() => {});
|
||||
this.unsubNumber = companyAutomat.subscribe(
|
||||
this,
|
||||
() => {
|
||||
this.headerItem = createDummyTestOrder();
|
||||
this.forceUpdate();
|
||||
},
|
||||
['numbers'],
|
||||
);
|
||||
void companyAutomat.actions.ensureNumbersLoaded().catch(() => {});
|
||||
void this.loadParties();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import FillSpace from 'dashgui/FillSpace';
|
||||
import { PanelTile, PanelTileGrid } from 'dashgui/panels';
|
||||
import BackendError from '../BackendError';
|
||||
import orderAutomat from '../../automats/orderAutomat';
|
||||
import accountAutomat from '../../automats/accountAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { payment as paymentApi } from '../../api';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
@@ -184,14 +184,18 @@ export class OrderPaymentList extends PureComponent {
|
||||
status: state.status,
|
||||
error: state.error ? backendErrorFrom(state.error) : null,
|
||||
}));
|
||||
this.unsubAccount = accountAutomat.subscribe(this, (state) => ({
|
||||
accountsById: Object.fromEntries((state.items || []).map((a) => [a.id, a.name])),
|
||||
}));
|
||||
this.unsubAccount = companyAutomat.subscribe(
|
||||
this,
|
||||
(state) => ({
|
||||
accountsById: Object.fromEntries((state.accounts || []).map((a) => [a.id, a.name])),
|
||||
}),
|
||||
['accounts'],
|
||||
);
|
||||
|
||||
if (orderAutomat.state.status === 'idle') {
|
||||
void orderAutomat.actions.load().catch(() => {});
|
||||
}
|
||||
void accountAutomat.actions.ensureLoaded().catch(() => {});
|
||||
void companyAutomat.actions.ensureAccountsLoaded().catch(() => {});
|
||||
void this.loadPartiesData();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { PanelTile, PanelTileGrid } from 'dashgui/panels';
|
||||
import BackendError from '../BackendError';
|
||||
import SkeleHashImage from '../settings/SkeleHashImage';
|
||||
import quoteAutomat, { CHUNK_SIZE } from '../../automats/quoteAutomat';
|
||||
import numberAutomat from '../../automats/numberAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { quote as quoteApi } from '../../api';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
@@ -593,11 +593,15 @@ export class QuoteList extends PureComponent {
|
||||
filterKey: quoteAutomat.getFilterKey(state),
|
||||
entityId: state.entityId ?? null,
|
||||
}));
|
||||
this.unsubNumber = numberAutomat.subscribe(this, () => {
|
||||
this.headerItem = createDummyTestQuote();
|
||||
this.forceUpdate();
|
||||
});
|
||||
void numberAutomat.actions.ensureLoaded().catch(() => { });
|
||||
this.unsubNumber = companyAutomat.subscribe(
|
||||
this,
|
||||
() => {
|
||||
this.headerItem = createDummyTestQuote();
|
||||
this.forceUpdate();
|
||||
},
|
||||
['numbers'],
|
||||
);
|
||||
void companyAutomat.actions.ensureNumbersLoaded().catch(() => { });
|
||||
void this.loadParties();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import retoureAutomat, {
|
||||
isRetourePaid,
|
||||
isRetoureShipped,
|
||||
} from '../../automats/retoureAutomat';
|
||||
import numberAutomat from '../../automats/numberAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { retoure as retoureApi } from '../../api';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
@@ -600,10 +600,14 @@ export class RetoureList extends PureComponent {
|
||||
filterKey: retoureAutomat.getFilterKey(state),
|
||||
entityId: state.entityId ?? null,
|
||||
}));
|
||||
this.unsubNumber = numberAutomat.subscribe(this, () => {
|
||||
this.headerItem = createDummyTestRetoure();
|
||||
this.forceUpdate();
|
||||
});
|
||||
this.unsubNumber = companyAutomat.subscribe(
|
||||
this,
|
||||
() => {
|
||||
this.headerItem = createDummyTestRetoure();
|
||||
this.forceUpdate();
|
||||
},
|
||||
['numbers'],
|
||||
);
|
||||
void this.loadParties();
|
||||
}
|
||||
|
||||
|
||||
@@ -32,14 +32,14 @@ import { notifyError } from '../../utils/notify';
|
||||
import { formatDate, formatDateTime } from './manifestConfig';
|
||||
import orderAutomat from '../../automats/orderAutomat';
|
||||
import shipmentAutomat from '../../automats/shipmentAutomat';
|
||||
import carrierAutomat from '../../automats/carrierAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { SURFACE_RAISED, WHITE } from '../../colors';
|
||||
|
||||
// MARK: <AddPackageDialog/>
|
||||
class AddPackageDialog extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const carriers = (carrierAutomat.state.carriers || []).filter((c) => !c.isReverse);
|
||||
const carriers = (companyAutomat.state.carriers || []).filter((c) => !c.isReverse);
|
||||
this.state = {
|
||||
carriers,
|
||||
carrierId: carriers[0]?.id ?? '',
|
||||
@@ -50,17 +50,21 @@ class AddPackageDialog extends PureComponent {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsub = carrierAutomat.subscribe(this, (state) => {
|
||||
const carriers = (state.carriers || []).filter((c) => !c.isReverse);
|
||||
const currentId = this.state.carrierId;
|
||||
const valid = carriers.some((c) => c.id === currentId);
|
||||
return {
|
||||
carriers,
|
||||
carrierId: valid ? currentId : (carriers[0]?.id ?? ''),
|
||||
};
|
||||
});
|
||||
if (carrierAutomat.state.status !== 'ready') {
|
||||
void carrierAutomat.actions.load().catch(() => { });
|
||||
this.unsub = companyAutomat.subscribe(
|
||||
this,
|
||||
(state) => {
|
||||
const carriers = (state.carriers || []).filter((c) => !c.isReverse);
|
||||
const currentId = this.state.carrierId;
|
||||
const valid = carriers.some((c) => c.id === currentId);
|
||||
return {
|
||||
carriers,
|
||||
carrierId: valid ? currentId : (carriers[0]?.id ?? ''),
|
||||
};
|
||||
},
|
||||
['carriers'],
|
||||
);
|
||||
if (companyAutomat.state.carriersStatus !== 'ready') {
|
||||
void companyAutomat.actions.loadCarriers().catch(() => { });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,7 +418,7 @@ export default class ShipmentDetailDialog extends PureComponent {
|
||||
|
||||
const shLines = Array.isArray(shipment.lines) ? shipment.lines : [];
|
||||
const shPackages = Array.isArray(shipment.shippings) ? shipment.shippings : [];
|
||||
const carriers = carrierAutomat.state.carriers || [];
|
||||
const carriers = companyAutomat.state.carriers || [];
|
||||
const isShipmentShipped = Boolean(
|
||||
shipment.state === 'delivered' ||
|
||||
shipment.state === 'shipped' ||
|
||||
|
||||
@@ -24,7 +24,7 @@ import BackendError from '../BackendError';
|
||||
import AppDialog from '../AppDialog';
|
||||
import shipmentAutomat, { CHUNK_SIZE } from '../../automats/shipmentAutomat';
|
||||
import orderAutomat from '../../automats/orderAutomat';
|
||||
import numberAutomat from '../../automats/numberAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { shipment as shipmentApi } from '../../api';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
@@ -949,11 +949,15 @@ export class ShipmentList extends PureComponent {
|
||||
orders: state.items || [],
|
||||
}));
|
||||
|
||||
this.unsubNumber = numberAutomat.subscribe(this, () => {
|
||||
this.headerItem = createDummyTestShipment();
|
||||
this.forceUpdate();
|
||||
});
|
||||
void numberAutomat.actions.ensureLoaded().catch(() => {});
|
||||
this.unsubNumber = companyAutomat.subscribe(
|
||||
this,
|
||||
() => {
|
||||
this.headerItem = createDummyTestShipment();
|
||||
this.forceUpdate();
|
||||
},
|
||||
['numbers'],
|
||||
);
|
||||
void companyAutomat.actions.ensureNumbersLoaded().catch(() => {});
|
||||
|
||||
void shipmentAutomat.actions.load().catch(() => {});
|
||||
void orderAutomat.actions.load().catch(() => {});
|
||||
|
||||
@@ -4,15 +4,13 @@ import {
|
||||
order as orderApi,
|
||||
retoure as retoureApi,
|
||||
} from '../../api';
|
||||
import supplierAutomat from '../../automats/supplierAutomat';
|
||||
import entityAutomat from '../../automats/entityAutomat';
|
||||
import customergroupAutomat from '../../automats/customergroupAutomat';
|
||||
import deliveryAutomat from '../../automats/deliveryAutomat';
|
||||
import quoteAutomat from '../../automats/quoteAutomat';
|
||||
import orderAutomat from '../../automats/orderAutomat';
|
||||
import retoureAutomat from '../../automats/retoureAutomat';
|
||||
import { getArticle } from '../../automats/artikelAutomat';
|
||||
import { selectCompanyTaxrate } from '../../automats/companyAutomat';
|
||||
import companyAutomat, { selectCompanyTaxrate } from '../../automats/companyAutomat';
|
||||
import { netFromGross } from '../../utils/price';
|
||||
import { ensureDocumentNumber, getNextDocumentNumber } from '../../utils/documentNumber';
|
||||
import {
|
||||
@@ -646,18 +644,18 @@ export function apiFor(cfgOrKind) {
|
||||
export async function loadParties(cfgOrKind) {
|
||||
const kind = typeof cfgOrKind === 'string' ? cfgOrKind : cfgOrKind?.kind;
|
||||
if (kind === 'delivery') {
|
||||
if (supplierAutomat.state.status === 'ready') {
|
||||
return sortSuppliers(supplierAutomat.state.suppliers);
|
||||
if (companyAutomat.state.suppliersStatus === 'ready') {
|
||||
return sortSuppliers(companyAutomat.state.suppliers);
|
||||
}
|
||||
const list = await supplierAutomat.actions.load();
|
||||
const list = await companyAutomat.actions.loadSuppliers();
|
||||
return sortSuppliers(list);
|
||||
}
|
||||
const [entities, groups] = await Promise.all([
|
||||
entityAutomat.actions.ensureLoaded(),
|
||||
customergroupAutomat.actions.ensureLoaded(),
|
||||
companyAutomat.actions.ensureCustomergroupsLoaded(),
|
||||
]);
|
||||
const entityList = entities ?? entityAutomat.state.items ?? [];
|
||||
const groupList = groups ?? customergroupAutomat.state.items ?? [];
|
||||
const groupList = groups ?? companyAutomat.state.customergroups ?? [];
|
||||
return filterNonSupplierEntities(entityList, groupList);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
||||
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
|
||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import attributeAutomat from '../../automats/attributeAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import {
|
||||
SELECT_BG,
|
||||
@@ -56,7 +56,7 @@ class ArtikelMerkmalAuswahl extends PureComponent {
|
||||
async renameAttribute(attribute, name) {
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await attributeAutomat.actions.updateAttribute(attribute.id, { name });
|
||||
await companyAutomat.actions.updateAttribute(attribute.id, { name });
|
||||
enqueueSnackbar('Attribut gespeichert.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
this.setState({ draft: attribute.name ?? '' });
|
||||
@@ -87,7 +87,7 @@ class ArtikelMerkmalAuswahl extends PureComponent {
|
||||
const nextExclusive = !attribute.exclusive;
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await attributeAutomat.actions.updateAttribute(attribute.id, { exclusive: nextExclusive });
|
||||
await companyAutomat.actions.updateAttribute(attribute.id, { exclusive: nextExclusive });
|
||||
enqueueSnackbar(
|
||||
`Attribut „${attribute.name}“ auf ${nextExclusive ? 'Einzelauswahl' : 'Mehrfachauswahl'} gestellt.`,
|
||||
{ variant: 'info' }
|
||||
@@ -111,7 +111,7 @@ class ArtikelMerkmalAuswahl extends PureComponent {
|
||||
this.setState({ busy: true });
|
||||
void (async () => {
|
||||
try {
|
||||
await attributeAutomat.actions.deleteAttribute(attribute.id);
|
||||
await companyAutomat.actions.deleteAttribute(attribute.id);
|
||||
enqueueSnackbar('Attribut gelöscht.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
|
||||
@@ -7,7 +7,7 @@ import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
||||
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
|
||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import propertyAutomat from '../../automats/propertyAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
|
||||
class ArtikelMerkmalText extends PureComponent {
|
||||
@@ -54,7 +54,7 @@ class ArtikelMerkmalText extends PureComponent {
|
||||
async renameProperty(property, name) {
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await propertyAutomat.actions.updateProperty(property.id, { name });
|
||||
await companyAutomat.actions.updateProperty(property.id, { name });
|
||||
enqueueSnackbar('Merkmal gespeichert.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
this.setState({ draft: property.name ?? '' });
|
||||
@@ -85,7 +85,7 @@ class ArtikelMerkmalText extends PureComponent {
|
||||
const nextExclusive = !item.exclusive;
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await propertyAutomat.actions.updateProperty(item.id, { exclusive: nextExclusive });
|
||||
await companyAutomat.actions.updateProperty(item.id, { exclusive: nextExclusive });
|
||||
enqueueSnackbar(
|
||||
`Merkmal „${item.name}“ auf ${nextExclusive ? 'Einzelauswahl' : 'Mehrfachauswahl'} gestellt.`,
|
||||
{ variant: 'info' }
|
||||
@@ -108,7 +108,7 @@ class ArtikelMerkmalText extends PureComponent {
|
||||
this.setState({ busy: true });
|
||||
void (async () => {
|
||||
try {
|
||||
await propertyAutomat.actions.deleteProperty(item.id);
|
||||
await companyAutomat.actions.deleteProperty(item.id);
|
||||
enqueueSnackbar('Merkmal gelöscht.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
|
||||
@@ -7,7 +7,7 @@ import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
||||
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
|
||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import attributeAutomat from '../../automats/attributeAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
|
||||
class ArtikelMerkmalWert extends PureComponent {
|
||||
@@ -54,7 +54,7 @@ class ArtikelMerkmalWert extends PureComponent {
|
||||
async renameValue(item, val) {
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await attributeAutomat.actions.updateValue(item.id, { value: val });
|
||||
await companyAutomat.actions.updateValue(item.id, { value: val });
|
||||
enqueueSnackbar('Wert gespeichert.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
this.setState({ draft: item.value ?? '' });
|
||||
@@ -88,7 +88,7 @@ class ArtikelMerkmalWert extends PureComponent {
|
||||
this.setState({ busy: true });
|
||||
void (async () => {
|
||||
try {
|
||||
await attributeAutomat.actions.deleteValue(item.id);
|
||||
await companyAutomat.actions.deleteValue(item.id);
|
||||
enqueueSnackbar('Wert gelöscht.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { PanelTile, PanelTileGrid } from 'dashgui/panels';
|
||||
import BackendError from '../BackendError';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import attributeAutomat, { selectAttributes } from '../../automats/attributeAutomat';
|
||||
import companyAutomat, { selectAttributes } from '../../automats/companyAutomat';
|
||||
import ArtikelMerkmalAuswahl from './ArtikelMerkmalAuswahl';
|
||||
import ArtikelMerkmalWert from './ArtikelMerkmalWert';
|
||||
import { BLUE_PANEL, PURPLE_PANEL } from '../../colors';
|
||||
@@ -30,8 +30,8 @@ class AuswahlMerkmaleTab extends PureComponent {
|
||||
const initialSelectedAttrId = initialAttributes[0]?.id ?? null;
|
||||
|
||||
this.state = {
|
||||
attrStatus: attributeAutomat.state.status === 'succeeded' ? 'ready' : 'loading',
|
||||
error: attributeAutomat.state.error || null,
|
||||
attrStatus: companyAutomat.state.attributesStatus === 'succeeded' ? 'ready' : 'loading',
|
||||
error: companyAutomat.state.attributesError || null,
|
||||
attributes: initialAttributes,
|
||||
selectedAttributeId: initialSelectedAttrId,
|
||||
|
||||
@@ -64,9 +64,9 @@ class AuswahlMerkmaleTab extends PureComponent {
|
||||
componentDidMount() {
|
||||
this._cancelled = false;
|
||||
|
||||
this.unsubAttr = attributeAutomat.subscribe((state) => {
|
||||
this.unsubAttr = companyAutomat.subscribe((state) => {
|
||||
if (this._cancelled) return;
|
||||
if (state.status === 'succeeded') {
|
||||
if (state.attributesStatus === 'succeeded') {
|
||||
let selectedId = this.state.selectedAttributeId;
|
||||
if (!selectedId || !state.attributes.some((a) => a.id === selectedId)) {
|
||||
selectedId = state.attributes[0]?.id ?? null;
|
||||
@@ -77,12 +77,12 @@ class AuswahlMerkmaleTab extends PureComponent {
|
||||
attributes: state.attributes,
|
||||
selectedAttributeId: selectedId,
|
||||
});
|
||||
} else if (state.status === 'failed') {
|
||||
this.setState({ attrStatus: 'failed', error: backendErrorFrom(state.error) });
|
||||
} else if (state.attributesStatus === 'failed') {
|
||||
this.setState({ attrStatus: 'failed', error: backendErrorFrom(state.attributesError) });
|
||||
}
|
||||
});
|
||||
}, ['attributes', 'attributesStatus', 'attributesError']);
|
||||
|
||||
if (attributeAutomat.state.status !== 'succeeded') {
|
||||
if (companyAutomat.state.attributesStatus !== 'succeeded') {
|
||||
void this.load();
|
||||
} else {
|
||||
this._initAttrSortable();
|
||||
@@ -216,21 +216,21 @@ class AuswahlMerkmaleTab extends PureComponent {
|
||||
|
||||
async _persistAttrOrder(ids) {
|
||||
try {
|
||||
await attributeAutomat.actions.reorderAttributes(ids);
|
||||
await companyAutomat.actions.reorderAttributes(ids);
|
||||
} catch (err) {
|
||||
if (this._cancelled) return;
|
||||
notifyError(err);
|
||||
void attributeAutomat.actions.load();
|
||||
void companyAutomat.actions.loadAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
async _persistValOrder(attributeId, ids) {
|
||||
try {
|
||||
await attributeAutomat.actions.reorderValues(attributeId, ids);
|
||||
await companyAutomat.actions.reorderValues(attributeId, ids);
|
||||
} catch (err) {
|
||||
if (this._cancelled) return;
|
||||
notifyError(err);
|
||||
void attributeAutomat.actions.load();
|
||||
void companyAutomat.actions.loadAttributes();
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
@@ -238,11 +238,11 @@ class AuswahlMerkmaleTab extends PureComponent {
|
||||
// #region Actions & Handlers
|
||||
async load() {
|
||||
this.setState({
|
||||
attrStatus: attributeAutomat.state.status === 'succeeded' ? 'ready' : 'loading',
|
||||
attrStatus: companyAutomat.state.attributesStatus === 'succeeded' ? 'ready' : 'loading',
|
||||
error: null,
|
||||
});
|
||||
try {
|
||||
await attributeAutomat.actions.load();
|
||||
await companyAutomat.actions.loadAttributes();
|
||||
} catch (err) {
|
||||
if (this._cancelled) return;
|
||||
this.setState({ error: backendErrorFrom(err) });
|
||||
@@ -263,7 +263,7 @@ class AuswahlMerkmaleTab extends PureComponent {
|
||||
|
||||
this.setState({ attrSubmitting: true });
|
||||
try {
|
||||
const row = await attributeAutomat.actions.createAttribute({
|
||||
const row = await companyAutomat.actions.createAttribute({
|
||||
name,
|
||||
exclusive: true,
|
||||
});
|
||||
@@ -288,7 +288,7 @@ class AuswahlMerkmaleTab extends PureComponent {
|
||||
|
||||
this.setState({ valSubmitting: true });
|
||||
try {
|
||||
await attributeAutomat.actions.createValue(selectedAttributeId, { value });
|
||||
await companyAutomat.actions.createValue(selectedAttributeId, { value });
|
||||
if (this._cancelled) return;
|
||||
this.setState({ valFilter: '', valSubmitting: false });
|
||||
enqueueSnackbar(`Wert „${value}“ angelegt.`, { variant: 'success' });
|
||||
@@ -311,7 +311,7 @@ class AuswahlMerkmaleTab extends PureComponent {
|
||||
|
||||
this.setState({ attrDeletingUnused: true });
|
||||
try {
|
||||
await attributeAutomat.actions.deleteAttributes(unused.map((a) => a.id));
|
||||
await companyAutomat.actions.deleteAttributes(unused.map((a) => a.id));
|
||||
enqueueSnackbar(`${unused.length} ungenutzte Auswahl-Merkmale gelöscht.`, { variant: 'success' });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
@@ -336,7 +336,7 @@ class AuswahlMerkmaleTab extends PureComponent {
|
||||
|
||||
this.setState({ valDeletingUnused: true });
|
||||
try {
|
||||
await attributeAutomat.actions.deleteValues(unused.map((v) => v.id));
|
||||
await companyAutomat.actions.deleteValues(unused.map((v) => v.id));
|
||||
enqueueSnackbar(`${unused.length} ungenutzte Werte gelöscht.`, { variant: 'success' });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { PanelTile, PanelTileGrid } from 'dashgui/panels';
|
||||
import BackendError from '../BackendError';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import propertyAutomat, { selectProperties } from '../../automats/propertyAutomat';
|
||||
import companyAutomat, { selectProperties } from '../../automats/companyAutomat';
|
||||
import ArtikelMerkmalText from './ArtikelMerkmalText';
|
||||
import { ORANGE_PANEL } from '../../colors';
|
||||
|
||||
@@ -27,8 +27,8 @@ class TextMerkmaleTab extends PureComponent {
|
||||
const initialProperties = selectProperties() || [];
|
||||
|
||||
this.state = {
|
||||
propStatus: propertyAutomat.state.status === 'succeeded' ? 'ready' : 'loading',
|
||||
error: propertyAutomat.state.error || null,
|
||||
propStatus: companyAutomat.state.propertiesStatus === 'succeeded' ? 'ready' : 'loading',
|
||||
error: companyAutomat.state.propertiesError || null,
|
||||
properties: initialProperties,
|
||||
|
||||
propFilter: '',
|
||||
@@ -52,20 +52,20 @@ class TextMerkmaleTab extends PureComponent {
|
||||
this._cancelled = false;
|
||||
console.log('TextMerkmaleTab mounted', this.props);
|
||||
|
||||
this.unsubProp = propertyAutomat.subscribe((state) => {
|
||||
this.unsubProp = companyAutomat.subscribe((state) => {
|
||||
if (this._cancelled) return;
|
||||
if (state.status === 'succeeded') {
|
||||
if (state.propertiesStatus === 'succeeded') {
|
||||
this.setState({
|
||||
propStatus: 'ready',
|
||||
error: null,
|
||||
properties: state.properties,
|
||||
});
|
||||
} else if (state.status === 'failed') {
|
||||
this.setState({ propStatus: 'failed', error: backendErrorFrom(state.error) });
|
||||
} else if (state.propertiesStatus === 'failed') {
|
||||
this.setState({ propStatus: 'failed', error: backendErrorFrom(state.propertiesError) });
|
||||
}
|
||||
});
|
||||
}, ['properties', 'propertiesStatus', 'propertiesError']);
|
||||
|
||||
if (propertyAutomat.state.status !== 'succeeded') {
|
||||
if (companyAutomat.state.propertiesStatus !== 'succeeded') {
|
||||
void this.load();
|
||||
} else {
|
||||
this._initPropSortable();
|
||||
@@ -139,11 +139,11 @@ class TextMerkmaleTab extends PureComponent {
|
||||
|
||||
async _persistPropOrder(ids) {
|
||||
try {
|
||||
await propertyAutomat.actions.reorderProperties(ids);
|
||||
await companyAutomat.actions.reorderProperties(ids);
|
||||
} catch (err) {
|
||||
if (this._cancelled) return;
|
||||
notifyError(err);
|
||||
void propertyAutomat.actions.load();
|
||||
void companyAutomat.actions.loadProperties();
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
@@ -151,11 +151,11 @@ class TextMerkmaleTab extends PureComponent {
|
||||
// #region Actions & Handlers
|
||||
async load() {
|
||||
this.setState({
|
||||
propStatus: propertyAutomat.state.status === 'succeeded' ? 'ready' : 'loading',
|
||||
propStatus: companyAutomat.state.propertiesStatus === 'succeeded' ? 'ready' : 'loading',
|
||||
error: null,
|
||||
});
|
||||
try {
|
||||
await propertyAutomat.actions.load();
|
||||
await companyAutomat.actions.loadProperties();
|
||||
} catch (err) {
|
||||
if (this._cancelled) return;
|
||||
this.setState({ error: backendErrorFrom(err) });
|
||||
@@ -172,7 +172,7 @@ class TextMerkmaleTab extends PureComponent {
|
||||
|
||||
this.setState({ propSubmitting: true });
|
||||
try {
|
||||
const row = await propertyAutomat.actions.createProperty({
|
||||
const row = await companyAutomat.actions.createProperty({
|
||||
name,
|
||||
exclusive: true,
|
||||
});
|
||||
@@ -198,7 +198,7 @@ class TextMerkmaleTab extends PureComponent {
|
||||
|
||||
this.setState({ propDeletingUnused: true });
|
||||
try {
|
||||
await propertyAutomat.actions.deleteProperties(unused.map((p) => p.id));
|
||||
await companyAutomat.actions.deleteProperties(unused.map((p) => p.id));
|
||||
enqueueSnackbar(`${unused.length} ungenutzte Text-Merkmale gelöscht.`, { variant: 'success' });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
|
||||
@@ -17,7 +17,7 @@ import { enqueueSnackbar } from 'notistack';
|
||||
import { company } from '../../api';
|
||||
import categoryAutomat from '../../automats/categoryAutomat';
|
||||
import articleIndexAutomat from '../../automats/articleIndexAutomat';
|
||||
import brandAutomat from '../../automats/brandAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { orderAutomat } from '../../automats/orderAutomat';
|
||||
import SkeleHashImage from './SkeleHashImage';
|
||||
|
||||
@@ -240,7 +240,7 @@ export default class ImportExportSettingsPanel extends Component {
|
||||
enqueueSnackbar('Import erfolgreich abgeschlossen.', { variant: 'success' });
|
||||
void categoryAutomat.actions.loadTree();
|
||||
void articleIndexAutomat.actions.reload?.();
|
||||
void brandAutomat.actions.load?.();
|
||||
void companyAutomat.actions.loadBrands?.();
|
||||
if (stats?.ordersImported) {
|
||||
void orderAutomat.actions.reload?.();
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import BackendError from '../BackendError';
|
||||
import { number as numberApi } from '../../api';
|
||||
import numberAutomat from '../../automats/numberAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { requireArray, requireDefined, requireObject } from '../../utils/apiShape';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
@@ -53,7 +53,7 @@ export default class NumberSettingsPanel extends Component {
|
||||
this.setState({ status: 'loading', error: null });
|
||||
try {
|
||||
const numbers = requireArray(
|
||||
await numberAutomat.actions.reload(),
|
||||
await companyAutomat.actions.reloadNumbers(),
|
||||
'numbers',
|
||||
);
|
||||
const drafts = Object.fromEntries(
|
||||
@@ -146,7 +146,7 @@ export default class NumberSettingsPanel extends Component {
|
||||
const updated = requireDefined(data.number, 'number');
|
||||
|
||||
const numbers = this.state.numbers.map((n) => (n.id === id ? { ...n, ...body } : n));
|
||||
await this.context.updateNumber();
|
||||
await companyAutomat.actions.reloadNumbers();
|
||||
this.setState({
|
||||
numbers,
|
||||
drafts: {
|
||||
|
||||
@@ -17,7 +17,7 @@ import PointOfSaleIcon from '@mui/icons-material/PointOfSale';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import BackendError from '../BackendError';
|
||||
import CategoryPickerDialog from '../article/CategoryPickerDialog';
|
||||
import posterminalAutomat from '../../automats/posterminalAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import categoryAutomat, { whenCategoryTreeReady } from '../../automats/categoryAutomat';
|
||||
import { categoryDisplayName } from '../../utils/categoryUtils';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
@@ -43,9 +43,9 @@ function formatDateTime(val) {
|
||||
export default class PosTerminalSettingsPanel extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const terminals = posterminalAutomat.state.terminals ?? [];
|
||||
const terminals = companyAutomat.state.terminals ?? [];
|
||||
this.state = {
|
||||
status: posterminalAutomat.state.status === 'succeeded' ? 'ready' : 'loading',
|
||||
status: companyAutomat.state.terminalsStatus === 'succeeded' ? 'ready' : 'loading',
|
||||
error: null,
|
||||
terminals,
|
||||
categories: categoryAutomat.state.categories,
|
||||
@@ -67,9 +67,9 @@ export default class PosTerminalSettingsPanel extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsub = posterminalAutomat.subscribe(this, (state) => ({
|
||||
this.unsub = companyAutomat.subscribe(this, (state) => ({
|
||||
terminals: Array.isArray(state.terminals) ? state.terminals : [],
|
||||
status: state.status === 'succeeded' ? 'ready' : state.status === 'failed' ? 'failed' : this.state.status,
|
||||
status: state.terminalsStatus === 'succeeded' ? 'ready' : state.terminalsStatus === 'failed' ? 'failed' : this.state.status,
|
||||
}));
|
||||
this.unsubCategory = categoryAutomat.subscribe(this, (state) => ({
|
||||
categories: state.categories,
|
||||
@@ -89,7 +89,7 @@ export default class PosTerminalSettingsPanel extends Component {
|
||||
async loadTerminals() {
|
||||
this.setState({ error: null });
|
||||
try {
|
||||
const terminals = await posterminalAutomat.actions.load();
|
||||
const terminals = await companyAutomat.actions.loadTerminals();
|
||||
this.setState({
|
||||
status: 'ready',
|
||||
terminals: Array.isArray(terminals) ? terminals : [],
|
||||
@@ -111,14 +111,14 @@ export default class PosTerminalSettingsPanel extends Component {
|
||||
|
||||
this.setState({ creatingTerminal: true });
|
||||
try {
|
||||
const created = await posterminalAutomat.actions.create({ name: trimmed });
|
||||
const created = await companyAutomat.actions.createTerminal({ name: trimmed });
|
||||
|
||||
if (enableAllForNew && created?.id != null) {
|
||||
await posterminalAutomat.actions.enableAll({ terminalId: created.id });
|
||||
await companyAutomat.actions.enableAllTerminalCategories({ terminalId: created.id });
|
||||
await whenCategoryTreeReady();
|
||||
}
|
||||
|
||||
await posterminalAutomat.actions.load();
|
||||
await companyAutomat.actions.loadTerminals();
|
||||
this.setState({ newTerminalName: '' });
|
||||
enqueueSnackbar('Terminal hinzugefügt.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
@@ -131,8 +131,8 @@ export default class PosTerminalSettingsPanel extends Component {
|
||||
async handleDeleteTerminal(terminal) {
|
||||
this.setState({ deletingId: terminal.id });
|
||||
try {
|
||||
await posterminalAutomat.actions.delete(terminal.id);
|
||||
await posterminalAutomat.actions.load();
|
||||
await companyAutomat.actions.deleteTerminal(terminal.id);
|
||||
await companyAutomat.actions.loadTerminals();
|
||||
enqueueSnackbar('Terminal gelöscht.', { variant: 'info' });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
@@ -144,7 +144,7 @@ export default class PosTerminalSettingsPanel extends Component {
|
||||
async handleSetRootCategory(terminal, categoryId) {
|
||||
this.setState({ patchingTerminalId: terminal.id });
|
||||
try {
|
||||
await posterminalAutomat.actions.update(terminal.id, {
|
||||
await companyAutomat.actions.updateTerminal(terminal.id, {
|
||||
rootCategoryId: categoryId != null ? Number(categoryId) : null,
|
||||
});
|
||||
enqueueSnackbar('Root-Kategorie aktualisiert.', { variant: 'success' });
|
||||
@@ -158,7 +158,7 @@ export default class PosTerminalSettingsPanel extends Component {
|
||||
async handleToggleSyncRootArticles(terminal, nextValue) {
|
||||
this.setState({ patchingTerminalId: terminal.id });
|
||||
try {
|
||||
await posterminalAutomat.actions.update(terminal.id, {
|
||||
await companyAutomat.actions.updateTerminal(terminal.id, {
|
||||
syncRootArticles: Boolean(nextValue),
|
||||
});
|
||||
enqueueSnackbar('Synchronisierungseinstellung gespeichert.', { variant: 'success' });
|
||||
|
||||
@@ -6,7 +6,7 @@ import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
||||
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
|
||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import warehouseAutomat from '../../automats/warehouseAutomat';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
|
||||
class ArtikelLager extends PureComponent {
|
||||
@@ -51,7 +51,7 @@ class ArtikelLager extends PureComponent {
|
||||
|
||||
async renameWarehouse(item, name) {
|
||||
try {
|
||||
await warehouseAutomat.actions.update(item.id, { name });
|
||||
await companyAutomat.actions.updateWarehouse(item.id, { name });
|
||||
enqueueSnackbar('Lager gespeichert.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
this.setState({ draft: item.name ?? '' });
|
||||
@@ -78,7 +78,7 @@ class ArtikelLager extends PureComponent {
|
||||
if (!window.confirm(`Lager „${item.name}" wirklich löschen?`)) return;
|
||||
void (async () => {
|
||||
try {
|
||||
await warehouseAutomat.actions.remove(item.id);
|
||||
await companyAutomat.actions.removeWarehouse(item.id);
|
||||
enqueueSnackbar('Lager gelöscht.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
|
||||
Reference in New Issue
Block a user