u
This commit is contained in:
192
docs/supplierAutomat.md
Normal file
192
docs/supplierAutomat.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# `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.
|
||||
Reference in New Issue
Block a user