![]() |
Datenbank: Firebird • Version: 2.1 • Zugriff über: Dataset
Preisanpassung
Hallo,
jetzt, wo Alle die Preise anpassen, muß auch unsere Firma eine Preisanpassung anstreben. Habe in den letzten Jahren ein kleines Fakturaprogramm geschrieben und mit einem Artikelstamm. In der Artikelstamm-Tabelle steht auch der Grundpreis für den betreffenden Artikel. Jetzt kommt eine Preisanpassung in Prozent für alle Artikel. Meine Überlegungen sind folgende: 1) eine einfache Lösung wäre per SQL-Befehl den Grundpreis der Artikel der gesamten Datensätze um einen Faktor zur erhöhen. An dem betreffendem Tag wird diese Routine ausgeführt und Preise sind angepasst. Blöd ist nur, wenn man nach diesem Datum einen alten Auftrag (Rechnung) korrigieren muß, dann rechnet das Programm mit den neuen Preisen. 2) Das ganze vom Datum abhängig zu machen. Und zwar: a) die alten Preise behalten. eine Tabelle mit 2 Feldern erzeugen: Datum, Faktor. 1-Ter Datensatz: "01.01.2014, 1" . 2-ter Datensatz: "01.01.2023, 1.25". Je nach Auftragdatum den Grundpreis mit dem entsprechendem Faktor multiplizieren. b)mehrere Tabellen mit 3 Feldern: "Artikelnr, Datum, Preis" und je nach Datum in die entsprechende Tabelle springen und den Preis aufrufen. c) eine Tabelle mit "Artikelnr, Datum1, Preis1, Datum2, Preis2, Datum3, Preis3, usw." Neue Felder werden erstellt, wenn neue Preisanpassung benötigt. Sicherlich gibt es noch andere Ideen, die mir z.zt. nicht einfallen und deshalbt brauche ich Eure Hilfe. Gruß, Luckner |
AW: Preisanpassung
Deine erste Lösung ist mit Sicherheit die schlechteste wie du bereits selbst gemerkt hast.
Also ich würde dir dazu raten die Lösung 2A zu verwenden. Jedes mal wenn die Preise erhöht werden die Tabelle zu kopieren und dann zur neuen zu springen bläst die Datenbank von den Tabellen her sehr auf, das selbe bild für die andere Lösung mit Spalte pro Preis. Dann hast du irgendwann 200 Preisspalten. Je nachdem was du für ein Datenbanksystem hast, bekommst du da irgendwann auch Probleme, je nachdem wie viele Spalten eine Tabelle im System überhaupt haben darf. Wir nutzen auch die Lösung 2A allerdings mit 2 Datumspalten. Die erste ist das Datum ab wann der Preis gilt und die zweite Spalte ist das Datum bis wann der Preis gilt. Beim den aktuellen Preisen lassen wir Datum-bis dann immer auf null. Darüber kann man dann auch ganz bequem die aktuellen Preise abfragen, da man dann einfach nur alle Preise abfragt die kein Enddatum haben. |
AW: Preisanpassung
Zitat:
Ansonsten würde ich auch Lösung 2a verwenden. Aber dafür muss dann möglicherweise auch die Anwendung angepasst werden. Es sei denn, das ist so gut strukturiert, dass du eine View (oder andere Möglichkeiten der DB) genutzt hast. Das würde den Anpassungsaufwand relativ gering halten. |
AW: Preisanpassung
Zitat:
|
AW: Preisanpassung
Alle Artikel kopieren und in der Kopie den Preis anpassen?
Einfacher wäre es, wenn man die Artikel usw. revisionieren könnte (alle Artikel haben den selben Namen/Bezeichner aber sind mit einer internen Nummer/Serial mit den anderen Datensätzen verknubbelt), also alle alten Rechnungen laufen gegen die alte Revision/kopie und es somit passt es immer. |
AW: Preisanpassung
Ich möchte zu 2A noch anmerken, wenn sowieso eine Erweiterung ansteht, dann direkt noch zwei Spalten für MwSt-Änderung (mit Datumsbezug )mit aufnehmen, falls die sich auch mal ändert.
|
AW: Preisanpassung
Zitat:
|
AW: Preisanpassung
Zitat:
|
AW: Preisanpassung
Jetzt fällt mir noch auf, dass wenn nach der Preisktualisierung ein neuer Artikel angelegt wir, und dann ein aktl. Grundpreis festgelegt wird, dann wird er auch mit dem neuen Faktor berechnet und dann explodieren die Preise. Wer weiß dann schon, dass da noch ein Faktor dahinter liegt. In meinen Augen nicht so ideal.
Luckner |
AW: Preisanpassung
Zitat:
Luckner |
AW: Preisanpassung
Habt ihr seit der Anlage der Artikel nie die Preise geändert? Das Problem, das du beschreibst, muss es bereits gegeben habe. Hinzu kommt, dass Lieferanten ganz gerne unterjährig Preise anpassen.
Der Preis hat im Artikelstamm nichts verloren, der gehört im Datenmodell immer als eigene Instamz / Tabelle gepflegt. z.B. als Preisliste mit Artikelnummer, Gültig von, Gültig bis, Preis netto. Die Umsatzsteuer muss in einer eigenen Tabelle gepflegt werden (mit ähnlichem Aufbau) ggf. erweitert um Land und entsprechende Umsatzsteuer (Import). Grüße Mikhal |
AW: Preisanpassung
Sehe ich auch so, wobei ich immer nur ein Datum verwenden würde (GueltigAB oder ähnlich). Das hat zwar den Nachteil, dass die Preisabfrage eines Artikels zum Zeitpunkt X etwas umständlich wird, aber so können keine Überschneidungen oder Zeitspannen ohne Preis auftreten.
|
AW: Preisanpassung
Hallo mikhal,
nein die Preise wurden damals (Senior-Chef) als Festpreis ausgehandelt und bis heute nicht erhöht. Diese Faktura wurde damals, aufgrund von speziellen Anforderungen und Besonderheiten für einen speziellen Kunden, von mir auf Firebird-Basis geschrieben und in laufe der Zeit erweitert und angepasst. Wir haben hier noch eine andere Faktura für andere Aufträge (auch schon locker 25 Jahre alt, die ebenfalls von einem Freelancer auf Access-Basis erstellt) und die, aufgrund der weiteren User und anderen Erweiterungen, sehr langsam, nicht mehr passend und oft absturzgefährdet geworden ist. Die schreibe ich jetzt auch um auf Firebird und würde diese neuen Erkenntnisse dort ebenfalls unterbringen. Wie würde dann eine Tabelle mit Preisliste mit Artikelnummer, Gültig von, Gültig bis, Preis netto, aussehen? Gruß, Luckner |
AW: Preisanpassung
Dann erweitere deine Erkenntnisse um weitere Überlegungen:
+ Trennung von Verkaufs- und Einkaufspreisen + Verkaufspreise mit Staffeln + Einkaufspreise mit Staffeln und verschiedenen Lieferanten Wie werden Rabatte verarbeitet? Auch hier kann/sollte man nach Verkauf und Einkauf unterscheiden. Das Ganze ist ein weites Feld mit vielen Tretminen. Spreche mal mit den Einkauf und dem Verkauf, finde heraus, wie dort die Preise und damit die Rechnungen/Bestellungen behandelt werden. Letztlich muss anschließend ein eindeutiger Einkaufspreis für einen Artikel bei einer gegebenen Menge bei einem bestimmten Lieferanten mit einem möglichen Rabatt herauskommen und zwar bei den gleichen Parametern immer der gleiche. Das gilt auch beim Verkaufspreis... Grüße Mikhal PS: Lieferzeiten sind auch nicht schlecht in der Betrachtung (für den Einkauf und für den Verkauf). |
AW: Preisanpassung
Hi mikhal,
EK-Preise, VK-Preise und auch andere Sachen werden in der neuen Software sicherlich eine Rolle spielen. Ich brauche speziell für diese eine Faktura nur eine Lösung zur einer vernüftigen Preisanpassung. Auch für die Zukunft (solange es diesen, guten Kunden gibt) brauche ich diese Ideen. Hier spielen EK-Preise usw. keine Rolle. Luckner |
AW: Preisanpassung
Du brauchst eine Tabelle mit der Kunden-ID, der Artikel-ID, dem VK-Preis und dem Zeitraum der Gültigkeit.
Beim Zeitraum gibt es verschiedene Möglichkeiten. Entweder hast ein Ab-Datum, ein Bis-Datum oder beides. Ich bevorzuge die Variante, bei der beides vorhanden ist. Dann kann man z.B. einen kundengebunden Artikelpreis nicht nutzbar machen, ohne ihn zu löschen und hat trotzdem die Info, von wann bis wann er genutzt wurde. Man könnte auch ein Feld einführen, das kennzeichnet, welcher Datensatz aktiv ist. Aber das wäre im Grunde redundant, da die Info sich aus dem Zeitraum ergibt. |
AW: Preisanpassung
Zur Zeit, geht es nur um einen Kunden. Also, es spricht doch alles dafür, wie ich es unter Punkt 2) a oder b geschrieben habe?
Luckner |
AW: Preisanpassung
Zitat:
Zitat:
Kann es weitere Preisanpassungen geben? |
AW: Preisanpassung
Eine richtige Faktura sollte unterschiedliche Preise je Kunde können und natürlich müssen alle Preise historisierbar sein! Wie man das unter der Haube macht ist eigentlich egal.
Du hast im Moment ein Problem, weil du die Preise nicht historisierbar gehalten hast. Das Selbe muss auch auf der Lieferantenseite geschehen. Sprich historisierbare EK-Preise und differenziert nach Lieferant. |
AW: Preisanpassung
Diese spez. Faktura ist nur für diesen Kunden geschrieben. Wenn er mal kein Bock mehr auf uns hat, wird dieses Programm auch wieder eingestampft. In der neuen allgemeinen Faktura, die gerade am entstehen ist, werden natürlich diese Punkte alle berücksichtigt werden. Da ich aber nicht weiß, ob der Kunde die nächsten 20 Jahre mit uns arbeiten möchte, würde ich nicht unbedingt nur eine einfache Lösung haben wollen, sondern eine die möglicherweise auch später funktioniert. Wenn der Kunde, jedoch in 2 Jahren weg ist, dann ist diese Faktura auch weg. Rechnungen liegen als Papier und/oder als PDF-Datei im Archiv.
Luckner. |
AW: Preisanpassung
Eine Preisliste kann (und sollte IMHO) auch aus Kopf- und Detai-Tabelle bestehen.
Im Kopf kommt Gültigkeitsdauer, eventuell KundenId (oder -1 wenn für alle), Name der Liste etc rein. Im Detail kommt Artikel-ID mit Netto-Verkaufspreis. Nicht jeder Artikel muss in der Liste stehen, es werden einfach bei der Preisfindung alle für den Kunden gültigen Listen mit dem Artikel durchsucht und der günstigste Preis genommen. |
AW: Preisanpassung
In diesem Fall Aufschlag für alle Artikel. Ist es jetzt besser (auch für die Zukunft gedacht) in einer Tabelle pro Preisanpassung, jeweils 2 Spalten einzufügen mit DatumAb und Preis? Oder besser pro Preisanpassung eine neue Tabelle mit 2 Feldern zu erzeugen?
Luckner |
AW: Preisanpassung
Eine neue Tabelle pro Preisanpassung macht garkeinen Sinn, wie willst denn da ein gültiges SQL bauen. Muss man doch ständig seinen Code anpassen.
Wenn Du es ohne Kopf machen willst (ist dann eben nicht normalisiert), dann halt so ArtikelId PreisNetto (der Preis ohne Mwst) PreisBrutto (Preis mit Mwst, damit Du ihn ohne Rundungsungenauigkeiten als Festwert hast) GültigVon GültigBis Mwst-Satz ist preislistenunabhängig, deswegen eher ein Attribut des Artikels. |
AW: Preisanpassung
Da das ein kundenspezifischer Preis ist, würde ich die Kunden-ID mitführen. Es ist zwar im Moment nur ein Kunde, aber sollte ein weiterer dazu kommen, ist das in der Tabelle bereits berücksichtigt.
|
AW: Preisanpassung
Also, soweitich verstanden habe, pro Preisanpassung jeweils die 5 Spalten zufügen?
Luckner |
AW: Preisanpassung
Dann musst du wieder deinen Code bei jeder Preisänderung anpacken.
Die fügst in die Tabelle Preisliste für jeden Artikel einen neuen Datensatz ein mit dem neuen Preis und dem Datum gültig ab. Beim derzeit gültigen Preis in der Preisliste trägst du in Gültig bis das Datum des neuen Preises -1 ein, so es dieses Feld gibt. Dann hast du eine Chronologie der Preise ohne dass du dir jedesmal die Mühe machen musst, deinen Quelltext anzupassen. Die Preisfindung realisierst du dann über eine View oder eine Function auf der DB. Grüße Mikhal |
AW: Preisanpassung
Jetzt verstehe ich. Aber bläht sich die Tabelle bei 20.000 Artikel und 3 und mehr Preisanpassungen nicht richtig auf? Nach der 3. Anpsssung sind dann 60.000 Datensätze.
Luckner |
AW: Preisanpassung
Da macht man einen Index drauf, dann ist das kein Problem
|
AW: Preisanpassung
Index gesetzt, dann sind das etwa 16 Vergleiche bei 60.000 Datensätzen, bis der Datensatz gefunden wird, dauert bestimmt nicht lang.
Ja, die Tabelle wächst, aber dafür sind Datenbanken konstruiert. Sie sollen große Datenmengen in kurzer Zeit verarbeiten können. Und Speicherplatz war früher das teuerste, heute wird Speicherwachstum kaum noch berücksichtigt. Auch eine so alte Firebird, wie du sie verwendest, kommt schon mit riesigen Datenmengen in angemessener Zeit zurecht - bei entsprechendem RAM und Plattenplatz. Grüße Mikhal |
AW: Preisanpassung
Vielen Dank,
dann werde ich das so machen. In der neuen Software werde ich diese Preisgestalltung ähnlich aufbauen. Auch die anderen Vorschläge (Lieferanten, Mwst-Sätze, usw.) berücksichtigen. Gruß, Luckner |
AW: Preisanpassung
Jetzt bitte noch eine Hilfestellung,
habe jetzt eine neue Tabelle, mit Artikel_ID, Grundpreis und Datum, in die Datenbank eingesetzt. Jezt fehlt mir eine Idee, aus welchem Datumabschnitt ich den Preis nehmen soll. Als Kriterium ist das Bestelldatum des Auftrages. Bei nur einer Preisanpassung ist das kein Problem. Würde ich ein 'SELECT * FROM ARTIKELPREISE a WHERE a.DATUM > Bestelldatum AND a.ARTIKELNR = Artikelnr' machen. Bei mehreren Preisanpassungen habe ich dann zu jedem Artikel mehrere Datum in der Tabelle. Bei alten Aufträgen müsste ich den richtigen Preis aus dem entsprechendem Zeitraum nehmen. Da fehlt mir die Idee. Gruß, Luckner |
AW: Preisanpassung
SQL-Code:
SELECT first 1 * FROM ARTIKELPREISE a WHERE a.DATUM > Bestelldatum AND a.ARTIKELNR = Artikelnr order by a.Datum
First 1, da Du nur einen Datensatz als Ergebnis haben möchtest. Order by a.Datum, da der gesuchte Satz der Satz sein soll, dessen Datum größer dem Bestelldatum ist. Was ist, wenn am Tag der Bestellung auch eine Preisanpassung stattfand? Also z. B. Bestellung am 01.06.2022 und Preisanpassung am 01.06.2022. Damit würden dann die Preise, die bis zum 31.05.2022 gültig waren, auch für die Bestellung am 01.06.2022 herangezogen. Müsste es von daher nicht a.Datum >= Bestelldatum heißen? |
AW: Preisanpassung
Ja, hast Du recht. Bin davon ausgegangen, dass solche Anpassungen immer zum 01.01. gemacht werden und an diesem Tage Keiner arbeitet. Aber ja, Dein Vorschlag ist besser.
Gruß, Luckner |
AW: Preisanpassung
Hallo Delphi.Narium,
jetzt sehe ich noch einen Logig-Fehler. Bei ' ... WHERE a.DATUM > Bestelldatum ...'. Bestelldatum ist immer > als a.DATUM. Das Ergebnis dieser Abfrage wäre leer. Gruß, Luckner |
AW: Preisanpassung
Und wenn Du da mal die Werte vertauschst?
SQL-Code:
WHERE Bestelldatum >= a.DATUM
Also in etwa so:
SQL-Code:
SELECT first 1 * FROM ARTIKELPREISE a WHERE Bestelldatum >= a.Datum AND a.ARTIKELNR = Artikelnr order by a.Datum desc
First 1, da Du nur einen Datensatz als Ergebnis haben möchtest. Order by a.Datum desc, da der gesuchte Satz der Satz sein soll, bei dem das Datum das größte Datum ist, welches kleiner oder gleich dem Bestelldatum ist. |
AW: Preisanpassung
Zitat:
SQL-Code:
gibt, dann kann man zukünftige Änderungen auch schon in Ruhe vorher eingeben, welche dann erst ab dem Stichtag gelten.
AND a.Datum <= Now
|
AW: Preisanpassung
Hallo,
Bei
Delphi-Quellcode:
scheint es zu funktionieren. Schließt sich das 'Bestelldatum' >= a.DATUM AND a.DATUM <= 'NOW', mit dem = gleichzeitig auf beiden Seiten, nicht aus?
SELECT first 1 * FROM ARTIKELPREISE a WHERE 'Bestelldatum' >= a.DATUM AND a.DATUM <= 'NOW' AND a.ID_ARTIKEL = 1 order by a.Datum DESC
Luckner |
AW: Preisanpassung
Habe jetzt folgende Routine geschrieben:
Delphi-Quellcode:
Routine funktioniert soweit. Ich frage mich nur, ob man es besser schreiben kann. Benutze jetzt 2 Datasets, aber es geht möglicherweise auch im einem Dataset und einem 'insert-Befehl'.
Faktor := 1 + StrToFloat(JvEditPreiszuschlag.Text)/100; //Preiszuschlag ist in % für alle Artikel
ShowMessage('Faktor ist ' + FloatToStr(Faktor)); DatamoduleArtikel.DataModule1.IBDatabaseArtikel.Connected := False; //Hier wird das Datum der letzten Preiserhöhung ermittelt DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.Close; DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.SelectSQL.Clear; DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.SelectSQL.Add('select * from ARTIKELPREISE order by Datum'); DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.Open; DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.Last; DatumPreiserhoehung := DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGridDATUM.AsDateTime; DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.Close; ShowMessage('Datum der letzten Preisehöhung ist ' + DateToStr(DatumPreiserhoehung)); DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.SelectSQL.Clear; //Alle Artikelpreise seit der letzten Preisanpassung ermitteln DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.SelectSQL.Add('select * from ARTIKELPREISE WHERE DATUM = ' + QuotedStr(DateToStr(DatumPreiserhoehung)) + ' ORDER BY ARTIKELNR'); DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.Open; DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.FetchAll; ShowMessage('Anzahl der Datensätze ist ' + IntToStr(DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.RecordCount)); DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.First; DatamoduleArtikel.DataModule1.IBDataSetArtikelpreise.Open; for i := 0 to DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.RecordCount -1 do begin DatamoduleArtikel.DataModule1.IBDataSetArtikelpreise.Append; //Tabelle mit neuen Preisen ergänzt DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseID_ARTIKEL.AsInteger := DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGridID_ARTIKEL.AsInteger; DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseARTIKELNR.AsString := DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGridARTIKELNR.AsString; DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGRUNDPREIS.AsFloat := DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGridGRUNDPREIS.AsFloat * Faktor; DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseDATUM.AsDateTime := StrToDate(JvDateDatePreisanpassung.Text); //ShowMessage('Neuer Preis ist ' + FloatToStr(DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGRUNDPREIS.AsFloat)); DatamoduleArtikel.DataModule1.IBDataSetArtikelpreise.Post; DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.Next; end; if DatamoduleArtikel.DataModule1.IBTransactionArtikel.InTransaction then DatamoduleArtikel.DataModule1.IBTransactionArtikel.Commit; Gruß, Luckner |
AW: Preisanpassung
Du benötigst doch nur das Datum. Warum dann
SQL-Code:
?
select * from ARTIKELPREISE order by Datum
Dazu benötigst Du nur den höchsten Datumswert, da wäre doch eventuell ein
SQL-Code:
angebracht.
select max(Datum) as Datum from ARTIKELPREISE
QuotedStr ist für Pascal/Delphi, aber (eigentlich) nicht für SQL, auch wenn's da meist klappt, da scheint mir der Verwendung von Parametern eher angebracht.
Delphi-Quellcode:
Statt
DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.SelectSQL.Add('select ID_ARTIKEL, ARTIKELNR, GRUNDPREIS, DATUM from ARTIKELPREISE WHERE DATUM = :datum ORDER BY ARTIKELNR');
DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.ParamByName('Datum').AsDateTime := DatumPreiserhoehung;
SQL-Code:
nutze ich immer
select * from
SQL-Code:
. Warum soll mir die Datenbank 1000ende Werte liefern, wenn ich nur einen oder 4 oder ... benötige?
select Liste der tatsächlich benötigten Spalten from
Und weiterer Vorteil: Wenn mal wer die Datenbankstruktur ändert und es gibt eine Spalte nicht mehr, so erhalte ich hier einen entsprechenden, verständlichen SQL-Fehler und nicht irgendwo eine Schutzverletzung, wenn z. B. ein persistentes Feld noch Nil ist oder sowas in der Richtung. Und den Inhalt von neuen Spalten, den ich im Programm nicht benötige, muss mir die Datenbank auch nicht liefern ;-) Keine Ahnung, ob (mit Deinen Datenbankkomponenten und FireBird zusammen) sowas sinngemäß funktionieren könnte:
Delphi-Quellcode:
DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.SQL.Add('insert into ARTIKELPREISE select ID_ARTIKEL, ARTIKELNR, GRUNDPREIS * :faktor as GRUNDPREIS, :datum_neu as DATUM from ARTIKELPREISE where datum = :datum');
DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.ParamByName('datum').AsDateTime := DatumPreiserhoehung; DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.ParamByName('datum_neu').AsDateTime := StrToDate(JvDateDatePreisanpassung.Text); DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.ParamByName('faktor').AsFloat := faktor; DatamoduleArtikel.DataModule1.IBDataSetArtikelpreiseGrid.ExecSQL; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 23:42 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz