# `deliveryEditorAutomat` > **Source File**: [`src/automats/deliveryEditorAutomat.js`](file:///home/seb/src/dashfront/src/automats/deliveryEditorAutomat.js) > **Registration Keys**: `'delivery-draft'` (draft editor) / `'delivery-{docId}'` (saved delivery editors) --- ## 1. Overview & Purpose `deliveryEditorAutomat` powers the inbound delivery reception, receipt reconciliation, and invoice editor (`/lieferung/neu` and `/lieferung/:deliveryId`). It manages complex multi-line receipts, OCR attachments, and automatic matching of supplier articles to internal catalog SKUs. ### Core Responsibilities - **Multi-Instance Architecture**: Supports an unsaved draft editor (`deliveryDraftAutomat`) as well as isolated, named automat instances for each open delivery (`getDeliveryEditorAutomat(docId)`). - **Line Item Management**: Adding, editing, reordering, and deleting delivery lineitems (`name`, `sku`, `amount`, `pu`, `unit`, `netprice`, `taxrate`). - **Supplier Article Auto-Matching**: As lines are typed or parsed from receipts, automatically queries `GET /api/supplier/:id/articles` and indexes them by supplier SKU, internal SKU, and barcode EANs. - **Unlinking & Re-matching**: Supports unlinking mistaken supplier-article associations (`unlinkSupplierArticle`) and refreshing lines. - **Attachment & Receipt Pipelines**: Manages uploaded PDFs and image receipts, OCR parsing state, and attachment previews. - **Reactive Navigation Badges**: Used by `src/nav.js` to show live uncommitted line badges on the sidebar navigation items. --- ## 2. State Structure ```ts interface DeliveryLine { key: string | number; // Stable client-side unique identifier name: string | null; // Article description sku: string | number | null; // Supplier SKU or internal SKU amount: number | null; // Delivered quantity pu: number | null; // Price unit (e.g. 1, 100) unit: string | null; // Unit of measure (e.g. 'Stück', 'kg') netprice: number | null; // Net unit price taxrate: number | null; // VAT rate (e.g. 19.0) } interface SupplierArticle { id?: string | number; sku?: string | number | null; article?: { sku?: string | number | null; eans?: Array; articleean?: Array; }; } interface DeliveryEditorState { deliveryEntities: Array; // Available suppliers/parties id: string | number | null; deliveryId: string | number | null; // Alias of id date: string | null; // ISO delivery date string deliveryEntity: string | number | null; // Selected supplier ID partyId: string | number | null; // Alias of deliveryEntity externalNumber: string | null; // Supplier invoice / delivery slip number external_number: string | null; // Alias of externalNumber number: string | null; // Internal receipt number type: string | null; lines: Array; // Editable line items lineitems: Array; // Alias of lines notes: Array; // Delivery notes attachments: Array; // PDF/image attachments files: Array; // Alias of attachments supplierArticles: Array; // Loaded supplier articles supplierArticlesMap: Record; // Lookup map by SKU / EAN supplierArticlesByLineKey: Record; // Matched article per line supplierArticlesLoading: boolean; // Pending article lookup loadingLineKeys: Record; // Per-line loading state busySupplierArticleLineKeys: Record; // Lines with pending unlinks supplierArticlesError: string | null; unmatchedSupplierName: string | null; // Parsed supplier name without match } ``` --- ## 3. Factory & Instance Resolution ### `createDeliveryEditorAutomat(options?: { name?: string } | string): Automat` Factory function creating a new delivery editor automat or retrieving an existing one by name. ### `deliveryDraftAutomat` / `deliveryEditorAutomat` The singleton automat with name `'delivery-draft'` used for creating new, unsaved deliveries (`/lieferung/neu`). ### `getDeliveryEditorAutomat(docId: string | number | null): Automat` Returns `deliveryDraftAutomat` when `docId` is null/undefined. If `docId` is provided, returns or instantiates `'delivery-${docId}'`. --- ## 4. Helper Utilities - **`createEmptyLineitem(line?: Partial): DeliveryLine`**: Returns a normalized lineitem with a generated unique `key`. - **`buildSupplierArticlesMap(articles, existingMap?): Record`**: Indexes articles by supplier SKU, internal SKU, and EANs (storing both original and lowercased keys for case-insensitive lookup). --- ## 5. Actions & Methods Actions can be invoked directly from the automat instance: ### Header & Supplier Details - **`setId(id)`**: Sets `id` and `deliveryId`. - **`setDeliveryEntities(entities)`**: Sets available supplier entities dropdown list. - **`setDelivery(delivery)`**: Replaces the full delivery state, normalizes aliases (`lines`/`lineitems`, `attachments`/`files`), and immediately triggers `fetchSupplierArticles` if a supplier is set. - **`setDate(date)`**: Sets delivery date. - **`setDeliveryEntity(entityId)` / `setPartyId(partyId)`**: Sets the active supplier and refreshes supplier articles for the current lines. - **`setExternalNumber(externalNumber)`**: Sets external supplier invoice/slip number. - **`setNumber(number)`**: Sets internal receipt number. - **`setType(type)`**: Sets delivery document type. - **`setNotes(notes)`**: Sets delivery notes array. - **`setUnmatchedSupplierName(name)`**: Sets OCR-extracted supplier name before an entity is matched. ### Line Items - **`setLines(lines)` / `setLineitems(lines)`**: Sets the entire lines array. - **`addLineitem(line?)` / `addLine(line?)`**: Appends a new line item. - **`updateLineitem(payload)` / `updateLine(payload)`**: Updates line fields identified by `index` or `key`. - **`removeLineitem(payload)` / `removeLine(payload)`**: Deletes a line by index or key, and cleans up its matched supplier article entry. ### Supplier Article Matching & Unlinking - **`fetchSupplierArticles(targetEntityId?, targetLines?): Promise`**: Fetches all supplier articles for the unique SKUs across the lines and builds the matching map. - **`fetchSupplierArticleForSku(sku, lineKey?): Promise`**: Loads and matches a supplier article for a single line SKU. - **`unlinkSupplierArticle(internalSku, assignmentId, lineKey?): Promise`**: Calls `DELETE /api/article/:sku/supplier/:id` to unassign an incorrect supplier article match, cleans up the map, and re-resolves the line. ### Attachments & Resets - **`addAttachment(attachment)`**: Appends a file/receipt attachment. - **`setAttachments(attachments)`**: Replaces the attachments array. - **`updateAttachment(payload)`**: Updates attachment fields by `id`. - **`removeAttachment(idOrPayload)`**: Removes an attachment by ID or index. - **`resetDelivery()`**: Clears delivery fields while preserving available supplier entities. - **`resetDeliveryEditor()`**: Resets the complete state back to `initialState`. --- ## 6. Exported Selectors Plain selectors operating on `deliveryEditorAutomat.state` by default: ```js selectDeliveryId(state?) selectDeliveryEntities(state?) selectDeliveryDate(state?) selectDeliveryEntity(state?) selectUnmatchedSupplierName(state?) selectDeliveryExternalNumber(state?) selectDeliveryNumber(state?) selectDeliveryType(state?) selectDeliveryLineitems(state?) selectDeliveryNotes(state?) selectDeliveryAttachments(state?) selectSupplierArticles(state?) selectSupplierArticlesMap(state?) selectSupplierArticlesByLineKey(state?) selectSupplierArticlesLoading(state?) selectLoadingLineKeys(state?) selectBusySupplierArticleLineKeys(state?) ``` --- ## 7. Usage Examples ### A. React `PureComponent` Delivery Lines Table ```jsx import { PureComponent } from 'react'; import deliveryEditorAutomat from '../automats/deliveryEditorAutomat'; export class DeliveryLinesTable extends PureComponent { constructor(props) { super(props); this.state = { lines: deliveryEditorAutomat.state.lines, matches: deliveryEditorAutomat.state.supplierArticlesByLineKey, }; } componentDidMount() { this.unsub = deliveryEditorAutomat.subscribe(this, (state) => ({ lines: state.lines, matches: state.supplierArticlesByLineKey, })); } componentWillUnmount() { this.unsub?.(); } handleAddLine = () => { deliveryEditorAutomat.actions.addLineitem({ amount: 1 }); }; handleSkuChange = (key, sku) => { deliveryEditorAutomat.actions.updateLineitem({ key, sku }); deliveryEditorAutomat.actions.fetchSupplierArticleForSku(sku, key); }; render() { const { lines, matches } = this.state; return (
{lines.map((l) => ( ))}
SKU Artikelname Menge Zugeordneter Artikel
this.handleSkuChange(l.key, e.target.value)} /> {l.name} {l.amount} {matches[l.key]?.article?.sku ?? 'Nicht zugeordnet'}
); } } ``` --- ## 8. Best Practices & Gotchas 1. **Always use line `key`**: Line indices shift when rows are deleted or reordered. Use `l.key` generated by `createEmptyLineitem()` for React keys and lookup indices. 2. **Draft vs Saved Delivery**: Always use `getDeliveryEditorAutomat(deliveryId)` when editing an existing delivery so edits don't contaminate the `/lieferung/neu` draft state. 3. **Sidebar badge count**: `src/nav.js` directly inspects `deliveryEditorAutomat.state.lines.length` to render draft item badges.