u
This commit is contained in:
@@ -1,19 +1,16 @@
|
||||
import { Component } from 'react';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { notifyError } from '../utils/notify';
|
||||
import { StorageManagerContext } from '../api/storageManagerContext';
|
||||
import authAutomat from '../automats/authAutomat';
|
||||
import authAutomat, { bootstrapAuthMe } from '../automats/authAutomat';
|
||||
|
||||
let sessionBootstrapped = false;
|
||||
|
||||
class AuthBootstrap extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
componentDidMount() {
|
||||
if (sessionBootstrapped) return;
|
||||
sessionBootstrapped = true;
|
||||
|
||||
this.context.authMePromise
|
||||
bootstrapAuthMe()
|
||||
.then((session) => {
|
||||
if (session.forbidden) {
|
||||
enqueueSnackbar(session.message, { variant: 'warning' });
|
||||
|
||||
@@ -25,15 +25,14 @@ import PointOfSaleIcon from '@mui/icons-material/PointOfSale';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import BackendError from './BackendError';
|
||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||
import { StorageManagerContext } from '../api/storageManagerContext';
|
||||
import { requireDefined, requireObject } from '../utils/apiShape';
|
||||
import companyAutomat from '../automats/companyAutomat';
|
||||
import posterminalAutomat from '../automats/posterminalAutomat';
|
||||
import { whenCategoryTreeReady } from '../automats/categoryAutomat';
|
||||
import { ISO639_OPTIONS, parseTaxRate } from '../utils/companyForm';
|
||||
import { backendErrorFrom, isBackendFailure } from '../utils/backendError';
|
||||
import { notifyError } from '../utils/notify';
|
||||
|
||||
export default class CompanySettingsDialog extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -84,13 +83,7 @@ export default class CompanySettingsDialog extends Component {
|
||||
|
||||
async loadCompany() {
|
||||
try {
|
||||
const session = await this.context.authMePromise;
|
||||
const companyId = requireDefined(session.user?.selectedCompanyId, 'selectedCompanyId');
|
||||
const companyData = await this.context.companyPromise;
|
||||
const companyRow = requireDefined(
|
||||
Array.isArray(companyData) ? companyData.find((c) => c.id === companyId) : companyData,
|
||||
'company',
|
||||
);
|
||||
const companyRow = await companyAutomat.actions.load();
|
||||
this.syncFormFromCompany(companyRow);
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
@@ -99,7 +92,7 @@ export default class CompanySettingsDialog extends Component {
|
||||
|
||||
async loadTerminals() {
|
||||
try {
|
||||
const terminals = await this.context.posterminalPromise;
|
||||
const terminals = await posterminalAutomat.actions.load();
|
||||
this.setState({ terminals: Array.isArray(terminals) ? terminals : [] });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
@@ -112,17 +105,17 @@ export default class CompanySettingsDialog extends Component {
|
||||
|
||||
this.setState({ creatingTerminal: true });
|
||||
try {
|
||||
const data = await this.context.posterminal.upsert({ name: trimmed });
|
||||
requireObject(data, 'PUT /api/posterminal');
|
||||
requireObject(data.posterminal, 'posterminal');
|
||||
requireDefined(data.posterminal.id, 'posterminal.id');
|
||||
await posterminalAutomat.actions.create({ name: trimmed });
|
||||
|
||||
if (enableAllForNew) {
|
||||
await this.context.posterminal.setOnAll(data.posterminal.id);
|
||||
await this.context.updateCategoryTree();
|
||||
const created = posterminalAutomat.state.terminals.at(-1);
|
||||
if (created?.id != null) {
|
||||
await posterminalAutomat.actions.enableAll({ terminalId: created.id });
|
||||
await whenCategoryTreeReady();
|
||||
}
|
||||
}
|
||||
|
||||
const terminals = await this.context.updatePosterminal();
|
||||
const terminals = await posterminalAutomat.actions.load();
|
||||
this.setState({ newTerminalName: '', terminals: Array.isArray(terminals) ? terminals : [] });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
@@ -133,8 +126,8 @@ export default class CompanySettingsDialog extends Component {
|
||||
|
||||
async handleDeleteTerminal(terminal) {
|
||||
try {
|
||||
await this.context.posterminal.delete(terminal.id);
|
||||
const terminals = await this.context.updatePosterminal();
|
||||
await posterminalAutomat.actions.delete(terminal.id);
|
||||
const terminals = await posterminalAutomat.actions.load();
|
||||
this.setState({ terminals: Array.isArray(terminals) ? terminals : [] });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
@@ -160,15 +153,12 @@ export default class CompanySettingsDialog extends Component {
|
||||
|
||||
this.setState({ busy: true, error: null });
|
||||
try {
|
||||
const session = await this.context.authMePromise;
|
||||
const companyId = requireDefined(session.user?.selectedCompanyId, 'selectedCompanyId');
|
||||
await this.context.company.update(companyId, {
|
||||
await companyAutomat.actions.update({
|
||||
name: name.trim(),
|
||||
iso639,
|
||||
taxrate: parsedTaxrate,
|
||||
reducedtaxrate: parsedReducedtaxrate,
|
||||
});
|
||||
await this.context.updateCompany();
|
||||
enqueueSnackbar('Unternehmen gespeichert.', { variant: 'success' });
|
||||
onClose();
|
||||
} catch (err) {
|
||||
|
||||
@@ -34,8 +34,9 @@ import TuneIcon from '@mui/icons-material/Tune';
|
||||
import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
|
||||
import BackendError from './BackendError';
|
||||
import { company, number as numberApi } from '../api';
|
||||
import { StorageManagerContext } from '../api/storageManagerContext';
|
||||
import authAutomat from '../automats/authAutomat';
|
||||
import authAutomat, { bootstrapAuthMe } from '../automats/authAutomat';
|
||||
import companyAutomat from '../automats/companyAutomat';
|
||||
import numberAutomat from '../automats/numberAutomat';
|
||||
import {
|
||||
defaultIso639,
|
||||
ISO639_OPTIONS,
|
||||
@@ -105,8 +106,6 @@ function formatNumberPreview(prefix, padding, next, offset = 0) {
|
||||
}
|
||||
|
||||
class CompanySetupDialog extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -149,7 +148,7 @@ class CompanySetupDialog extends Component {
|
||||
this.setState({ busy: true, error: null });
|
||||
try {
|
||||
await authAutomat.actions.selectCompany(companyId);
|
||||
await this.context.updateAuthMe();
|
||||
await bootstrapAuthMe();
|
||||
} catch (err) {
|
||||
this.setState({ error: backendErrorFrom(err) });
|
||||
} finally {
|
||||
@@ -223,9 +222,9 @@ class CompanySetupDialog extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
await this.context.updateAuthMe();
|
||||
await this.context.updateCompany();
|
||||
await this.context.updateNumber?.();
|
||||
await bootstrapAuthMe();
|
||||
await companyAutomat.actions.load();
|
||||
await numberAutomat.actions.reload().catch(() => {});
|
||||
} catch (err) {
|
||||
this.setState({ error: backendErrorFrom(err) });
|
||||
} finally {
|
||||
@@ -236,7 +235,7 @@ class CompanySetupDialog extends Component {
|
||||
async handleLogout() {
|
||||
try {
|
||||
await authAutomat.actions.logout();
|
||||
await this.context.updateAuthMe();
|
||||
await bootstrapAuthMe();
|
||||
} catch (err) {
|
||||
this.setState({ error: backendErrorFrom(err) });
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ import Typography from '@mui/material/Typography';
|
||||
import GoogleIcon from '@mui/icons-material/Google';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { notifyErrorMessage } from '../utils/notify';
|
||||
import authAutomat from '../automats/authAutomat';
|
||||
import { StorageManagerContext } from '../api/storageManagerContext';
|
||||
import authAutomat, { bootstrapAuthMe } from '../automats/authAutomat';
|
||||
import {
|
||||
abortGoogleSignIn,
|
||||
FEDCM_BLOCKED_MESSAGE,
|
||||
@@ -20,8 +19,6 @@ import {
|
||||
import quixposLogo from '../assets/quixpos-logo.png';
|
||||
|
||||
class LoginPage extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { ...authAutomat.state, busy: false };
|
||||
@@ -50,7 +47,7 @@ class LoginPage extends PureComponent {
|
||||
});
|
||||
this.setState({ busy: true });
|
||||
await authAutomat.actions.loginWithGoogle(idToken);
|
||||
await this.context.updateAuthMe();
|
||||
await bootstrapAuthMe();
|
||||
} catch (err) {
|
||||
if (err instanceof GoogleSignInCancelledError) return;
|
||||
logError('[login] Google sign-in failed', {
|
||||
|
||||
@@ -12,16 +12,13 @@ import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import { StorageManagerContext } from '../api/storageManagerContext';
|
||||
import authAutomat from '../automats/authAutomat';
|
||||
import authAutomat, { bootstrapAuthMe } from '../automats/authAutomat';
|
||||
import { helpAutomat, toggleHelp } from '../automats';
|
||||
import CompanySettingsDialog from './CompanySettingsDialog';
|
||||
import SettingsDialog from './settings/SettingsDialog';
|
||||
import { notifyError } from '../utils/notify';
|
||||
|
||||
class UserMenu extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -87,7 +84,7 @@ class UserMenu extends PureComponent {
|
||||
this.setState({ anchorEl: null });
|
||||
try {
|
||||
await authAutomat.actions.logout();
|
||||
await this.context.updateAuthMe();
|
||||
await bootstrapAuthMe();
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@ import BrokenImageIcon from '@mui/icons-material/BrokenImage';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { serper } from '../../api';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { serper, article as articleApi } from '../../api';
|
||||
import artikelAutomat from '../../automats/artikelAutomat';
|
||||
import { articleWithUploadedPhoto } from '../../utils/articleDetail';
|
||||
import { prepareArticlePhoto } from '../../utils/prepareArticlePhoto';
|
||||
@@ -49,8 +48,6 @@ function computeDefaultQuery(article) {
|
||||
* Dialog that searches Google Images via Serper.dev SSE stream for article photos.
|
||||
*/
|
||||
export default class ArticleImageSearchDialog extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -236,7 +233,7 @@ export default class ArticleImageSearchDialog extends PureComponent {
|
||||
const file = avifBlob instanceof File
|
||||
? avifBlob
|
||||
: new File([avifBlob], 'article-photo.webp', { type: avifBlob.type || 'image/webp' });
|
||||
const res = await this.context.article.addImage(articleData.sku, file);
|
||||
const res = await articleApi.uploadImage(articleData.sku, file);
|
||||
requireObject(res, 'POST /api/article/:sku/image');
|
||||
requireObject(res.image, 'image');
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Component } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { thumb } from '../../api';
|
||||
import ThumbHashImage from './ThumbHashImage';
|
||||
|
||||
class ArticlePhoto extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { thumbLoaded: false };
|
||||
@@ -80,7 +78,7 @@ class ArticlePhoto extends Component {
|
||||
{fullHash ? (
|
||||
<Box
|
||||
component="img"
|
||||
src={this.context.thumb.url(fullHash)}
|
||||
src={thumb.url(fullHash)}
|
||||
alt=""
|
||||
onLoad={this.handleThumbLoad}
|
||||
onError={this.handleThumbError}
|
||||
|
||||
@@ -14,7 +14,7 @@ import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { article as articleApi } from '../../api';
|
||||
import artikelAutomat from '../../automats/artikelAutomat';
|
||||
import { requireArray, requireObject } from '../../utils/apiShape';
|
||||
import {
|
||||
@@ -46,8 +46,6 @@ const overlayIconSx = {
|
||||
};
|
||||
|
||||
class ArticlePhotosDialog extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.fileInputRef = createRef();
|
||||
@@ -72,7 +70,7 @@ class ArticlePhotosDialog extends Component {
|
||||
try {
|
||||
const file = await prepareArticlePhoto(rawBlob);
|
||||
const uploadStarted = performance.now();
|
||||
const res = await this.context.article.addImage(articleData.sku, file);
|
||||
const res = await articleApi.uploadImage(articleData.sku, file);
|
||||
console.log('[article-photo] upload', {
|
||||
uploadMs: Math.round(performance.now() - uploadStarted),
|
||||
bytes: file.size,
|
||||
@@ -130,7 +128,7 @@ class ArticlePhotosDialog extends Component {
|
||||
const { article: articleData, onArticleUpdated } = this.props;
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await this.context.article.deleteImage(articleData.sku, fullHash);
|
||||
await articleApi.deleteImage(articleData.sku, fullHash);
|
||||
const updated = articleWithoutPhoto(articleData, fullHash);
|
||||
artikelAutomat.actions.setArticle(updated);
|
||||
onArticleUpdated?.(updated);
|
||||
@@ -155,7 +153,7 @@ class ArticlePhotosDialog extends Component {
|
||||
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
const res = await this.context.article.orderImages(articleData.sku, { fullHashes });
|
||||
const res = await articleApi.orderImages(articleData.sku, fullHashes);
|
||||
requireObject(res, 'PUT /api/article/:sku/images/order');
|
||||
requireArray(res.fullHashes, 'fullHashes');
|
||||
const updated = articleWithPhotoOrder(articleData, res.fullHashes);
|
||||
|
||||
@@ -27,8 +27,7 @@ import TrendingDownIcon from '@mui/icons-material/TrendingDown';
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { stock, article as articleApi } from '../../api';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { stock } from '../../api';
|
||||
import { formatStock } from './searchFormat';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import artikelAutomat from '../../automats/artikelAutomat';
|
||||
@@ -43,8 +42,6 @@ const REASON_PRESETS = [
|
||||
];
|
||||
|
||||
export default class ArticleStockAdjustDialog extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const initialWhId = this.resolveInitialWarehouseId(props);
|
||||
@@ -214,9 +211,9 @@ export default class ArticleStockAdjustDialog extends PureComponent {
|
||||
|
||||
// 1. Update track_stock if changed
|
||||
if (trackStockChanged) {
|
||||
const store = this.context;
|
||||
const setFn = store?.article?.setTrackStock ?? articleApi.setTrackStock;
|
||||
const trackRes = await setFn(article.sku, { track_stock: trackStock });
|
||||
const trackRes = await artikelAutomat.actions.setTrackStock(article.sku, {
|
||||
track_stock: trackStock,
|
||||
});
|
||||
updatedArticle = trackRes?.article || null;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import TextInput from '../TextInput';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import unitAutomat from '../../automats/unitAutomat';
|
||||
import { unit as unitApi } from '../../api';
|
||||
import { requireObject } from '../../utils/apiShape';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import { parsePriceInput } from '../../utils/price';
|
||||
@@ -63,8 +64,6 @@ function formValid(form) {
|
||||
}
|
||||
|
||||
export default class ArticleUnitEditorDialog extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -86,7 +85,7 @@ export default class ArticleUnitEditorDialog extends Component {
|
||||
async loadUnits() {
|
||||
this.setState({ unitsLoading: true });
|
||||
try {
|
||||
const units = await this.context.unitPromise;
|
||||
const units = await unitAutomat.actions.load();
|
||||
this.setState({ units, unitsLoading: false });
|
||||
} catch (err) {
|
||||
this.setState({ unitsLoading: false });
|
||||
@@ -117,7 +116,7 @@ export default class ArticleUnitEditorDialog extends Component {
|
||||
if (!formValid(form)) return;
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
const data = await this.context.unit.upsert({
|
||||
const data = await unitApi.upsert({
|
||||
si: String(form.si ?? '').trim(),
|
||||
basesi: String(form.basesi ?? '').trim(),
|
||||
basesifactor: parseFactor(form.basesifactor),
|
||||
@@ -126,7 +125,7 @@ export default class ArticleUnitEditorDialog extends Component {
|
||||
});
|
||||
requireObject(data, 'PUT /api/unit');
|
||||
requireObject(data.unit, 'unit');
|
||||
const units = await this.context.updateUnit();
|
||||
const units = await unitAutomat.actions.load();
|
||||
this.setState({
|
||||
busy: false,
|
||||
units,
|
||||
@@ -145,8 +144,8 @@ export default class ArticleUnitEditorDialog extends Component {
|
||||
if (!selectedSi || selectedSi === DEFAULT_UNIT_SI) return;
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await this.context.unit.delete(selectedSi);
|
||||
const units = await this.context.updateUnit();
|
||||
await unitApi.delete(selectedSi);
|
||||
const units = await unitAutomat.actions.load();
|
||||
this.setState({ busy: false, units, form: emptyForm(), selectedSi: null });
|
||||
this.props.onUnitDeleted?.(selectedSi);
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { PanelTileGrid } from 'dashgui/panels';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { getCategoryState } from '../../automats/categoryAutomat';
|
||||
import withNavigate from '../withNavigate';
|
||||
import {
|
||||
buildCategoryPath,
|
||||
@@ -21,8 +21,6 @@ import ArticleViewLists from './boxes/ArticleViewLists';
|
||||
const PHOTO_TILE_SX = { minHeight: 0 };
|
||||
|
||||
class ArticleView extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
@@ -70,7 +68,7 @@ class ArticleView extends PureComponent {
|
||||
handleArticleDeleted() {
|
||||
const { navigate } = this.props;
|
||||
const { article } = this.state;
|
||||
const categoryState = this.context.getCategoryState();
|
||||
const categoryState = getCategoryState();
|
||||
const categories = categoryState?.categories ?? null;
|
||||
const rootCategoryId = categoryState?.rootCategoryId ?? null;
|
||||
const categoryId = article?.categoryIds?.[0];
|
||||
@@ -83,7 +81,7 @@ class ArticleView extends PureComponent {
|
||||
|
||||
render() {
|
||||
const { article } = this.state;
|
||||
const categoryState = this.context.getCategoryState();
|
||||
const categoryState = getCategoryState();
|
||||
const categories = categoryState?.categories ?? null;
|
||||
const rootCategoryId = categoryState?.rootCategoryId ?? null;
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import TextInput from '../../TextInput';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Box from '@mui/material/Box';
|
||||
import { EditablePanelTile } from 'dashgui/panels';
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import { requireDefined, requireObject } from '../../../utils/apiShape';
|
||||
import brandAutomat from '../../../automats/brandAutomat';
|
||||
import artikelAutomat from '../../../automats/artikelAutomat';
|
||||
import { requireObject } from '../../../utils/apiShape';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
import InputAdornment from '@mui/material/InputAdornment';
|
||||
import { BrandLabel, BrandThumb } from '../../brand';
|
||||
@@ -111,37 +112,29 @@ class BrandEdit extends Component {
|
||||
}
|
||||
|
||||
export default class ArticleViewBrand extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const brands = this.context?.brands;
|
||||
this.state = {
|
||||
brands: brands ?? [],
|
||||
brands: brandAutomat.state.brands ?? [],
|
||||
};
|
||||
this._cancelled = false;
|
||||
this._brandsReady = brands != null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._cancelled = false;
|
||||
if (this._brandsReady) return;
|
||||
this.context.brandPromise
|
||||
.then((list) => {
|
||||
if (!this._cancelled) this.setState({ brands: list });
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!this._cancelled) notifyError(err);
|
||||
});
|
||||
this.unsub = brandAutomat.subscribe(this, (state) => ({
|
||||
brands: state.brands ?? [],
|
||||
}));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._cancelled = true;
|
||||
this.unsub?.();
|
||||
this.unsub = null;
|
||||
}
|
||||
|
||||
handleSave = async (draft) => {
|
||||
const { article: articleData, onArticleUpdated } = this.props;
|
||||
const store = this.context;
|
||||
const { brands } = this.state;
|
||||
const name = String(draft.name ?? '').trim();
|
||||
if (!name) {
|
||||
@@ -156,16 +149,16 @@ export default class ArticleViewBrand extends PureComponent {
|
||||
if (existing) {
|
||||
brandId = existing.id;
|
||||
} else {
|
||||
const createdRes = await store.brand.upsert({ name });
|
||||
requireObject(createdRes, 'PUT /api/brand');
|
||||
requireObject(createdRes.brand, 'brand');
|
||||
requireDefined(createdRes.brand.id, 'brand.id');
|
||||
brandId = createdRes.brand.id;
|
||||
const nextBrands = await store.updateBrand();
|
||||
await brandAutomat.actions.upsert({ name });
|
||||
const nextBrands = await brandAutomat.actions.load();
|
||||
this.setState({ brands: nextBrands });
|
||||
brandId = nextBrands.find(
|
||||
(row) => String(row.name ?? '').toLowerCase() === name.toLowerCase(),
|
||||
)?.id;
|
||||
if (brandId == null) throw new Error('Marke konnte nicht gespeichert werden.');
|
||||
}
|
||||
|
||||
const res = await store.article.setBrand(articleData.sku, { brandId });
|
||||
const res = await artikelAutomat.actions.setBrand(articleData.sku, { brandId });
|
||||
requireObject(res, 'PUT /api/article/:sku/brand');
|
||||
requireObject(res.article, 'article');
|
||||
onArticleUpdated?.(res.article);
|
||||
|
||||
@@ -4,7 +4,7 @@ import TextInput from '../../TextInput';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Box from '@mui/material/Box';
|
||||
import { EditablePanelTile } from 'dashgui/panels';
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import artikelAutomat from '../../../automats/artikelAutomat';
|
||||
import { requireObject } from '../../../utils/apiShape';
|
||||
import { articleEans } from '../../../utils/articleDetail';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
@@ -131,14 +131,12 @@ class EansEdit extends Component {
|
||||
}
|
||||
|
||||
export default class ArticleViewEANs extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
handleSave = async (draft) => {
|
||||
const { article: articleData, onArticleUpdated } = this.props;
|
||||
const eans = (draft.eans ?? [])
|
||||
.map((ean) => String(ean).trim())
|
||||
.filter((ean) => ean.length > 0);
|
||||
const res = await this.context.article.setEans(articleData.sku, { eans });
|
||||
const res = await artikelAutomat.actions.setEans(articleData.sku, eans);
|
||||
requireObject(res, 'PUT /api/article/:sku/eans');
|
||||
requireObject(res.article, 'article');
|
||||
onArticleUpdated?.(res.article);
|
||||
|
||||
@@ -35,11 +35,9 @@ import { article as articleApi } from '../../../api';
|
||||
import attributeAutomat, { selectAttributes } from '../../../automats/attributeAutomat';
|
||||
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import articleIndexAutomat from '../../../automats/articleIndexAutomat';
|
||||
|
||||
export default class ArticleViewLists extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const initialAttributes = selectAttributes() || [];
|
||||
@@ -167,13 +165,12 @@ export default class ArticleViewLists extends PureComponent {
|
||||
}
|
||||
|
||||
getApi() {
|
||||
return this.context?.article || articleApi;
|
||||
return articleApi;
|
||||
}
|
||||
|
||||
async loadAllArticles() {
|
||||
try {
|
||||
const data = await this.context?.articleIndexPromise;
|
||||
const articles = Array.isArray(data) ? data : (data?.articles || []);
|
||||
const articles = await articleIndexAutomat.actions.ensureLoaded();
|
||||
if (!this._cancelled) {
|
||||
this.setState({ allArticles: articles });
|
||||
}
|
||||
@@ -1316,7 +1313,7 @@ export default class ArticleViewLists extends PureComponent {
|
||||
}
|
||||
|
||||
renderStandardView(selectableAttributes, assignedAttributeIds) {
|
||||
const { busy, activeCreatorMode, selectedAttributeId, attributes } = this.state;
|
||||
const { busy, activeCreatorMode, selectedAttributeId } = this.state;
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
{activeCreatorMode === 'father' ? (
|
||||
@@ -1422,7 +1419,6 @@ export default class ArticleViewLists extends PureComponent {
|
||||
allArticles,
|
||||
busy,
|
||||
activeCreatorMode,
|
||||
activeListTab,
|
||||
selectedChildValueId,
|
||||
newChildText,
|
||||
isCustomChildValue,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Component, PureComponent } from 'react';
|
||||
import { EditablePanelTile } from 'dashgui/panels';
|
||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import { getCategoryState } from '../../../automats/categoryAutomat';
|
||||
import artikelAutomat from '../../../automats/artikelAutomat';
|
||||
import { getCategoryBreadcrumbs, categoryDisplayName } from '../../../utils/categoryUtils';
|
||||
import { Box, Chip, IconButton } from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
@@ -88,8 +89,6 @@ function categoryLabels(categoryIds, categories, rootCategoryId) {
|
||||
}
|
||||
|
||||
class CategoryPills extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { pickerOpen: false, targetPillId: null };
|
||||
@@ -133,7 +132,7 @@ class CategoryPills extends Component {
|
||||
render() {
|
||||
const { categoryIds, editMode } = this.props;
|
||||
const { pickerOpen, targetPillId } = this.state;
|
||||
const { categories, rootCategoryId } = this.context.getCategoryState() ?? {};
|
||||
const { categories, rootCategoryId } = getCategoryState() ?? {};
|
||||
const labels = categoryLabels(categoryIds, categories, rootCategoryId);
|
||||
|
||||
const targetNode = categories && targetPillId != null ? categories[String(targetPillId)] : null;
|
||||
@@ -273,8 +272,6 @@ class NameEdit extends Component {
|
||||
}
|
||||
|
||||
export default class ArticleViewName extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -294,7 +291,6 @@ export default class ArticleViewName extends PureComponent {
|
||||
handleSave = async () => {
|
||||
const { article: articleData, onArticleUpdated } = this.props;
|
||||
const { nameDraft, categoryIdsDraft } = this.state;
|
||||
const store = this.context;
|
||||
const name = nameDraft.trim();
|
||||
const categoryIds = categoryIdsDraft;
|
||||
const nameChanged = name !== String(articleData.name ?? '').trim();
|
||||
@@ -303,14 +299,12 @@ export default class ArticleViewName extends PureComponent {
|
||||
let article = null;
|
||||
|
||||
if (nameChanged) {
|
||||
console.log('storing name update', name, articleData.name);
|
||||
const nameRes = await store.article.update(articleData.sku, { name });
|
||||
const nameRes = await artikelAutomat.actions.update(articleData.sku, { name });
|
||||
article = nameRes.article;
|
||||
}
|
||||
|
||||
if (categoriesChanged) {
|
||||
console.log('storing categories update', categoryIds, articleData.categoryIds);
|
||||
const catRes = await store.article.setCategories(articleData.sku, { categoryIds });
|
||||
const catRes = await artikelAutomat.actions.setCategories(articleData.sku, categoryIds);
|
||||
article = catRes.article;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ import Typography from '@mui/material/Typography';
|
||||
import { EditablePanelTile, PanelTile } from 'dashgui/panels';
|
||||
import ArticlePriceTiersDialog from '../ArticlePriceTiersDialog';
|
||||
import ArticleUnitEditorDialog from '../ArticleUnitEditorDialog';
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import unitAutomat from '../../../automats/unitAutomat';
|
||||
import companyAutomat from '../../../automats/companyAutomat';
|
||||
import artikelAutomat from '../../../automats/artikelAutomat';
|
||||
import { requireDefined, requireObject } from '../../../utils/apiShape';
|
||||
import {
|
||||
buildPosPricesPutBody,
|
||||
@@ -169,9 +171,8 @@ function priceDraftValid(draft) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function companyFromStore(store) {
|
||||
if (!store) return null;
|
||||
return store.company ?? null;
|
||||
function companyFromStore() {
|
||||
return companyAutomat.state.current ?? null;
|
||||
}
|
||||
|
||||
class PriceAmountField extends Component {
|
||||
@@ -361,13 +362,10 @@ class PriceEdit extends Component {
|
||||
}
|
||||
|
||||
export default class ArticleViewPrice extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const store = this.context;
|
||||
const company = companyFromStore(store);
|
||||
const units = store?.units;
|
||||
const company = companyFromStore();
|
||||
const units = unitAutomat.state.units;
|
||||
this.state = {
|
||||
tiersDialogOpen: false,
|
||||
unitEditorOpen: false,
|
||||
@@ -375,37 +373,28 @@ export default class ArticleViewPrice extends PureComponent {
|
||||
company,
|
||||
};
|
||||
this._cancelled = false;
|
||||
this._ready = company != null && units != null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._cancelled = false;
|
||||
if (this._ready) return;
|
||||
void this.load();
|
||||
this.unsubUnits = unitAutomat.subscribe(this, (state) => ({
|
||||
units: state.units ?? [],
|
||||
}));
|
||||
this.unsubCompany = companyAutomat.subscribe(this, (state) => ({
|
||||
company: state.current ?? null,
|
||||
}));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._cancelled = true;
|
||||
}
|
||||
|
||||
async load() {
|
||||
const store = this.context;
|
||||
try {
|
||||
const [unitList, company] = await Promise.all([
|
||||
store.unitPromise,
|
||||
store.companyPromise,
|
||||
]);
|
||||
if (!this._cancelled) {
|
||||
this.setState({ units: unitList, company: company ?? null });
|
||||
}
|
||||
} catch (err) {
|
||||
if (!this._cancelled) notifyError(err);
|
||||
}
|
||||
this.unsubUnits?.();
|
||||
this.unsubUnits = null;
|
||||
this.unsubCompany?.();
|
||||
this.unsubCompany = null;
|
||||
}
|
||||
|
||||
handleSave = async (draft) => {
|
||||
const { article: articleData, onArticleUpdated } = this.props;
|
||||
const store = this.context;
|
||||
const { company } = this.state;
|
||||
const saved = priceDraftValue(articleData, company);
|
||||
let nextArticle = articleData;
|
||||
@@ -426,12 +415,12 @@ export default class ArticleViewPrice extends PureComponent {
|
||||
if (isNoPackUnit(unitSi, unitamount)) {
|
||||
throw new Error('Einheit pc × 1 ist keine Packungseinheit');
|
||||
}
|
||||
const res = await store.article.update(articleData.sku, { unitamount, unitSi });
|
||||
const res = await artikelAutomat.actions.update(articleData.sku, { unitamount, unitSi });
|
||||
requireObject(res, 'PATCH /api/article/:sku');
|
||||
requireObject(res.article, 'article');
|
||||
nextArticle = res.article;
|
||||
} else {
|
||||
const res = await store.article.update(articleData.sku, {
|
||||
const res = await artikelAutomat.actions.update(articleData.sku, {
|
||||
unitamount: 1,
|
||||
unitSi: 'pc',
|
||||
});
|
||||
@@ -456,7 +445,7 @@ export default class ArticleViewPrice extends PureComponent {
|
||||
taxRate,
|
||||
tiers: draft.tiers ?? [],
|
||||
});
|
||||
const res = await store.article.setPrices(articleData.sku, { prices });
|
||||
const res = await artikelAutomat.actions.setPrices(articleData.sku, prices);
|
||||
requireObject(res, 'PUT /api/article/:sku/prices');
|
||||
requireObject(res.article, 'article');
|
||||
nextArticle = res.article;
|
||||
@@ -480,7 +469,8 @@ export default class ArticleViewPrice extends PureComponent {
|
||||
}
|
||||
|
||||
refreshUnits = async () => {
|
||||
this.setState({ units: await this.context.unitPromise });
|
||||
const units = await unitAutomat.actions.load();
|
||||
this.setState({ units });
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -10,15 +10,12 @@ import Tooltip from '@mui/material/Tooltip';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import attributeAutomat, { selectAttributes } from '../../../automats/attributeAutomat';
|
||||
import propertyAutomat, { selectProperties } from '../../../automats/propertyAutomat';
|
||||
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
|
||||
import { article as articleApi } from '../../../api';
|
||||
|
||||
export default class ArticleAttributesTab extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
getArticle() {
|
||||
return this.props.article || getArticle(this.props.sku);
|
||||
}
|
||||
@@ -165,16 +162,15 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
if (!sku) return;
|
||||
this.setState({ loading: true });
|
||||
try {
|
||||
const api = this.context?.article || articleApi;
|
||||
const [attrRes, propRes] = await Promise.all([
|
||||
api.getAttributeValues(sku),
|
||||
api.getProperties(sku),
|
||||
const res = await Promise.all([
|
||||
articleApi.getAttributeValues(sku),
|
||||
articleApi.getProperties(sku),
|
||||
]);
|
||||
if (this._cancelled) return;
|
||||
|
||||
this.setState({
|
||||
assignedAttributes: Array.isArray(attrRes?.attributevalues) ? attrRes.attributevalues : [],
|
||||
assignedProperties: Array.isArray(propRes?.properties) ? propRes.properties : [],
|
||||
assignedAttributes: Array.isArray(res[0]?.attributevalues) ? res[0].attributevalues : [],
|
||||
assignedProperties: Array.isArray(res[1]?.properties) ? res[1].properties : [],
|
||||
loading: false,
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -231,7 +227,6 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
const effectiveKey = selectedOption?.key || selectedMerkmalKey;
|
||||
|
||||
if (saving || !sku || !effectiveKey) return;
|
||||
const api = this.context?.article || articleApi;
|
||||
|
||||
if (effectiveKey.startsWith('attr-')) {
|
||||
const attrId = Number(effectiveKey.replace('attr-', ''));
|
||||
@@ -262,7 +257,7 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
|
||||
this.setState({ saving: true });
|
||||
try {
|
||||
const res = await api.setAttributeValues(sku, nextValues);
|
||||
const res = await articleApi.setAttributeValues(sku, nextValues);
|
||||
if (this._cancelled) return;
|
||||
this.setState({
|
||||
assignedAttributes: Array.isArray(res?.attributevalues) ? res.attributevalues : [],
|
||||
@@ -304,7 +299,7 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
|
||||
this.setState({ saving: true });
|
||||
try {
|
||||
const res = await api.setProperties(sku, nextProps);
|
||||
const res = await articleApi.setProperties(sku, nextProps);
|
||||
if (this._cancelled) return;
|
||||
this.setState({
|
||||
assignedProperties: Array.isArray(res?.properties) ? res.properties : [],
|
||||
@@ -331,8 +326,7 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
|
||||
this.setState({ saving: true });
|
||||
try {
|
||||
const api = this.context?.article || articleApi;
|
||||
const res = await api.setAttributeValues(sku, nextValues);
|
||||
const res = await articleApi.setAttributeValues(sku, nextValues);
|
||||
if (this._cancelled) return;
|
||||
this.setState({
|
||||
assignedAttributes: Array.isArray(res?.attributevalues) ? res.attributevalues : [],
|
||||
@@ -357,8 +351,7 @@ export default class ArticleAttributesTab extends PureComponent {
|
||||
|
||||
this.setState({ saving: true });
|
||||
try {
|
||||
const api = this.context?.article || articleApi;
|
||||
const res = await api.setProperties(sku, nextProperties);
|
||||
const res = await articleApi.setProperties(sku, nextProperties);
|
||||
if (this._cancelled) return;
|
||||
this.setState({
|
||||
assignedProperties: Array.isArray(res?.properties) ? res.properties : [],
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Component } from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import { articleHistoryAutomat } from '../../../automats/articleDetailAutomat';
|
||||
import sseAutomat from '../../../automats/sseAutomat';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
import ResultsPagination from '../ResultsPagination';
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
@@ -13,46 +15,68 @@ function formatDate(iso) {
|
||||
return new Date(iso).toLocaleString('de-DE');
|
||||
}
|
||||
|
||||
export default class ArticleHistoryTab extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
export default class ArticleHistoryTab extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
history: null,
|
||||
total: null,
|
||||
skip: 0,
|
||||
};
|
||||
this.unwatchHistory = null;
|
||||
this.setSkipAndOffset = null;
|
||||
this.state = { all: [], skip: 0 };
|
||||
this.handlePageChange = this.handlePageChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { unwatch, setSkipAndOffset } = this.context.article.historyWatch(
|
||||
this.props.sku,
|
||||
(history, total) => this.setState({ history, total }),
|
||||
);
|
||||
this.unwatchHistory = unwatch;
|
||||
this.setSkipAndOffset = setSkipAndOffset;
|
||||
setSkipAndOffset(0, PAGE_SIZE);
|
||||
this.watch();
|
||||
void articleHistoryAutomat.load(String(this.props.sku)).catch((err) => {
|
||||
if (this.unsub) notifyError(err);
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.sku !== this.props.sku) {
|
||||
this.watch();
|
||||
void articleHistoryAutomat.load(String(this.props.sku)).catch((err) => {
|
||||
if (this.unsub) notifyError(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unwatchHistory?.();
|
||||
this.unwatchHistory = null;
|
||||
this.setSkipAndOffset = null;
|
||||
this.unsub?.();
|
||||
this.unsub = null;
|
||||
this.unwatchSignal?.();
|
||||
this.unwatchSignal = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to the keyed history cache and to the article SSE signal.
|
||||
* The backend re-emits `article-<sku>` whenever the article changes, and
|
||||
* `sseAutomat` reloads the history key for us.
|
||||
*/
|
||||
watch() {
|
||||
this.unsub?.();
|
||||
const handle = articleHistoryAutomat.key(String(this.props.sku));
|
||||
this.unsub = handle.subscribe(this, (item) => {
|
||||
const all = Array.isArray(item.data) ? item.data : [];
|
||||
this.setState({ all });
|
||||
});
|
||||
|
||||
this.unwatchSignal?.();
|
||||
this.unwatchSignal = sseAutomat.actions.watchArticle(String(this.props.sku), () => {
|
||||
void articleHistoryAutomat.reload(String(this.props.sku)).catch(() => {});
|
||||
});
|
||||
}
|
||||
|
||||
/** Client-side pagination over the full history list. */
|
||||
pageOf(all, skip) {
|
||||
return all.slice(skip, skip + PAGE_SIZE);
|
||||
}
|
||||
|
||||
handlePageChange(_event, page) {
|
||||
const skip = (page - 1) * PAGE_SIZE;
|
||||
this.setSkipAndOffset?.(skip, PAGE_SIZE);
|
||||
this.setState({ skip });
|
||||
this.setState({ skip: (page - 1) * PAGE_SIZE });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { history, total, skip } = this.state;
|
||||
if (history == null || total == null) return null;
|
||||
const { all, skip } = this.state;
|
||||
const total = all.length;
|
||||
const history = this.pageOf(all, skip);
|
||||
|
||||
const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
||||
const currentPage = Math.floor(skip / PAGE_SIZE) + 1;
|
||||
|
||||
@@ -2,13 +2,11 @@ import { Component } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
import { article as articleApi } from '../../../api';
|
||||
import { artikelAutomat } from '../../../automats';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
|
||||
export default class ArticleSettingsTab extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { busy: false };
|
||||
@@ -24,7 +22,7 @@ export default class ArticleSettingsTab extends Component {
|
||||
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await this.context.article.delete(articleData.sku);
|
||||
await articleApi.delete(articleData.sku);
|
||||
artikelAutomat.actions.clearArticle(articleData.sku);
|
||||
onArticleDeleted?.(articleData);
|
||||
} catch (err) {
|
||||
|
||||
@@ -20,15 +20,16 @@ import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
|
||||
import { StorageManagerContext } from '../../../api/storageManagerContext';
|
||||
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';
|
||||
import ArticleSupplierDialog from '../ArticleSupplierDialog';
|
||||
|
||||
export default class ArticleSupplierTab extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -40,10 +41,12 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
editItem: null,
|
||||
busyId: null,
|
||||
};
|
||||
this.unwatchSuppliers = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsubCompany = companyAutomat.subscribe(this, (state) => ({
|
||||
company: state.current ?? null,
|
||||
}));
|
||||
this.initData();
|
||||
}
|
||||
|
||||
@@ -54,46 +57,53 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unwatchSuppliers?.();
|
||||
this.unwatchSuppliers = null;
|
||||
this.unsubSuppliers?.();
|
||||
this.unsubSuppliers = null;
|
||||
this.unwatchSignal?.();
|
||||
this.unwatchSignal = null;
|
||||
this.unsubCompany?.();
|
||||
this.unsubCompany = null;
|
||||
}
|
||||
|
||||
async initData() {
|
||||
const sku = this.props.article?.sku;
|
||||
if (!sku) return;
|
||||
|
||||
this.unwatchSuppliers?.();
|
||||
this.setState({ loading: true });
|
||||
|
||||
// 1. Subscribe to SSE-backed supplier updates
|
||||
const { unwatch, refresh } = this.context.article.supplierWatch(
|
||||
sku,
|
||||
(assignedSuppliers) => {
|
||||
this.setState({
|
||||
assignedSuppliers: assignedSuppliers ?? [],
|
||||
loading: false,
|
||||
});
|
||||
},
|
||||
);
|
||||
this.unwatchSuppliers = unwatch;
|
||||
this.refreshSuppliers = refresh;
|
||||
|
||||
// 2. Fetch available company suppliers and company metadata
|
||||
try {
|
||||
const [companySuppliers, company] = await Promise.all([
|
||||
supplierAutomat.actions.load(),
|
||||
this.context.company ?? this.context.companyPromise,
|
||||
]);
|
||||
// 1. Subscribe to the keyed supplier cache and the article SSE signal
|
||||
this.unsubSuppliers?.();
|
||||
const handle = articleSupplierAutomat.key(String(sku));
|
||||
this.unsubSuppliers = handle.subscribe(this, (item) => {
|
||||
this.setState({
|
||||
companySuppliers: companySuppliers ?? [],
|
||||
company: company ?? null,
|
||||
assignedSuppliers: Array.isArray(item.data) ? item.data : [],
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
void articleSupplierAutomat.load(String(sku)).catch((err) => {
|
||||
if (this.unsubSuppliers) notifyError(err);
|
||||
});
|
||||
|
||||
this.unwatchSignal?.();
|
||||
this.unwatchSignal = sseAutomat.actions.watchArticle(String(sku), () => {
|
||||
void articleSupplierAutomat.reload(String(sku)).catch(() => {});
|
||||
});
|
||||
|
||||
// 2. Fetch available company suppliers
|
||||
try {
|
||||
const companySuppliers = await supplierAutomat.actions.load();
|
||||
this.setState({ companySuppliers: companySuppliers ?? [] });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
/** Re-fetches the assigned suppliers for the current article. */
|
||||
refreshSuppliers = async () => {
|
||||
const sku = this.props.article?.sku;
|
||||
if (!sku) return;
|
||||
await articleSupplierAutomat.reload(String(sku));
|
||||
};
|
||||
|
||||
handleOpenAdd = () => {
|
||||
this.setState({ dialogOpen: true, editItem: null });
|
||||
};
|
||||
@@ -112,7 +122,7 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
|
||||
try {
|
||||
if (payload.id) {
|
||||
await this.context.article.updateSupplier(sku, payload.id, {
|
||||
await articleApi.updateSupplier(sku, payload.id, {
|
||||
sku: payload.sku,
|
||||
pu: payload.pu,
|
||||
stock: payload.stock,
|
||||
@@ -121,7 +131,7 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
});
|
||||
enqueueSnackbar('Lieferantendaten aktualisiert', { variant: 'success' });
|
||||
} else {
|
||||
await this.context.article.addSupplier(sku, {
|
||||
await articleApi.addSupplier(sku, {
|
||||
entityId: payload.entityId,
|
||||
sku: payload.sku,
|
||||
pu: payload.pu,
|
||||
@@ -132,7 +142,7 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
enqueueSnackbar('Lieferant erfolgreich zugewiesen', { variant: 'success' });
|
||||
}
|
||||
this.handleCloseDialog();
|
||||
await this.refreshSuppliers?.();
|
||||
await this.refreshSuppliers();
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
throw err;
|
||||
@@ -150,9 +160,9 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
|
||||
this.setState({ busyId: item.id });
|
||||
try {
|
||||
await this.context.article.deleteSupplier(sku, item.id);
|
||||
await articleApi.deleteSupplier(sku, item.id);
|
||||
enqueueSnackbar('Lieferant entfernt', { variant: 'success' });
|
||||
await this.refreshSuppliers?.();
|
||||
await this.refreshSuppliers();
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
} finally {
|
||||
@@ -176,11 +186,11 @@ export default class ArticleSupplierTab extends PureComponent {
|
||||
this.setState({ assignedSuppliers: nextList, busyId: moved.id });
|
||||
|
||||
try {
|
||||
await this.context.article.orderSuppliers(sku, { ids });
|
||||
await this.refreshSuppliers?.();
|
||||
await articleApi.orderSuppliers(sku, ids);
|
||||
await this.refreshSuppliers();
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
await this.refreshSuppliers?.();
|
||||
await this.refreshSuppliers();
|
||||
} finally {
|
||||
this.setState({ busyId: null });
|
||||
}
|
||||
|
||||
@@ -20,13 +20,11 @@ import CloseIcon from '@mui/icons-material/Close';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined';
|
||||
import LinkIcon from '@mui/icons-material/Link';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import articleIndexAutomat from '../../automats/articleIndexAutomat';
|
||||
import ArticlePhoto from '../article/ArticlePhoto';
|
||||
import { BrandLabel } from '../brand';
|
||||
|
||||
export default class ArticleSearchDialog extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -68,13 +66,7 @@ export default class ArticleSearchDialog extends PureComponent {
|
||||
async loadArticles() {
|
||||
this.setState({ loading: true, error: null });
|
||||
try {
|
||||
let list = [];
|
||||
if (this.context?.articleIndexPromise) {
|
||||
list = await this.context.articleIndexPromise;
|
||||
} else if (this.context?.article?.index) {
|
||||
const res = await this.context.article.index();
|
||||
list = res?.articles || [];
|
||||
}
|
||||
const list = await articleIndexAutomat.actions.ensureLoaded();
|
||||
this.setState({ articles: Array.isArray(list) ? list : [], loading: false });
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Laden des Artikel-Index:', err);
|
||||
|
||||
@@ -27,7 +27,6 @@ import CloseIcon from '@mui/icons-material/Close';
|
||||
import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined';
|
||||
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
|
||||
import WarehouseIcon from '@mui/icons-material/Warehouse';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import { formatDate } from './manifestConfig';
|
||||
@@ -38,8 +37,6 @@ import {
|
||||
import warehouseAutomat from '../../automats/warehouseAutomat';
|
||||
|
||||
export default class BookDeliveryDialog extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -160,12 +157,7 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
|
||||
this.setState({ loading: true, error: null });
|
||||
try {
|
||||
const ledgerApi = this.context?.ledger;
|
||||
const deliveryApi = this.context?.delivery;
|
||||
const summary = await fetchDeliveryLedgerSummary(delivery.id, {
|
||||
ledgerApi,
|
||||
deliveryApi,
|
||||
});
|
||||
const summary = await fetchDeliveryLedgerSummary(delivery.id);
|
||||
this.initAmountsFromSummary(summary, prefillLineId);
|
||||
this.setState({ summary, loading: false });
|
||||
} catch (err) {
|
||||
@@ -255,14 +247,10 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
|
||||
this.setState({ isBooking: true, error: null, warehouseError: null });
|
||||
try {
|
||||
const ledgerApi = this.context?.ledger;
|
||||
const deliveryApi = this.context?.delivery;
|
||||
|
||||
await bookDeliveryLines({
|
||||
delivery: summary.delivery || delivery,
|
||||
linesToBook,
|
||||
warehouseId: selectedWarehouseId,
|
||||
ledgerApi,
|
||||
transactionText,
|
||||
});
|
||||
|
||||
@@ -273,10 +261,7 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
);
|
||||
|
||||
// Refresh summary
|
||||
const updatedSummary = await fetchDeliveryLedgerSummary(delivery.id, {
|
||||
ledgerApi,
|
||||
deliveryApi,
|
||||
});
|
||||
const updatedSummary = await fetchDeliveryLedgerSummary(delivery.id);
|
||||
|
||||
if (onBookSuccess) {
|
||||
onBookSuccess(updatedSummary);
|
||||
|
||||
@@ -13,7 +13,7 @@ import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import BackendError from '../BackendError';
|
||||
import { RecordList } from 'dashgui/lists';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import deliveryAutomat from '../../automats/deliveryAutomat';
|
||||
import { delivery } from '../../api';
|
||||
import { supplierAutomat } from '../../automats';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
@@ -39,8 +39,6 @@ function formatFileSize(bytes) {
|
||||
}
|
||||
|
||||
export class DeliveryList extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -77,14 +75,7 @@ export class DeliveryList extends PureComponent {
|
||||
}
|
||||
|
||||
async fetchDeliveries() {
|
||||
if (this.context?.deliveries) {
|
||||
return this.context.deliveries;
|
||||
}
|
||||
if (this.context?.deliveryPromise) {
|
||||
return await this.context.deliveryPromise;
|
||||
}
|
||||
const res = await (this.context?.delivery?.list ? this.context.delivery.list() : delivery.list());
|
||||
return res?.deliveries ?? res ?? [];
|
||||
return deliveryAutomat.actions.ensureLoaded();
|
||||
}
|
||||
|
||||
async load() {
|
||||
@@ -103,12 +94,10 @@ export class DeliveryList extends PureComponent {
|
||||
}
|
||||
|
||||
async handleRetry() {
|
||||
if (this.context?.updateDelivery) {
|
||||
try {
|
||||
await this.context.updateDelivery();
|
||||
} catch {
|
||||
// Will be caught in load()
|
||||
}
|
||||
try {
|
||||
await deliveryAutomat.actions.reload();
|
||||
} catch {
|
||||
// Will be caught in load()
|
||||
}
|
||||
void this.load();
|
||||
}
|
||||
@@ -140,15 +129,13 @@ export class DeliveryList extends PureComponent {
|
||||
|
||||
async handleBookSuccess() {
|
||||
this.handleCloseBookDialog();
|
||||
if (this.context?.updateDelivery) {
|
||||
try {
|
||||
const updated = await this.context.updateDelivery();
|
||||
if (this._cancelled) return;
|
||||
this.setState({ docs: sortDeliveries(updated) });
|
||||
return;
|
||||
} catch {
|
||||
// Fall back to load()
|
||||
}
|
||||
try {
|
||||
const updated = await deliveryAutomat.actions.reload();
|
||||
if (this._cancelled) return;
|
||||
this.setState({ docs: sortDeliveries(updated) });
|
||||
return;
|
||||
} catch {
|
||||
// Fall back to load()
|
||||
}
|
||||
void this.load();
|
||||
}
|
||||
@@ -159,10 +146,8 @@ export class DeliveryList extends PureComponent {
|
||||
|
||||
this.setState({ busyId: doc.id });
|
||||
try {
|
||||
await (this.context?.delivery?.delete ? this.context.delivery.delete(doc.id) : delivery.delete(doc.id));
|
||||
if (this.context?.updateDelivery) {
|
||||
await this.context.updateDelivery();
|
||||
}
|
||||
await delivery.delete(doc.id);
|
||||
await deliveryAutomat.actions.reload();
|
||||
if (this._cancelled) return;
|
||||
await this.load();
|
||||
enqueueSnackbar('Lieferung gelöscht.', { variant: 'success' });
|
||||
|
||||
@@ -4,7 +4,7 @@ import CircularProgress from '@mui/material/CircularProgress';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import BackendError from '../BackendError';
|
||||
import { PanelTileGrid } from 'dashgui/panels';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { delivery as deliveryApi } from '../../api';
|
||||
import { requireArray, requireDefined, requireObject } from '../../utils/apiShape';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
@@ -20,11 +20,10 @@ import {
|
||||
lineFromApi,
|
||||
loadParties,
|
||||
manifestConfig,
|
||||
apiFor,
|
||||
} from './manifestConfig';
|
||||
|
||||
class ManifestForm extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.cfg = manifestConfig(props.kind);
|
||||
@@ -88,7 +87,7 @@ class ManifestForm extends PureComponent {
|
||||
async loadPartiesOnly() {
|
||||
this.setState({ status: 'loadingParties', error: null, partiesLoading: true });
|
||||
try {
|
||||
const parties = await loadParties(this.props.kind, this.context);
|
||||
const parties = await loadParties(this.props.kind);
|
||||
if (this._cancelled) return;
|
||||
this.setState({
|
||||
status: 'ready',
|
||||
@@ -120,8 +119,8 @@ class ManifestForm extends PureComponent {
|
||||
this.setState({ status: 'loading', error: null, partiesLoading: true });
|
||||
try {
|
||||
const [docRes, parties] = await Promise.all([
|
||||
this.context[cfg.api].get(docId),
|
||||
loadParties(kind, this.context),
|
||||
apiFor(cfg).get(docId),
|
||||
loadParties(kind),
|
||||
]);
|
||||
if (this._cancelled) return;
|
||||
|
||||
@@ -243,10 +242,10 @@ class ManifestForm extends PureComponent {
|
||||
};
|
||||
|
||||
try {
|
||||
const gen = this.context?.delivery?.parsePdfSse;
|
||||
const gen = deliveryApi.parsePdfSse;
|
||||
if (!gen) throw new Error('parsePdfSse nicht verfügbar.');
|
||||
|
||||
for await (const { event, data } of this.context.delivery.parsePdfSse(file, { signal: abortController.signal })) {
|
||||
for await (const { event, data } of gen(file, { signal: abortController.signal })) {
|
||||
if (this._cancelled || abortController.signal.aborted) return;
|
||||
switch (event) {
|
||||
case 'status':
|
||||
@@ -372,24 +371,25 @@ class ManifestForm extends PureComponent {
|
||||
}
|
||||
|
||||
this.setState({ saving: true });
|
||||
const api = apiFor(cfg);
|
||||
try {
|
||||
if (mode === 'create') {
|
||||
const data = await this.context[cfg.api].create(body);
|
||||
const data = await api.create(body);
|
||||
requireObject(data, cfg.postPathLabel);
|
||||
requireObject(data[cfg.docField], cfg.docField);
|
||||
requireDefined(data[cfg.docField].id, `${cfg.docField}.id`);
|
||||
await this.context[cfg.updateCache]();
|
||||
await cfg.listAutomat.actions.reload();
|
||||
if (this._cancelled) return;
|
||||
enqueueSnackbar(cfg.createdMsg, { variant: 'success' });
|
||||
this.props.navigate(cfg.editPath(data[cfg.docField].id));
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await this.context[cfg.api].update(docId, body);
|
||||
const data = await api.update(docId, body);
|
||||
requireObject(data, cfg.patchPathLabel);
|
||||
requireObject(data[cfg.docField], cfg.docField);
|
||||
requireDefined(data[cfg.docField].id, `${cfg.docField}.id`);
|
||||
await this.context[cfg.updateCache]();
|
||||
await cfg.listAutomat.actions.reload();
|
||||
if (this._cancelled) return;
|
||||
const doc = data[cfg.docField];
|
||||
const apiLines = requireArray(doc.lines, `${cfg.docField}.lines`);
|
||||
|
||||
@@ -2,7 +2,6 @@ import { PureComponent } from 'react';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import BackendError from '../BackendError';
|
||||
import { RecordList } from 'dashgui/lists';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import withNavigate from '../withNavigate';
|
||||
@@ -14,11 +13,10 @@ import {
|
||||
numberDisplay,
|
||||
partyDisplayName,
|
||||
sortDocs,
|
||||
apiFor,
|
||||
} from './manifestConfig';
|
||||
|
||||
class ManifestList extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.cfg = manifestConfig(props.kind);
|
||||
@@ -52,8 +50,8 @@ class ManifestList extends PureComponent {
|
||||
this.setState({ status: 'loading', error: null });
|
||||
try {
|
||||
const [docs, parties] = await Promise.all([
|
||||
cfg.listPromise(this.context),
|
||||
loadParties(this.props.kind, this.context),
|
||||
cfg.listAutomat.actions.ensureLoaded(),
|
||||
loadParties(this.props.kind),
|
||||
]);
|
||||
if (this._cancelled) return;
|
||||
const sortedDocs = sortDocs(docs);
|
||||
@@ -96,8 +94,9 @@ class ManifestList extends PureComponent {
|
||||
const cfg = this.cfg;
|
||||
this.setState({ busyId: doc.id });
|
||||
try {
|
||||
await this.context[cfg.api].delete(doc.id);
|
||||
const list = await this.context[cfg.updateCache]();
|
||||
const api = apiFor(cfg.kind);
|
||||
await api.delete(doc.id);
|
||||
const list = await cfg.listAutomat.actions.reload();
|
||||
if (this._cancelled) return;
|
||||
this.setState({
|
||||
docs: sortDocs(list),
|
||||
|
||||
@@ -14,7 +14,6 @@ import VerticalAlignBottomIcon from '@mui/icons-material/VerticalAlignBottom';
|
||||
import ViewSidebarIcon from '@mui/icons-material/ViewSidebar';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { PanelTile } from 'dashgui/panels';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { delivery as deliveryApi } from '../../api';
|
||||
|
||||
/**
|
||||
@@ -24,8 +23,6 @@ import { delivery as deliveryApi } from '../../api';
|
||||
* attachment is selected; unmounting closes it.
|
||||
*/
|
||||
export class PdfViewerPanel extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -77,10 +74,7 @@ export class PdfViewerPanel extends PureComponent {
|
||||
if (effectiveDocId != null && fileId != null) {
|
||||
this.setState({ loading: true, error: null });
|
||||
try {
|
||||
const getFile =
|
||||
this.context?.delivery?.getFile?.bind(this.context.delivery) ||
|
||||
deliveryApi.getFile;
|
||||
const blob = await getFile(effectiveDocId, fileId);
|
||||
const blob = await deliveryApi.getFile(effectiveDocId, fileId);
|
||||
this._objectUrl = URL.createObjectURL(blob);
|
||||
this.setState({ url: this._objectUrl, loading: false, error: null });
|
||||
return;
|
||||
|
||||
@@ -17,7 +17,6 @@ import VerticalAlignBottomIcon from '@mui/icons-material/VerticalAlignBottom';
|
||||
import ViewSidebarIcon from '@mui/icons-material/ViewSidebar';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { PanelTile } from 'dashgui/panels';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { delivery as deliveryApi } from '../../api';
|
||||
|
||||
/**
|
||||
@@ -26,8 +25,6 @@ import { delivery as deliveryApi } from '../../api';
|
||||
* selected; unmounting closes it.
|
||||
*/
|
||||
export class TextEditorPanel extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -89,10 +86,7 @@ export class TextEditorPanel extends PureComponent {
|
||||
if (effectiveDocId != null && fileId != null) {
|
||||
this.setState({ loading: true });
|
||||
try {
|
||||
const getFile =
|
||||
this.context?.delivery?.getFile?.bind(this.context.delivery) ||
|
||||
deliveryApi.getFile;
|
||||
const blob = await getFile(effectiveDocId, fileId);
|
||||
const blob = await deliveryApi.getFile(effectiveDocId, fileId);
|
||||
const text = await blob.text();
|
||||
this.setState({
|
||||
content: text,
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import {
|
||||
delivery as deliveryApi,
|
||||
quote as quoteApi,
|
||||
order as orderApi,
|
||||
} 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';
|
||||
|
||||
/** Groups that are not sales customers (filter client-side). */
|
||||
export const EXCLUDED_CUSTOMER_GROUP_NAMES = new Set(['Lieferanten', 'POS']);
|
||||
@@ -481,12 +491,27 @@ export const DELIVERY_LINE_COLUMNS = [
|
||||
|
||||
/** @typedef {'delivery'|'quote'|'order'} ManifestKind */
|
||||
|
||||
/**
|
||||
* Resolves the api namespace for a manifest kind.
|
||||
*
|
||||
* Delivery, quote, and order share one editor/list shape but talk to three
|
||||
* different endpoints.
|
||||
*
|
||||
* @param {ManifestKind} kind Manifest kind.
|
||||
* @returns {{get: Function, create: Function, update: Function, delete: Function}}
|
||||
*/
|
||||
export function apiFor(kind) {
|
||||
if (kind === 'delivery') return deliveryApi;
|
||||
if (kind === 'quote') return quoteApi;
|
||||
if (kind === 'order') return orderApi;
|
||||
throw new Error(`Unknown manifest kind: ${kind}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load party options for Autocomplete / list name lookup.
|
||||
* @param {ManifestKind} kind
|
||||
* @param {object} ctx StorageManager instance
|
||||
*/
|
||||
export async function loadParties(kind, ctx) {
|
||||
export async function loadParties(kind) {
|
||||
if (kind === 'delivery') {
|
||||
if (supplierAutomat.state.status === 'ready') {
|
||||
return sortSuppliers(supplierAutomat.state.suppliers);
|
||||
@@ -495,8 +520,8 @@ export async function loadParties(kind, ctx) {
|
||||
return sortSuppliers(list);
|
||||
}
|
||||
const [entities, groups] = await Promise.all([
|
||||
ctx.entities ?? ctx.entityPromise,
|
||||
ctx.customergroups ?? ctx.customergroupPromise,
|
||||
entityAutomat.actions.ensureLoaded(),
|
||||
customergroupAutomat.actions.ensureLoaded(),
|
||||
]);
|
||||
return filterCustomerEntities(entities, groups);
|
||||
}
|
||||
@@ -538,8 +563,7 @@ export function manifestConfig(kind) {
|
||||
headerColor: '#fed7aa',
|
||||
linesColor: '#bbf7d0',
|
||||
requirePartyBeforeLines: false,
|
||||
updateCache: 'updateDelivery',
|
||||
listPromise: (ctx) => ctx.deliveries ?? ctx.deliveryPromise,
|
||||
listAutomat: deliveryAutomat,
|
||||
partiesByIdFromList: (parties) =>
|
||||
Object.fromEntries(parties.map((row) => [row.id, row])),
|
||||
// Delivery uses a server-allocated read-only number plus an optional
|
||||
@@ -575,8 +599,7 @@ export function manifestConfig(kind) {
|
||||
headerColor: '#fde68a',
|
||||
linesColor: '#bbf7d0',
|
||||
requirePartyBeforeLines: false,
|
||||
updateCache: 'updateQuote',
|
||||
listPromise: (ctx) => ctx.quotes ?? ctx.quotePromise,
|
||||
listAutomat: quoteAutomat,
|
||||
partiesByIdFromList: (parties) =>
|
||||
Object.fromEntries(parties.map((row) => [row.id, row])),
|
||||
columns: LINE_COLUMNS,
|
||||
@@ -609,8 +632,7 @@ export function manifestConfig(kind) {
|
||||
headerColor: '#c4b5fd',
|
||||
linesColor: '#bbf7d0',
|
||||
requirePartyBeforeLines: false,
|
||||
updateCache: 'updateOrder',
|
||||
listPromise: (ctx) => ctx.orders ?? ctx.orderPromise,
|
||||
listAutomat: orderAutomat,
|
||||
partiesByIdFromList: (parties) =>
|
||||
Object.fromEntries(parties.map((row) => [row.id, row])),
|
||||
columns: LINE_COLUMNS,
|
||||
|
||||
@@ -15,7 +15,8 @@ import Typography from '@mui/material/Typography';
|
||||
import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import BackendError from '../BackendError';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { number as numberApi } from '../../api';
|
||||
import numberAutomat from '../../automats/numberAutomat';
|
||||
import { requireArray, requireDefined, requireObject } from '../../utils/apiShape';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
@@ -31,8 +32,6 @@ function parseNext(value) {
|
||||
}
|
||||
|
||||
export default class NumberSettingsPanel extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -54,7 +53,7 @@ export default class NumberSettingsPanel extends Component {
|
||||
this.setState({ status: 'loading', error: null });
|
||||
try {
|
||||
const numbers = requireArray(
|
||||
await this.context.updateNumber(),
|
||||
await numberAutomat.actions.reload(),
|
||||
'numbers',
|
||||
);
|
||||
const drafts = Object.fromEntries(
|
||||
@@ -141,7 +140,7 @@ export default class NumberSettingsPanel extends Component {
|
||||
},
|
||||
}));
|
||||
try {
|
||||
const data = await this.context.number.update(id, body);
|
||||
const data = await numberApi.update(id, body);
|
||||
requireObject(data, 'PATCH /api/number');
|
||||
requireObject(data.number, 'number');
|
||||
const updated = requireDefined(data.number, 'number');
|
||||
|
||||
@@ -25,7 +25,7 @@ import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
|
||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import BackendError from '../BackendError';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { printer as printerApi } from '../../api';
|
||||
import { requireArray, requireObject } from '../../utils/apiShape';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
@@ -186,8 +186,6 @@ class PrinterFormDialog extends Component {
|
||||
}
|
||||
|
||||
export default class PrinterSettingsPanel extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -216,7 +214,7 @@ export default class PrinterSettingsPanel extends Component {
|
||||
async load() {
|
||||
this.setState({ status: 'loading', error: null });
|
||||
try {
|
||||
const data = await this.context.printer.list();
|
||||
const data = await printerApi.list();
|
||||
requireObject(data, 'GET /api/printer');
|
||||
const printers = requireArray(data.printers, 'printers');
|
||||
this.setState({ status: 'ready', printers });
|
||||
@@ -243,7 +241,7 @@ export default class PrinterSettingsPanel extends Component {
|
||||
}
|
||||
|
||||
async handleAddSubmit(body) {
|
||||
const data = await this.context.printer.create(body);
|
||||
const data = await printerApi.create(body);
|
||||
requireObject(data, 'POST /api/printer');
|
||||
const printer = data.printer;
|
||||
this.setState((prev) => ({
|
||||
@@ -255,7 +253,7 @@ export default class PrinterSettingsPanel extends Component {
|
||||
|
||||
async handleEditSubmit(body) {
|
||||
const { editPrinter } = this.state;
|
||||
const data = await this.context.printer.update(editPrinter.id, body);
|
||||
const data = await printerApi.update(editPrinter.id, body);
|
||||
requireObject(data, 'PATCH /api/printer/:id');
|
||||
const updated = data.printer;
|
||||
this.setState((prev) => ({
|
||||
@@ -268,7 +266,7 @@ export default class PrinterSettingsPanel extends Component {
|
||||
async handleDelete(id) {
|
||||
this.setState({ deleteId: id, deleteBusy: true });
|
||||
try {
|
||||
await this.context.printer.delete(id);
|
||||
await printerApi.delete(id);
|
||||
this.setState((prev) => ({
|
||||
printers: prev.printers.filter((p) => p.id !== id),
|
||||
deleteId: null,
|
||||
@@ -314,7 +312,7 @@ export default class PrinterSettingsPanel extends Component {
|
||||
this.setState({ printers: reordered });
|
||||
|
||||
try {
|
||||
await this.context.printer.order({ printerIds });
|
||||
await printerApi.order({ printerIds });
|
||||
} catch (err) {
|
||||
if (this._unmounted) return;
|
||||
notifyError(err);
|
||||
|
||||
@@ -14,7 +14,7 @@ import Typography from '@mui/material/Typography';
|
||||
import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import BackendError from '../BackendError';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { setting as settingApi } from '../../api';
|
||||
import { requireArray, requireDefined, requireObject } from '../../utils/apiShape';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
@@ -42,8 +42,6 @@ function keyLabel(key) {
|
||||
}
|
||||
|
||||
export default class SettingsKeysPanel extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -64,7 +62,7 @@ export default class SettingsKeysPanel extends Component {
|
||||
async load() {
|
||||
this.setState({ status: 'loading', error: null });
|
||||
try {
|
||||
const data = await this.context.setting.list();
|
||||
const data = await settingApi.list();
|
||||
requireObject(data, 'GET /api/company/setting');
|
||||
const settings = requireArray(data.settings, 'settings');
|
||||
const drafts = Object.fromEntries(
|
||||
@@ -119,7 +117,7 @@ export default class SettingsKeysPanel extends Component {
|
||||
},
|
||||
}));
|
||||
try {
|
||||
const data = await this.context.setting.upsert(key, body);
|
||||
const data = await settingApi.upsert(key, body);
|
||||
requireObject(data, 'PUT /api/company/setting/:key');
|
||||
requireObject(data.setting, 'setting');
|
||||
const updated = requireDefined(data.setting, 'setting');
|
||||
|
||||
Reference in New Issue
Block a user