Produkte
Zweck
Der Artikel- und Leistungsstamm haelt physische Produkte, Dienstleistungen und Calculation-Products (Vorlagen aus mehreren SalesDocumentItems). Produkte werden in Verkaufs- und Einkaufsbelegen referenziert.
Drei Auspraegungen über Flags:
shopProduct— für den Web-Shop sichtbar.priceOnRequest— Preis auf Anfrage;sellPricewird im Output ausgeblendet.isCalculationProduct— Vorlagen-Container; statt Lieferanten-Tab erscheint der Tab Calculation Items.
Voraussetzungen
Berechtigungen (CASL)
Frontend-Page-Guard:
| Action | Subject | Keycloak-Rolle |
|---|---|---|
view | FE_Product | — |
view | Product | APP_SPEAMCORE_VIEW_PRODUCT |
Tab-Subjects (conditional):
| Tab | Sub-Pfad | Subject | Sichtbar wenn |
|---|---|---|---|
| Attribute | /products/:id/attributes | AttributeParent:view | immer |
| Lieferanten | /products/:id/suppliers | ProductSupplier:view | isCalculationProduct = false |
| Calculation Items | /products/:id/calculation-items | SalesDocumentItem:view | isCalculationProduct = true |
| Komponenten | /products/:id/components | ProductComponent:view | immer |
| Dokumente | /products/:id/documents | Document:view | immer |
API-Datenzugriff:
| Action | Subject | Endpoint | Keycloak-Rolle |
|---|---|---|---|
view | Product | GET /api/products, GET /api/products/:id | APP_SPEAMCORE_VIEW_PRODUCT |
create | Product | POST /api/products | APP_SPEAMCORE_CREATE_PRODUCT |
update | Product | PATCH /api/products/:id | APP_SPEAMCORE_UPDATE_PRODUCT |
delete | Product | DELETE /api/products/:id | APP_SPEAMCORE_DELETE_PRODUCT |
view + view | Product + SalesDocumentItem | GET /api/products/:productId/calculation-items | APP_SPEAMCORE_VIEW + VIEW_PRODUCT + SALES_DOCUMENT_ITEM |
update + create | Product + SalesDocumentItem | POST /api/products/:productId/calculation-items | APP_SPEAMCORE_UPDATE + CREATE_PRODUCT + SALES_DOCUMENT_ITEM |
update + update | Product + SalesDocumentItem | PATCH /api/products/:productId/calculation-items/:itemId | APP_SPEAMCORE_UPDATE + UPDATE_PRODUCT + SALES_DOCUMENT_ITEM |
update + delete | Product + SalesDocumentItem | DELETE /api/products/:productId/calculation-items/:itemId | APP_SPEAMCORE_UPDATE + DELETE_PRODUCT + SALES_DOCUMENT_ITEM |
Schritt-für-Schritt-Anleitung
Produkt anlegen
- Produkte (
/products) → + Neu. - Pflegen Sie Pflichtfelder: Titel, Mengeneinheit, ggf. Warengruppe und Hersteller.
- Setzen Sie
priceOnRequestodersellPrice. - Ggf.
shopProduct = truefür den Web-Shop. - Speichern.
Calculation-Product anlegen
- Beim Anlegen:
isCalculationProduct = truesetzen. - Auf der Detailseite den Tab Calculation Items öffnen.
- Beliebige
SalesDocumentItems als Vorlage hinterlegen — diese werden bei Verwendung des Produkts in einen Verkaufsbeleg übernommen.

