Files
dashfront/docs/supplierAutomat.md
2026-09-23 17:24:33 +02:00

193 lines
5.5 KiB
Markdown

# `supplierAutomat`
> **Source File**: [`src/automats/supplierAutomat.js`](file:///home/seb/src/dashfront/src/automats/supplierAutomat.js)
> **Registration Key**: `'supplier'` (`Automat.get('supplier')` / `window.__AUTOMATS__.get('supplier')`)
---
## 1. Overview & Purpose
`supplierAutomat` manages the supplier directory for the active company (`GET /api/supplier`). Suppliers are used when receiving deliveries, reconciling purchase invoices, and organizing supplier-specific article catalogs.
### Core Responsibilities
- **Company Scoping & Auth Sync**: Caches suppliers for the selected company and automatically invalidates or re-fetches when switching companies or logging out.
- **In-flight Deduplication**: Multiple components invoking `load()` concurrently share a single HTTP request promise (`inflightLoad`).
- **Full CRUD Support**: Creating, updating (renaming), and deleting suppliers with automatic cache refresh.
- **Silent Background Refreshes**: `reload()` updates supplier lists in the background without clearing current state.
---
## 2. State Structure
```ts
interface Supplier {
id: string | number;
name: string;
[key: string]: any;
}
interface SupplierState {
/** Company suppliers list. */
suppliers: Array<Supplier>;
/** Async lifecycle phase. */
status: 'idle' | 'loading' | 'ready' | 'failed';
/** Last error object if load failed. */
error: Error | null;
}
```
### Initial State
```js
const initialState = {
suppliers: [],
status: 'idle',
error: null,
};
```
---
## 3. Configuration & Options
```js
new Automat(initialState, actions, { name: 'supplier' });
```
- **`name`**: `'supplier'`
- **`persist`**: `false`
---
## 4. Actions & Methods
Access actions via `supplierAutomat.actions.<method>()`.
### `load(): Promise<Array<Supplier>>`
Loads the supplier list via `GET /api/supplier`. Deduplicates concurrent callers: if a request is already in flight, it returns the existing promise. Sets `status` to `'ready'` on success.
### `reload(): Promise<Array<Supplier>>`
Silently re-fetches the supplier list without setting `status` back to `'loading'`. Preserves the current list until the new response arrives.
### `create(body: { name: string }): Promise<Supplier>`
Creates a new supplier via `POST /api/supplier`, waits for `reload()`, and returns the newly created supplier object.
### `update(id: string | number, body: { name: string }): Promise<Supplier>`
Updates supplier details via `PATCH /api/supplier/:id`, waits for `reload()`, and returns the updated supplier.
### `remove(id: string | number): Promise<void>`
Deletes a supplier via `DELETE /api/supplier/:id` and triggers `reload()`.
### `setSuppliers(suppliers: Array<Supplier>): void`
Directly replaces the cached supplier array without a network round-trip.
### `clear(): void`
Resets the state back to `initialState`.
---
## 5. Cross-Automat Subscriptions
`supplierAutomat` tracks `authAutomat` through `syncWithAuth`:
```js
function authKey(authState) {
if (authState?.status !== 'authed' || !authState.user) return null;
const companyId = authState.user.selectedCompanyId;
return companyId == null ? null : String(companyId);
}
function syncWithAuth(authState) {
const key = authKey(authState);
if (key === lastAuthKey) return;
lastAuthKey = key;
if (key == null) {
supplierAutomat.actions.clear();
} else {
void supplierAutomat.actions.load().catch(() => {});
}
}
authAutomat.subscribe(syncWithAuth);
syncWithAuth(authAutomat.state);
```
Whenever the user authenticates with a company, suppliers load automatically; when logging out, they clear.
---
## 6. Usage Examples
### A. React `PureComponent` Supplier Dropdown
```jsx
import { PureComponent } from 'react';
import PropTypes from 'prop-types';
import supplierAutomat from '../automats/supplierAutomat';
export class SupplierDropdown extends PureComponent {
static propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
onChange: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = {
suppliers: supplierAutomat.state.suppliers,
status: supplierAutomat.state.status,
};
}
componentDidMount() {
this.unsub = supplierAutomat.subscribe(this, ['suppliers', 'status']);
if (this.state.status === 'idle') {
supplierAutomat.actions.load().catch(console.error);
}
}
componentWillUnmount() {
this.unsub?.();
}
render() {
const { suppliers, status } = this.state;
const { value, onChange } = this.props;
if (status === 'loading') {
return <select disabled><option>Lieferanten laden...</option></select>;
}
return (
<select value={value ?? ''} onChange={(e) => onChange(e.target.value)}>
<option value="">-- Lieferant wählen --</option>
{suppliers.map((s) => (
<option key={s.id} value={s.id}>{s.name}</option>
))}
</select>
);
}
}
```
### B. Creating a Supplier
```js
import supplierAutomat from '../automats/supplierAutomat';
async function addNewSupplier(name) {
try {
const supplier = await supplierAutomat.actions.create({ name });
console.log('Supplier created:', supplier);
} catch (err) {
console.error('Failed to create supplier:', err);
}
}
```
---
## 7. Best Practices & Gotchas
1. **Automatic Refresh**: `create`, `update`, and `remove` automatically call `reload()`, so subscribers will receive the updated list without manual re-querying.
2. **Inflight Safety**: You can safely call `supplierAutomat.actions.load()` in multiple components mounted at the same time without causing duplicate HTTP requests.