Kondition

Aus FreiBier
Version vom 26. August 2013, 14:10 Uhr von Tbayen (Diskussion | Beiträge)

(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche

Heute habe ich mich mit Michael Plückhahn über die Erfassung von Rückvergütungen unterhalten. Bei der Überlegung, wie man das ins Datenmodell aufnimmt, kam mir ein alter Gedanke wieder hoch, den ich in älteren Überlegungen zu einem Datenmodell schonmal niedergeschrieben hatte. Ich möchte ihn hier nochmals in Worte fassen.

Im Grunde geht es um die Erkenntnis, das es bei der Kalkulation eines Artikels sowie damit verbundenen Kenngrößen wie Einkaufskosten, Netto-Einkaufspreis, Rabatten, Deckungsbeitrag etc. eine Vielzahl von Eigenheiten gibt, die man in einem idealen Datenmodell alle zusammenfassen kann. Dadurch, das wir mit relativ vielen verschiedenen Lieferanten und mit noch viel mehr verschiedenen Kunden zusammenarbeiten und dadurch, das immer mal wieder neue Arten von Rabatten, Anreizen, Vergünstigungen etc. in Mode kommen (und hinterher wieder schwer aus dem Markt zu bekommen sind), ergibt sich eine Vielzahl von Möglichkeiten. Ich nenne alle diese Besonderheiten "Konditionen". Bevor man hier hingeht und hinter jeder Besonderheit und jeder Idee eines geisteskranken PKS-Systems eines Lieferanten hinterherprogrammiert, erscheint es einfacher, zu versuchen, eine eierlegende Wollmilchsau zu schreiben, mit der man alle Möglichkeiten in einem System abdecken kann.

Inhaltsverzeichnis

Was soll erfasst werden?

Grundlage des Systems soll sein, das man einen Preis ausgehend von einer vorherigen Basiskondition immer weiterentwickeln kann und so zu einer Kalkulationsreihe kommt, die irgendwann einen Gewinn ergibt. Ein Beispiel:

  • Listenpreis - Der Preis, der zuerst mal in einer Preisliste erscheint, aber meistens nichts zu sagen hat
    • Ein Kasten Bier hat ab Brauerei einen Listenpreis von X €
  • Ab-Rampe-Preis - Der Preis, der sich nach Abzug aller Direktrabatte aus der Rechnung ergibt
    • Weil ich ein Hauptgroßhänder bin, erhalte ich hierauf einen Funktionsrabatt von 5,- €/hl
    • Da ich meine Absätze an den Hersteller melde, bekomme ich eine Meldevergütung von 1%
    • Wenn ich mein Leergut sortiert zurückgebe, bekomme ich noch eine Sortiervergütung von -,10 €/Kasten
    • Da ich unverzüglich per Bankabbuchung bezahle, erhalte ich Skonto von 2%
  • Frei-Hof-Preis
    • Meine Spedition nimmt X € für die Strecke und transportiert dafür 32 Paletten (mit je 40 Kästen bzw. 12 Fässern)
    • Ich bezahle noch eine Maut-Rechnung
  • Einkaufspreis
    • nachgelagerte EK-Konditionen: Ich erhalte von der Brauerei vierteljährlich nochmals 0,20 € für meine Marktbearbeitung
  • Einstandskosten - Der Preis zu dem ich die Ware bei optimaler Abwicklung mindestens abgeben muss, wenn ich nicht zulegen will.
    • Ich habe bestimmte Kosten, um die Ware einzulagern und habe ausgerechnet, daß das -,50 €/Kasten entspricht
    • Ich habe für einen Artikel einen besonderen Aufwand, weil er extra gepflegt und beworben werden muss
    • Ich muss die Fixkosten und Abschreibungen meines Betriebes auf meine Stückkosten umrechnen
  • Preisgruppenpreis
    • Je nach Kundengruppen gibt es einen bestimmten Kalkulationsaufschlag pro Kasten, pro Hektoliter (bei Fässern) oder prozentual (üblich z.B. bei Wein)
  • Sonderpreis
    • Es gibt Artikelrabatte, die direkt am Preis des Artikels abgezogen werden
    • Manche davon werden extra angezeigt, manche einfach still und leise abgezogen
    • Natürlich gibt es hier auch Sonderpreise, die nicht von irgendeinem Grundpreis her berechnet, sondern einfach gesetzt werden
    • Auch diese Rabatte können Mengenabhängig sein - sogar Mengenabhängig von einer Gruppe von Artikeln (Bei 6 Kästen Cola/Fanta/Sprite gibt es einen gratis)
    • Manche Kunden sollen ganz einfach den gleichen Preis wie ein anderer Kunde bekommen (z.B. bei mehreren Filialen)
  • Rechnungsrabatte
    • Es gibt auch Rabatte, die erst an der Rechnung abgezogen werden wie z.B. ein Skonto oder ein Mengenrabatt
    • Manche Händler nehmen einen Logistikaufschlag von z.B. 5,- €/Lieferung (also pro Rechnung)
  • nachgelagerter Rabatt
    • Am Ende des Jahres schreiben wir vielen Kunden nochmals eine Rückvergütung pro hl, Kasten oder manchmal auch prozentual gut
    • Wir bekommen aber auch oft eine jährliche (oder qurtalsweise oder monatliche) Beteiligung von einem Hersteller pro hl oder auch pro Füllung
  • sonstige Kosten und Leistungen
    • Manchmal zahlt man einem Kunden einen Zuschuß zu einer Werbung oder zu einer Einrichtung oder ähnliches. Diese Zuschüsse sind oftmals nicht direkt einem einzelnen Umsatz zuzuordnen, sondern werden z.B. auf ein Jahr gerechnet.

Hier ergeben sich natürlich auch einige bekannte kalkulatorische Größen: Das Ergebnis nach dem nachgelagerten Rabatt ist dann der Netto-Preis. Die Differenz zwischen Einstandskosten und dem Nettopreis ist der Deckungsbeitrag 1, zwischen Einstandskosten und dem Ende der Liste ist der Deckungsbeitrag 2.


Datenstruktur

Wie kann nun eine Struktur aussehen, die all das abbildet? Ich stelle mir ein einzelnes Objekt für eine Kondition vor, das alle Varianten abbilden kann. Diese Konditionen werden dann anhand verschiedener Kriterien in eine feste Reihenfolge oder Hierarchie gebracht, so das sie sich für einen bestimmten Kunden und Artikel in einer fixen Reihe auf jeweils ein anderes Konditionsobjekt (Elternobjekt) beziehen können (ein Kalkulationsschritt baut auf dem vorherigen auf).

Um einen klaren Aufbau und eine klare Kalkulationsgrundlage schaffen zu können, sollten alle Konditionen auf einen einzelnen Artikel umgerechnet werden können. Manchmal ist das aber nicht so einfach. So sollte z.B. ein Zuschuß, der für eine Werbung gezahlt wird, im Prinzip auf den gesamten Jahresumsatz eines Kunden umgerechnet werden. Der ist ja aber während des Jahres noch nicht bekannt (obwohl ich natürlich schon vorher eine gewisse Kalkulation vornehmen möchte). Mein Gedanke hierzu ist, das man derartige Leistungen entweder vorher irgendwie nach einer Schätzung umrechnet (vor Eingabe des Konditionsobjektes oder innerhalb?!?) oder diese am Ende der Kalkulationskette sammelt, um sie als einen letzten, optionalen Schritt z.B. erst bei einer Gesamtbetrachtung eines Kunden in die Kalkulation aufzunehmen.

Ein Teil der Felder beschäftigt sich mit dem Gültigkeitsbereich einer Kondition, d.h. für welchen Kunden, welche Artikel und zu welcher Zeit gilt sie.


Umsetzung

Ich möchte versuchen, die Datenstruktur von Anfang an so umfangreich wie möglich anzulegen. Welche Funktionen dann konkret implementiert sind, wird dann von Fall zu Fall erweitert.


Datenmodell

Tabelle BAY_Condition:

Standardfelder aus ADempiere

  • AD_Client_ID
  • AD_Org_ID
  • Created
  • CreatedBy
  • Updated
  • UpdatedBy
  • Name - Kurzbezeichnung einer Kondition, die z.B. auf einer Rechnung angedruckt werden kann
  • Description - ausführlichere Beschreibung, falls eine Kondition etwas komplizierter zu erklären ist
  • IsActive - Konditionen können hiermit außer Kraft gesetzt werden

Gültigkeitsbereich

Kunden bzw. Liste / Gruppe von Kunden

Grundsätzlich ist es hier denkbar, das ich eine Liste von Kunden (als eigene Untertabelle) und eine Liste von Kundengruppen (auch als eigene Untertabelle), z.B. als Preisgruppe nutze. Dies habe ich z.B. bei den Meldungsregeln so gemacht. Hierdurch werden allerdings entsprechende SQL-Ausdrücke recht kompliziert und im Grunde genommen läuft es doch immer darauf hinaus, das ich einen oder mehrere Kunden anspreche, also: "eine Gruppe". Da ich bereits Kundengruppen beliebig definieren kann, sollte es vielleicht sogar reichen, wenn man nur eine einzige Kundengruppe angeben kann. Andererseits würde die Möglichkeit, mehrere Gruppen anzugeben, die Verwaltung sicherlich vereinfachen. Auf der anderen Seite kommt der Fall recht häufig vor, das man eine Kondition (z.B. einen Sonderpreis) für einen einzigen Kunden eingibt. Da ist das Herumgegruppe recht aufwändig. Eine weitere Besonderheit ist, das manche Konditionen für ein bestimmtes Gastronomie-Objekt gelten, d.h. für alle Kunden, die hintereinander eine Gaststätte betreiben. Man könnte dafür einen Objekt-Eintrag machen oder für jedes Objekt jeweils eine Gruppe anlegen.

Mein Vorschlag ist vorläufig folgendes:

  • C_BPartner_ID - um einen Kunden anzugeben, auf dessen Umsätze sich die Kondition bezieht (kann NULL sein)
  • BAY_ConditionCustomer - eine n:m Untertabelle, um eine Liste von Kundengruppen angeben zu können. In der GUI sollte es eine ordentliche Möglichkeit geben, dabei eine eigene Gruppe für eine Kondition anzulegen und mehrere Kunden aufzunehmen

sind beide Felder NULL, bezieht sich die Kondition auf alle Kunden

Artikel bzw. Gruppe von Artikeln

Hier gilt das für Kunden gesagte ähnlich. Mein Vorschlag ist folgender:

  • M_Product_ID - um einen einzelnen Artikel auszuwählen (kann NULL sein)
  • BAY_ConditionProduct - eine n:m Untertabelle, um eine oder mehrere Artikelgruppen angeben zu können.

sind beide Felder NULL, bezieht sich die Kondition auf alle Artikel

zeitliche Einschränkung

Ab wann und bis wann gilt die Kondition?

  • StartDate
  • EndDate


Sortierung der Konditionen

Als erstes hätte ich gerne eine Einordnung in eine Konditionsebene. Diese Ebenen entsprechen grob dem oben dargestellten Aufbau, also: "abRampe", "Einkaufsrabatt", "Fracht", "Lagerumschlag", "interneKosten", etc.... Diese Ebenen brauchen einen ansehnlichen Namen, aber auch einen numerischen Schlüssel (in ADempiere am besten das "Value" Feld), um sie sortieren zu können. Dabei sollte man noch Platz für Erweiterungen lassen. Haben wir uns einmal entschieden, das der Einkaufspreis mit "6" eingeordnet angegeben ist, wird dieser Wert überall im Programm verwendet sein und kann also später nicht mehr angepasst werden. Also setzen wir das auf "600".

Man kann für dieses Konditionslevel entweder eine weitere Tabelle oder aber eine Listenreferenz (das bedeutet in ADempiere sowas wie ein enum) benutzen. Da es sich nur um eine reine Value-Name-Beziehung handelt, könnte eine Listenreferenz ausreichen. Das vereinfacht auch spätere SQL-Abfragen. Für Auswertungen außerhalb des normalen ADempiere-Betriebs (insbesondere in JasperReports) wäre dann aber dennoch eine Tabelle nötig, die die Zahlen in der Datenbank in Texte übersetzt. Andererseits muss es diese Tabelle in den Weiten der ADempiere-Datenbank ja bereits geben, wenn ich das dort konfiguriert habe...

Ich weiss nicht genau, ob es möglich ist, in ADempiere ein anderes Feld als die interne (zumeist unsichtbare) ID als Fremdschlüssel zu benutzen. Soweit ich weiss, geht das leider nicht, das bleibt aber auszuprobieren.

Darüberhinaus braucht man bei der Fülle der Gedanken, die ich oben angegeben habe, aber wahrscheinlich noch ein zusätzliches Sortierfeld, um Konditionen in eine einheitliche Reihenfolge zu bringen.

  • Tabelle BAY_ConditionLevel (nur als Hilfstabelle, voll eingebunden oder aus dem ADempiere Dictionary extrahiert als Subquery oder View)
  • Level (oder ggf. BAY_ConditionLevel_ID)
  • SortOrder

Grundsätzlich ergibt sich aus der Sortierung der Konditionen ein automatischer Aufbau einer Kalkulation. Dennoch möchte man manchmal die Reihenfolge ändern, abkürzen oder Punkte überspringen. Beispiel: Es gibt für alle Kunden der Gruppe "Gastronomie" einen Logistikaufschlag von 10,- €/hl. Diese Kondition gehört in das Level "Preisgruppenpreis". Nun habe ich aber einen Kunden, der sehr einfach zu beliefern ist und meinem Fahrer beim Abladen hilft. Dieser soll einen Logistikaufschlag von nur 5,- €/hl erhalten. Also muss ich die globale Kondition irgendwie "abschalten" bzw. "überspringen" können. Hierzu kann man zwei Felder machen, mit denen man abngeben kann, auf welcher Sortierebene die Basiskondition dieser Kondition liegt.

  • BaseLevel
  • BaseSortOrder

Sind diese Felder NULL, wird einfach die nächst höhere Kondition in der Sortierreihenfolge ausgewählt.


Besondere Einschränkungen

Eine klassische Kondition ist ein Mengenrabatt. Dieser kann pro Produkt aber auch pro Produktgruppe oder auch für die gesamte Abnahme gelten. Außerdem kann er pro Lieferung oder auch für einen Zeitraum gelten.

Um anzugeben, für welche Produkte die Kondition gilt, gilt wieder das oben über Artikel und Artikelgruppen gesagte. Da Mengenrabatte aber nicht ständig vorkommen und da ich das Datenmodell nicht mehr als nötig aufblasen möchte, gebe ich hier nur eine einzige Gruppe an. Das erlaubt, hier beliebige Artikel rein- oder rauszutun. Die Kombination von Gruppen ist allerdings nicht nötig und muss dann von Hand nachgebildet werden.

  • MinimumInterval - enum-Wert: delivery, month, quarter, year (oder NULL deaktiviert die Minimum-Funktion)
  • MinimumValue - Minimumwert, um den es geht
  • MinimumProductGroup - Artikelgruppe, wenn nicht alle Artikel zusammengefasst werden sollen

Manche Händler haben einen Aufschlag für Einzelflaschen. Für mich persönlich ist das jetzt nicht sooo wichtig, ich möchte das aber hier mit aufführen. Außerdem gibt es noch weitere Bedingungen, die für manche Konditionen erfüllt sein müssen. Bestellt der Kunde auf seinen ordentlichen Liefertag, bekommt er einen Extra-Rabatt, zahlt er per Bankabbuchung, bekommt er einen besonderen Rabatt, nutzt er den Notdienst, zahlt er einen Aufpreis, etc... Letztlich sind das alles besondere Bedingungen, die irgendwie speziell im Programm verdrahtet sein müssen. Dennoch sollte das System flexibel genug sein, das man später weitere Bedingungen hinzufügen kann.

Ich denke, hier kann man am besten ein Textfeld nehmen, das einen kuerzen Schlüsselwert für eine Sonderbedingung enthält, die erfüllt sein muss, damit diese Kondition gezogen wird. In ADempiere kann man das dann als Listenreferenz (also als enum-Feld) einbinden und in den Beschreibungstext eine längere Beschreibung einstellen.

  • Requirement - enum (später erweiterbar), z.B.: singleunit, deliveryday, bankdirect


Beschreibung der Kondition

Als grundsätzlichen Konditionstyp sind denkbar: Fixpreis in Euro, Aufschlag in Euro oder Aufschlag in Prozent. Hierfür sollte man eine Listenreferenz (enum-Feld als String in der Datenbank) verwenden.

Ebenso wichtig ist, auf welche Maßeinheit eines Artikels sich die Kondition bezieht. Auch hier bietet sich ein enum-Feld an. Denkbare Werte sind pro Stück, Füllung, Hektoliter, Lieferung, Jahr.

In den allermeisten Fällen

  • ConditionType - fix, increase, procent
  • Amount - Der eigentliche Betrag (je nach ConditionType entweder in Euro oder in Prozent)
  • BruttoNetto - Fällt Umsatzsteuer an oder ist "Amount" bereits ein Bruttobetrag? ('B', 'N' NOT NULL)
  • UnitOfMeasure - item, unit, hl, delivery, month, year


Wohin fliesst die Kondition

Im allgemeinen geht eine Kondition auch an den Kunden, der den entsprechenden Umsatz tätigt. In manchen Fällen ist das aber anders. Ein Handelsvertreter erhält eine Provision, ein Hauseigentümer erhält eine Rückvergütung oder eine Brauerei gibt uns eine Beteiligung. Ist das der Fall, brauchen wir ein Feld, das angibt, wer Begünstigter (oder Benachteiligter, je nachdem) der Knodition ist:

  • Beneficary
Meine Werkzeuge
Namensräume

Varianten
Aktionen
Navigation
Werkzeuge