193 lines
5.5 KiB
Markdown
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.
|