4.6 KiB
unitAutomat
Source File:
src/automats/unitAutomat.js
Registration Key:'unit'(Automat.get('unit')/window.__AUTOMATS__.get('unit'))
1. Overview & Purpose
unitAutomat manages the active company's allowed units of measure (e.g. Stück, kg, Meter, Liter). Units are attached to articles and delivery lineitems to ensure consistent units of measure and price calculation across the catalog and invoices.
Core Responsibilities
- Company Scoping: Caches all measurement units for the currently selected company.
- Alphabetical Sorting: Automatically sorts units by their display symbol or SI code using German collation.
- Autonomous Bootstrap: Automatically loads whenever
authAutomattransitions to'authed'with a selected company, and resets when the session ends. - Full Unit Maintenance: Upserting (creating or modifying) and deleting units by SI key.
2. State Structure
interface Unit {
si: string; // International standard code (e.g. 'C62', 'KGM', 'LTR')
symbol?: string; // Display symbol (e.g. 'Stk', 'kg', 'l')
name?: string; // Full name (e.g. 'Stück', 'Kilogramm')
[key: string]: any;
}
interface UnitState {
/** Alphabetically sorted list of measurement units. */
units: Array<Unit>;
/** Async lifecycle status. */
status: 'idle' | 'loading' | 'succeeded' | 'failed';
/** Last error message if load failed. */
error: string | null;
}
Initial State
const initialState = {
units: [],
status: 'idle',
error: null,
};
3. Configuration & Options
new Automat(initialState, actions, { name: 'unit' });
name:'unit'persist:false
4. Actions & Methods
Access actions via unitAutomat.actions.<method>().
load(): Promise<Array<Unit>>
Fetches all units for the active company via GET /api/unit. Sorts them, updates state, and sets status to 'succeeded'.
upsert(body: object): Promise<Array<Unit>>
Creates or updates a unit via POST /api/unit/upsert. Merges the updated unit into state.units by matching on its si property, re-sorts the list, updates state, and returns the full updated list.
delete(si: string): Promise<void>
Deletes a unit by SI code via DELETE /api/unit/:si and filters it out of state.units.
reset(): void
Resets the state back to initialState.
5. Cross-Automat Subscriptions
unitAutomat tracks authentication changes automatically:
authAutomat.subscribe((auth) => {
const companyId = auth.user?.selectedCompanyId;
if (auth.status === 'authed' && companyId) {
void unitAutomat.actions.load();
} else if (auth.status === 'anon') {
unitAutomat.actions.reset();
}
});
6. Usage Examples
A. React PureComponent Unit Select Dropdown
import { PureComponent } from 'react';
import PropTypes from 'prop-types';
import unitAutomat from '../automats/unitAutomat';
export class UnitSelect extends PureComponent {
static propTypes = {
value: PropTypes.string,
onChange: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = {
units: unitAutomat.state.units,
status: unitAutomat.state.status,
};
}
componentDidMount() {
this.unsub = unitAutomat.subscribe(this, ['units', 'status']);
if (this.state.status === 'idle') {
unitAutomat.actions.load().catch(console.error);
}
}
componentWillUnmount() {
this.unsub?.();
}
render() {
const { units, status } = this.state;
const { value, onChange } = this.props;
if (status === 'loading') {
return <select disabled><option>Einheiten laden...</option></select>;
}
return (
<select value={value ?? ''} onChange={(e) => onChange(e.target.value)}>
<option value="">-- Einheit wählen --</option>
{units.map((u) => (
<option key={u.si} value={u.symbol || u.si}>
{u.symbol || u.si} {u.name ? `(${u.name})` : ''}
</option>
))}
</select>
);
}
}
B. Upserting a Unit
import unitAutomat from '../automats/unitAutomat';
async function addCustomUnit(si, symbol, name) {
try {
await unitAutomat.actions.upsert({ si, symbol, name });
console.log('Unit saved successfully');
} catch (err) {
console.error('Failed to save unit:', err);
}
}
7. Best Practices & Gotchas
- Unique Key: Units are uniquely identified by their
siproperty (not an integer ID). - Sort Guarantee: Units in
state.unitsare sorted alphabetically based onu.symbol ?? u.si.