Toolbar (Detail-Seite)
Schlanke Toolbar oben rechts:
| Icon | Aktion (aria-label) | CASL | Wirkung |
|---|---|---|---|
| ← | Zurückgehen | — | Zurück zur Liste. |
| 🏠 | Zur Startseite gehen | — | Springt auf das Dashboard / /. |
| ⏮/◀/▶/⏭ | Pagination | — | Navigation durch die gefilterte Liste — Massen-Bearbeitung ohne Liste-Sprung. |
Globale Floating-Drawer (links)
Wie auf jeder Detail-Seite verfuegbar — siehe Floating-Quickbar:
- KAL. (Mini-Kalender)
- ZEIT (Persoenliche Wochen-Arbeitszeit)
- ARBEIT (Eigene bevorstehende Aufträge)
UI-Elemente
Button: „+ Neu"
Listenseite. Erfordert create:Product.
Felder und Eingaben (Auswahl)
| Feldname | Pflicht | Datentyp | Wirkung beim Ausfuellen | Voraussetzung |
|---|---|---|---|---|
productNo | nein | String | Identifikation in Belegen, Lager und Shop. | Eindeutigkeit pro Mandant empfohlen. |
title | ja | String | Anzeigetext in allen Selects, Belegen und im Shop. | — |
description | nein | TEXT (HTML-Editor) | Lange Beschreibung in Belegen, PDFs und im Shop sichtbar. | HTML-Editor-Komponente. |
unitId | ja | UUID | Mengeneinheit für Beleg-Positionen, Lager-Bestand und Reports. | view:Unit; Master-Sync mind. einmal gelaufen. |
productTypeId | nein | UUID | Klassifizierung für Reports und ggf. spezifische Felder. Invisible bei isCalculationProduct=true. | view:ProductType. |
productGroupId | nein | UUID | Warengruppe — beeinflusst Mandanten-Sichtbarkeit über ProductGroupProfile. | view:ProductGroup; passendes Profil aktiv. |
manufacturerId | nein | UUID | Hersteller-Verknuepfung. | view:Manufacturer. |
manufacturerProductNo | nein | String | Hersteller-Artikelnummer für Bestellungen. | — |
hsCodeId | nein | UUID | Zolltarifnummer (HS-Code). Pflicht für Export. | Master-Sync mit HS-Codes. |
countryOfOriginId | nein | UUID | Ursprungsland für Zoll und Lieferdokumente. | view:Country. |
customsTariffNumber | nein | String | Freie Zoll-Tarifnummer. | — |
length / width / height | nein | Decimal | Abmessungen — für Versand und Lager. | — |
netWeight / grossWeight | nein | Decimal | Gewichte für Versand und Pflichtangaben. | — |
ean | nein | String | EAN-Barcode für Shop und Scanner. | — |
sellPrice | ja | Decimal | Verkaufspreis. Verborgen bei priceOnRequest=true. Validierung gegen ProductSpecialCondition blockt Werte unter dem Fixed-Discount. | Keine widersprechende Sonderkondition. |
priceOnRequest | ja | Boolean | Wenn true: Preis wird im Shop und in Belegen ausgeblendet, manueller Eingriff bei Angebot noetig. | — |
shopProduct | ja | Boolean | Wenn true: Produkt erscheint im Shop. Triggert afterUpdate-Hook, der Document.isPublic der Produkt-Bilder synct. | Mandanten-Profil hat passende Warengruppe freigeschaltet. |
isCalculationProduct | ja | Boolean | Wenn true: Tab-Struktur wechselt (Calculation Items statt Lieferanten). Vorlagen-Container für mehrere SalesDocumentItems. | Migration: nicht trivial nach Anlage zurueckwechseln. |
accountId | nein | UUID | Erloeskonto für Buchungen aus Verkauf. | view:Account. |
expenseAccountId | nein | UUID | Aufwandskonto für Buchungen aus Einkauf. | view:Account. |
marketplaceEnabled | nein | Boolean (Default false) | Bietet das Produkt im Produkt-Marktplatz anderen Mandanten an. | Mindestens ein Lieferant mit EK > 0 muss hinterlegt sein (sonst Fehler). |
marketplacePrice | nein | Decimal | Marktplatz-/Katalogpreis. Die Marge ergibt sich aus marketplacePrice − günstigster EK. | nur relevant bei marketplaceEnabled = true. |
masterId | — | UUID, nullable (read-only) | Roundtrip-ID vom Admin-Master — kennzeichnet ein bezogenes (gemirrortes) Produkt. | wird beim Push/Pull gesetzt. |
syncSellPriceToProvider | nein | Boolean | Verkaufspreis-Änderungen an den Anbieter zurücksynchronisieren (bidirektional). | — |
shortDescription | nein | TEXT (HTML) | Kurzbeschreibung — für kompakte Listen und Shop-Teaser. | — |
packagingUnit | nein | Decimal(12,3) | Verpackungseinheit auf Produkt-Ebene (je Lieferant separat überschreibbar). | — |
hasTieredPrices | nein | Boolean | Aktiviert mengengestaffelte Preise und öffnet den Staffelpreis-Editor. | — |
tieredPrices | nein | JSON | Mengengestaffelte Verkaufspreise [{fromQuantity, sellPrice}]. Einkaufsseitige Staffeln liegen je Lieferant, siehe Produkt-Lieferanten-Preise. | hasTieredPrices = true. |
baseProductId | nein | UUID (Self-Ref) | Verweis auf ein Basis-Produkt — kennzeichnet eine Variante. | — |
Lieferanten-Preise & Marge: EK/VK/Rabatt und der Deckungsbeitrag je Bezugsquelle werden im Tab Lieferanten gepflegt — siehe Produkt — Lieferanten und das Konzept Produkt-Lieferanten-Preise.
Produkt-Marktplatz (seit Juni 2026)
Analog zum Kurs-Marktplatz können Produkte mandantenübergreifend angeboten und bezogen werden — direkt im Produkt-Formular, ohne eigene Seite.
- Anbieten:
marketplaceEnabled = truesetzen und einenmarketplacePricehinterlegen. Voraussetzung ist mindestens ein Lieferant mit Einkaufspreis — das Formular zeigt dazu den günstigsten effektiven EK (über alle Lieferanten-Rabatte gerechnet) als Kalkulationshilfe an. - Beziehen / Re-Provider: Ein bezogenes Produkt (
masterId ≠ null) wird vom Anbieter gepflegt; es kann — wie beim Kurs — selbst wieder im Marktplatz angeboten werden. - Synchronisation: Push/Pull laufen über den Admin-Master; der lokale Marktplatz-Preis wird vom Anbieter-Sync nicht überschrieben. Eine Umsatz-/Abrechnungs-Übersicht wie beim Kurs-Marktplatz gibt es (noch) nicht.
Workflows und Zustaende
Produkte haben kein Status-Feld — Aktivität wird über shopProduct (sichtbar im Shop) oder über Sonderkonditionen geregelt.
Wiederverwendbare Konzepte
- Polymorpher Parent-Pattern —
DocumentParent.parentType = 'Product'für Anhänge. - Berechtigungen verstehen (CASL)
Verknuepfungen zu anderen Modulen
- Mengeneinheit (Master-Sync) —
unitId. - Warengruppe —
productGroupId(mit Client-Scope-Filterung überClientProductGroupProfile). - Hersteller —
manufacturerId. - Sonderkonditionen —
ProductSpecialCondition.productId. Validierung inbeforeCreate/beforeUpdate:sellPricedarf nicht unter den fixed-discount-Wert fallen. - Verkaufspositionen — Calculation-Items sind
SalesDocumentItems mit Vorlagen-Verknuepfung. - Lieferanten —
ProductSupplier(1:N) — nur beiisCalculationProduct=false. - Kurs-Marktplatz — Produkte können analog mandantenübergreifend angeboten/bezogen werden (Produkt-Marktplatz).
- Komponenten / Stueckliste —
ProductComponent. - Dokumente —
DocumentParent(parentType = 'Product');Document.isPublicsynct mitshopProduct.
Häufige Fehler und Lösungen
| Fehler | Lösung |
|---|---|
sellPrice unter Sonderkondition abgelehnt | ProductSpecialCondition setzt fixed discount. Preis erhoehen oder Kondition anpassen. |
| Tab „Lieferanten" fehlt | isCalculationProduct = true — Lieferanten und Calculation Items sind exklusiv. |
| Produkt erscheint nicht im Shop | shopProduct = false oder Warengruppe nicht im Client-Profil freigegeben. |
| Bilder erscheinen nicht im Shop | Document.isPublic = false. Wird automatisch synchronisiert, wenn shopProduct gesetzt wird (afterUpdate-Hook). |
| Preise eines Mandanten weichen ab | ProductGroupProfileProductGroupManufacturer.discount wirkt mandantenspezifisch (afterFind-Hook). |
API/Schnittstellen
| Methode | Endpoint | Zweck | CASL |
|---|---|---|---|
GET | /api/products | Liste mit Client-Scope und Discount | view Product |
GET | /api/products/:id | Detail | view Product |
POST | /api/products | Anlegen | create Product |
PATCH | /api/products/:id | Ändern | update Product |
DELETE | /api/products/:id | Soft-Delete | delete Product |
GET | /api/products/:productId/calculation-items | Vorlagen lesen (nur isCalculationProduct=true) | view Product + view SalesDocumentItem |
POST | /api/products/:productId/calculation-items | Vorlage anlegen | update Product + create SalesDocumentItem |
PATCH | /api/products/:productId/calculation-items/:itemId | Vorlage ändern | update Product + update SalesDocumentItem |
DELETE | /api/products/:productId/calculation-items/:itemId | Vorlage löschen | update Product + delete SalesDocumentItem |
Versionshinweise
- 2026-06-22: Neue Produkt-Felder
shortDescription,packagingUnit,hasTieredPrices,tieredPrices(Verkaufsstaffeln) undbaseProductId(Variante) ergänzt; Verweis auf die Lieferanten-Preis-Engine (Konzept). Verifiziert anproduct.model.ts. - 2026-06-12: Produkt-Marktplatz dokumentiert — neue Felder
marketplaceEnabled,marketplacePrice,masterId,syncSellPriceToProvider; mandantenübergreifendes Anbieten/Beziehen analog Kurs-Marktplatz, Voraussetzung Lieferant-mit-EK, günstigster-EK-Anzeige, Re-Provider. Verifiziert anproduct.model.ts(Marketplace-Invariante) undproductMarketplaceSync.service.ts. - 2026-04-29: Initiale Veroeffentlichung.