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

5.5 KiB

supplierAutomat

Source File: 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

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

const initialState = {
  suppliers: [],
  status: 'idle',
  error: null,
};

3. Configuration & Options

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:

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

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

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.