Home > Artikel > Ausgabe 8/2016 > Lokaler Webshop

Lokaler Webshop

Achtung: Sie sind nicht angemeldet. Wenn Sie Abonnent sind und sich anmelden, lesen Sie den kompletten Artikel, laden das PDF herunter oder probieren die Beispieldatenbank aus (sofern vorhanden).

Nur wenige Web-Applikationen kommen ohne eine zugrundeliegende Datenbank aus. Ob CMS-Systeme oder eCommerce-Anwendungen, alle speichern unter der Haube ihre Daten in DBMS-Servern, wie etwa MySQL. Wie stellen in dieser Ausgabe einen unter Access nachgebildeten Shop vor, der ähnliche Features aufweist, wie seine Vorbilder im Internet. Zwar nützt eine solche Anwendung reell wohl für nur wenige Einsatzbereiche, dafür aber lässt sich an ihr eine Menge demonstrieren.

Beispieldatenbank

Die Beispiele dieses Artikels finden Sie in der Datenbank 1608_Webshop.zip.

Demo-Webshop

Vorstellbar wäre etwa der Einsatz auf Messe- oder Verkaufsstandrechnern, wo Kunden nicht vorrätige Artikel über den lokalen Shop beziehen könnten. Weniger auf dem Praxiseinsatz liegt unser Augenmerk hier jedoch, als vielmehr auf der Darstellung des Datenmodells und dem, was sich aus ihm herausholen lässt.

Unsere einfache alte Kundendatenbank hat zunächst ausgesorgt. Dennoch bildet sie die Basis für die Kundenverwaltung des Shops. Angedockt ist an sie eine Bestellverwaltung, wie sie ebenfalls schon mehrfach in unseren Beiträgen verwendet wurde und in der ehrwürdigen Nordwind-Datenbank ihr Vorbild hat. Neuer hingegen ist ein Teil zur Verwaltung von Artikeln und Produktkategorien. Wie auch immer: Das Hauptformular der Anwendung zeigt sich, wie in Bild 1, in dem Hardware-Produkte über die virtuelle Firma ABasics Computer GmbH bezogen werden können. Hier wählen Sie Artikel über das Kombinationsfeld mit den Hauptkategorien oben links und weiter mit dem Listenfeld links und dessen Produktunterkategorien aus. Die Spezifikation des Produkts inklusive eines Vorschaubilds wird dann rechts im Formular eingeblendet. Der Artikel kann schließlich über den grünen Button rechts unten in den Warenkorb gelegt werden. Auf den weiteren Bestellvorgang kommen wir später noch zu sprechen.

So präsentiert sich der Webshop nach dem Start des Formulars frmShop mit eingestellten Kategorien und einem Produkt

Bild 1: So präsentiert sich der Webshop nach dem Start des Formulars frmShop mit eingestellten Kategorien und einem Produkt

Selbstverständlich ist dies nur ein stark vereinfachtes Muster, denn echte Webshop-Systeme weisen meist über hundert verknüpfte Tabellen auf und stellen in der Regel ausgewachsene Warenwirtschaftsanwendungen dar. Dennoch zeigt sich hier das Prinzip. Die Demodatenbank enthält übrigens über 10.000 existierende Artikel, die zumeist mit Vorschaubildern abgespeichert sind. Auf größere Produktbilder musste verzichtet werden, da dies den Download auf einige Gigabyte vergrößert hätte.

Datenmodell des Shops

In Bild 2 sind die Beziehungen zwischen sämtlichen Tabellen der Datenbank dargestellt. Ganz links finden sich die bekannten Kundentabellen im hellblauen Bereich. Die daran angeflanschten Bestelldaten sind gelb hinterlegt. Rechts finden Sie die zu Artikeln gehörigen Tabellen.

Layout des von einer Bestelldatenverwaltung abgeleiteten neuen Datenmodells des Webshops im Beziehungsfenster

Bild 2: Layout des von einer Bestelldatenverwaltung abgeleiteten neuen Datenmodells des Webshops im Beziehungsfenster

Die Kundentabelle tblKunden hat nun ein zusätzliches Feld Login bekommen. In diesem Textfeld wird für einen im Shop registrierten Kunden dessen Passwort gespeichert. Die Verbindung von Kunden zu Bestellungen passiert über das Schlüsselfeld KundeID der Tabelle tblBestellungen über eine 1:n-Beziehung mit Referenzieller Integrität. Das Löschen von Kunden zieht damit automatisch das Entfernen der zugehörigen Bestelldatensätze nach sich.

Bestellungen

Eine Bestellung wird über die vier Felder der Tabelle tblBestellungen identifiziert. Ihr Primärschlüssel ID (Autowert) dient gleichzeitig als Bestellnummer. Das Bestelldatum gibt den Zeitpunkt der Bestellaufgabe wieder. IDStatus bezieht sich auf ihren Bearbeitungsstatus, dessen Text aus der Nachschlagetabelle tblBearbeitungsstati kommt.

Die möglichen Werte sind hier:

0 - Bestellung noch nicht abgeschickt

1 - Bestellung aufgegeben

2 - Bestellung versandt

3 - Bestellung abgeschlossen

Der Vorgabewert 0 bedeutet dabei, dass eine Bestellung angelegt wurde, indem Artikel temporär in den Warenkorb gelegt wurden, dieser jedoch noch nicht zur Kasse gelangte.

Nicht ganz einleuchtend mag das Feld Gesamtsumme sein, welches den Bruttogesamtbetrag aller Artikel enthält. Tatsächlich ergibt sich dieser ja aus den Artikelpreisen, deren Anzahl und Steuersätzen. Das wiederspricht scheinbar den Regeln der Normalisierung von Datenbankmodellen. Tut es auch, doch ganz so akribisch muss man nicht vorgehen. Denn bei der Berechnung etwa von Umsatzstatistiken fallen die benötigten Abfragen einfacher aus, weil dafür nun die eine Tabelle tblBestellungen ausreicht. Im Interesse erhöhter Performance ist so eine Denormalisierung deshalb durchaus statthaft.

Die einzelnen Artikel einer Bestellung listet die 1:n-Tabelle tblBestelldetails auf. Die BestellID jedes Datensatzes bezieht sich auf die Bestellnummer ID in tblBestellungen. Auch hier ist Referenzielle Integrität mit Lösch- und Aktualisierungsweitergabe eingestellt. Das Löschen einer Bestellung löscht damit auch alle bestellten Positionen. Und damit führt das Löschen eines Kunden automatisch sowohl zum Entfernen all seiner Bestellungen und zusätzlich aller zugehörigen Bestellpositionen. Um verwaiste Datensätze müssen Sie sich deshalb nicht sorgen.

Die ArtikelID verweist auf einen der Shop-Artikel in der Tabelle tblArtikel. Die Beziehung zwischen beiden ist ebenfalls referenziell, jedoch ist hier keine Löschweitergabe festgelegt. Schließlich soll das Entfernen eines Artikels aus dem Produktsortiment ja nicht die auf diesen bezogenen Bestellungen durcheinanderbringen. Die Referenzielle Integrität sorgt hier im Gegenteil dafür, dass ein Artikel gar nicht gelöscht werden kann, wenn mit ihm Bestellungen verbunden sind. Die Datenbank-Engine verhindert dies nun automatisch und gibt gegebenenfalls einen entsprechenden Hinweis aus.

Zu einer Bestellposition gehört weiterhin die gewünschte Anzahl des Artikels. Sein Preis wird im Währungsfeld Netto verewigt, die angesetzte Umsatzsteuer im Double-Feld Ust, das als Prozentzahl formatiert wird. Auch hier scheint mangelhafte Normalisierung vorzuliegen, denn die gleichen Felder finden sich auch beim Produkt in der Tabelle tblArtikel wieder. Dem ist jedoch nicht so! Denn die Produktpreise werden ja fortwährend dem Markt angepasst. Entnähme man die Preise lediglich der Tabelle tblArtikel, so wiese die Rechnung nach Warenversand eventuell falsche Preisangaben aus. Die Preise müssen daher an dieser Stelle fest abgespeichert werden, damit sie die gleichen Werte haben, wie zum Bestelldatum, was dann sowohl in der Rechnung zum Ausdruck kommt, wie auch in der Bestellübersicht eines Kunden.

Das Feld Brutto ist eigentlich überflüssig, vereinfacht jedoch Abfragen auf die Tabelle. Es handelt sich bei ihm nicht um ein Eingabefeld, sondern um das in Access 2010 eingeführte Berechnete Feld. Hier ist eine Formel hinterlegt, die sich in Bild 3 aus der Feldeigenschaft Ausdruck ergibt. Als Ergebnistyp ist Double eingetragen. Für absolut korrekte Summenbildung in Abfragen wäre dieser Typ allerdings auf Währung zu ändern.

Die Formel für das Berechnete Feld Brutto in der Tabelle tblBestellDetails

Bild 3: Die Formel für das Berechnete Feld Brutto in der Tabelle tblBestellDetails

Bliebe noch das Ja/Nein-Feld Bearbeitet. Dieses bekommt der Kunde im Shop nicht zu Gesicht, denn hier handelt es sich um ein Hilfsfeld für den Vertrieb. Beim Packen einer Bestellung setzt der Bearbeiter für die jeweilige Bestellposition den Wert in einem noch zu besprechenden Bearbeitungsformular auf Ja, um die Übersicht zu behalten.

Produktkategorien

Bevor wir zu Artikeln und deren Detailtabellen kommen, seien noch die Kategorien des Shops erläutert. Sie teilen sich in die zwei Tabellen tblKategorien und tblHauptkategorien auf. Eine Hauptkategorie wären zum Beispiel Drucker. Deren Unterkategorien wäre Laserdrucker, Multifunktionsdrucker, Tintenstrahldrucker. Letztere verzeichnet die tblKategorien, wobei hier zu jedem Datensatz die ID der Hauptkategorie im Feld IDHauptkategorie abgespeichert ist. In der Artikeltabelle wiederum verweist der Long-Wert IDKategorie auf einen Datensatz in tblKategorien.

Im wahren Leben käme diese Kombination eher nicht zum Einsatz. Stattdessen verwendete man eine Rekursive Tabelle, die sowohl über- wie untergeordnete Kategorien enthielte. Eine Eltern-ID eines Datensatzes bezöge sich dann auf einen weiteren Datensatz der Tabelle. Derlei wurde in Ausgabe 09/2105 ausführlicher dargestellt. Der Vorteil wäre eine beliebige Schachtelungstiefe von Kategorien, wie diese auch tatsächlich in vielen Webshops anzutreffen ist. Der Nachteil ist die wesentlich komplexere Auswertung solcher Tabellen und deren Darstellung in der Benutzeroberfläche. Deshalb wurde für unsere Demo davon Abstand genommen.

Artikel

Der Primärschlüssel ID der Tabelle tblArtikel ist ein Autowert, auf den sich eine Bestellposition in tblBestelldetails bezieht. Zu den weiteren Features eines Artikels in tblArtikel zählen seine Bezeichnung im Feld Produkt, die Artikelnummer in Textfeld ArtNr, sein Nettopreis im Währungsfeld Netto und die vorgeschriebene Mehrwertsteuer im Prozentfeld Ust. Daneben kann für ein Produkt auch ein Rabatt im gleichnamigen Prozentfeld angesetzt werden. Der Standardwert beträgt hier 0. Welche Menge des Artikel sich im Lager befindet, verzeichnet das Long-Feld Lagerbestand. Anzeigen ist ein Ja/Nein-Feld, welches nur zu Verwaltung des Shops gedacht ist. Der Standardwert True führt dazu, dass der Artikel im Shop aufgelistet und angezeigt wird. Ein Filter in den dafür vorgesehenen Datenquellen sorgt hierfür.

Einige Nachschlagefelder und deren Tabellen vervollständigen den Entwurf. Die ID zu einer Produktkategorie in tblKategorien hatten wir ja schon. IDHersteller verweist auf einen Datensatz der Tabelle tblHersteller. Und IDVerfuegbarkeit steht in Beziehung zur Nachschlagetabelle tblVerfuegbarkeit, welche folgende Datensätze enthält:

-3 (leer)

-2 Aktuell nicht lieferbar

-1 Sofort (Auf Lager)

0 In 2-4 Tagen

1 In 5-7 Tagen

2 In 8-14 Tagen

3 Kann bestellt werden

Der Standardwert steht in tblArtikel zunächst auf -1. Jeder neu eingefügte Artikel bekommt damit die Eigenschaft Sofort (Auf Lager).

Nun soll jeder Artikel auch ein Vorschaubild zeigen. Dieses könnte man als Anlagefeld direkt in die Tabelle tblArtikel aufnehmen. Tatsächlich verwenden Webshops häufig aber ein und dasselbe Thumbnail mehrfach für unterschiedliche Produkte. Das machen auch wir. Die Bilder sind in die Tabelle tblBilder ausgelagert, die neben dem Autowert-Primärschlüsselfeld ID noch das Anlagefeld Bildanlage aufweist. Die Verbindung zwischen Artikel und Bild kommt nun jedoch nicht direkt zustande – man könnte dies durchaus tun! –, sondern über die n:m-Tabelle tblArtikelBilder. Damit können einem Artikel optional auch mehrere Bilder zugeordnet werden. IDProdukt verweist dabei auf den Artikel, IDBild auf die Anlage.

Ähnlich verhält es sich mit den Produkteigenschaften. Die Liste der Eigenschaften befindet sich in der Tabelle tblEigenschaften (Auszug in Bild 4). Die Verknüpfungstabelle tblArtikelEigenschaften verweist mit IDProd einerseits auf die ID eines Artikels in tblArtikel, andererseits mit IDProp auf eine Produkteigenschaft. Im Unterschied zum Entwurf mit den Vorschaubildern findet sich hier jedoch noch das Textfeld val, welches den eigentlichen Wert der Eigenschaft aufnimmt. Bild 5 demonstriert, wie diese Kombination funktioniert.

Mögliche Artikeleigenschaften

Bild 4: Mögliche Artikeleigenschaften

Artikeleigenschaften und Werte

Bild 5: Artikeleigenschaften und Werte

Obwohl das Feld IDProp vom Typ Zahl ist, wird hier der Textwert angezeigt, weil für das Feld als Datenblattanzeigetyp ein Kombinationsfeld vorgesehen ist, das die Nachschlagetabelle tblEigenschaften verwendet.

Ein Artikel kann somit eine beliebige Anzahl von Eigenschaften und Werten für sie aufweisen. Aus deren Gesamtheit soll sich später dann das Produktdatenblatt ergeben. Für den Artikel mit der ID 25, ein RAM-Speichermodul, gibt es etwa die drei Eigenschaften, welche in der Abbildung ganz oben stehen. Soweit das Wesentliche zum Datenmodell des Webshops.

Shop-Funktionen

An dieser Stelle eine Übersicht der aktuell implementierten Features des Shops, die nicht nur in diesem Beitrag, sondern auch in weiteren Ausgaben näher beleuchtet werden sollen:

  • Anzeigen von Kategorien
  • Anzeigen der Artikel mit Produktdatenblatt, Vorschaubild, Bestand und Verfügbarkeit
  • Filtern der Artikel nach Verfügbarkeit und Hersteller
  • Hinzufügen von Artikel zum Warenkorb
  • Anzeige des Warenkorbs mit der Möglichkeit zum Ändern der Artikelanzahl und/oder Löschen von Positionen
  • Eingabe und Änderung der Kundenadresse
  • Login registrierter Kunden
  • Abschluss der Bestellung über Kasse mit E-Mail-Versand einer Bestellbestätigung über Outlook
  • Übersicht über getätigte Bestellungen und deren Stati für eingeloggte Kunden
  • Bearbeitung einer Bestellung vom Vertrieb mit anschließendem E-Mail-Versand nach Warenausgang
  • Hilfsformular zum Anlegen der Shop-Artikel

Im Folgenden besprechen wir nach und nach die in dieser Anwendung vorhandenen Formulare, Berichte, Abfragen und Module, sowie die benötigten Techniken. Wir gehen dabei weitgehend chronologisch vor und orientieren uns an den einzelnen Vorgängen, die beim Öffnen der Datenbank vonstattengehen.

Webshop-Formular laden

Die Beispieldatenbank sieht das Formular frmIntro als Startformular vor, wie Sie das von allen unseren Demos kennen. Es ist in den Optionen der Aktuellen Datenbank eingetragen. Durch Klick auf dessen Detailbereich oder das Titel-Label wird die Prozedur CloseAndDisplay aufgerufen, die ihrerseits das Shop-Formular frmShop lädt und sich selbst beendet:

DoCmd.Close acForm, Me.Name

DoCmd.OpenForm "frmShop"

Haben Sie sich schon einmal gefragt, wieso überhaupt die zweite Zeile ausgeführt wird, wo das Formular doch zuvor bereits geschlossen wird? Es verhält sich so: Die Close-Methode setzt intern lediglich eine Meldung ab, dass das Formular geschlossen werden soll. Zu diesem Zeitpunkt ist es auch noch sichtbar. Entladen ist es ebenso wenig und der VBA-Code wird weiterhin abgearbeitet. Hätten Sie eine Ereignisprozedur Beim Entladen (Form_Unload) im Formularcode, so würde diese unmittelbar nach Aufruf von Close angesprungen. Erst nach komplettem Beenden der Prozedur sieht Access keinen Grund mehr, das Formular im Speicher zu halten und schließt es aufgrund der internen Meldung endgültig. Diese Meldung übrigens ließe sich entfernen, wenn in der Unload-Prozedur der Parameter Cancel auf True eingestellt würde. Das Formular schließt sich dann eben nicht! Stattdessen bekämen Sie eine Fehlermeldung zu Gesicht, die besagt, dass die Schließen-Aktion abgebrochen wurde.

Die angegebenen zwei Zeilen Code sind aber noch nicht alles, was in der Prozedur CloseAndDisplay abläuft. Sie finden dort zunächst noch die Anweisung

CurDB.Execute "DELETE FROM" & _

"tblBestellungen WHERE KundeID=999999"

Hier wird eine SQL-Aufforderung abgesetzt, die die Datenbank anweist, alle Bestelldatensätze zu löschen, welche die spezielle Kunden-ID 999.999 enthalten. Es wird sich gleich aufklären, was es mit dieser auf sich hat. Die Methode CurDB werden Sie im Befehlssatz von Access und VBA vergeblich suchen. Es ist eine Funktion im Modul mdlShop der Datenbank, die eine Objektinstanz von Database zurückgibt und in Listing 1 abgebildet ist. Die modulweit gültige Objektvariable thisdb steht im Kopf des Moduls. Die Prozedur ermittelt zunächst über Vergleich auf Nothing, ob die Variable bereits belegt ist. Falls nicht, so setzt sie sie deren Wert auf CurrentDb. Der Rückgabewert der Funktion selbst wiederum ist dann der Inhalt von thisdb.

Private thisdb As Database

Function CurDB() As Database

     If thisdb Is Nothing Then Set thisdb = CurrentDb

     Set CurDB = thisdb

End Function

Listing 1: Die Funktion CurDB gibt eine Instanz der aktuellen Datenbank, also CurrentDb , zurück

Wozu gibt es diese Funktion, und warum wird CurrentDb nicht direkt für die Execute-Methode zum Ausführen der SQL-Anweisung verwendet? In umfangreicheren Datenbanken wird auf das aktuelle Database-Objekt häufig Bezug genommen. Der häufigste Fall ist sicherlich das Öffnen von Recordsets über OpenRecordset. CurrentDb aber ist eine Funktion, die quasi eine Kopie des Database-Objekts von Access zum Zeitpunkt des Aufrufs erstellt. Das ist ein Vorgang, der etwas Zeit benötigt. Aus Performancegründen macht es daher Sinn, diese Kopie nicht jeweils neu zu erstellen, sondern in einer Objektvariablen ständig bereitzuhalten.

Noch eine weitere VBA-Zeile finden Sie in der Prozedur CloseAndDisplay:

LoadTempVar "KundeID"

Die Prozedur LoadTempVar steht ebenfalls im Modul mdlShop (Listing 2). Sie simuliert das Laden eines Cookies. Besuchen Sie einen Webshop, so hinterlässt dieser in der Regel auf Ihrem Rechner als Textdatei ein Cookie. Was der Betreiber in dieses hineinschreibt, bleibt ihm überlassen. Sie kennen aber sicher das Feature Automatisches Einloggen. Haben Sie sich auf einer Seite registriert, so schreibt sie unter Umständen Ihre Login-Daten in ein Cookie. Bei erneutem Besuch lädt sie dieses und stellt fest, dass Sie ein bereits registrierter Kunde sind und beim Registrieren ausdrücklich das automatische Einloggen wünschten. Die Kundendaten werden nun aus dem Cookie entnommen.

Function LoadTempVar(ByVal sName As String) As Variant

     Dim val As Variant

     Dim F As Integer

     

     F = FreeFile

     Open CurrentProject.Path & _

"\cookie.txt" For Binary As F

     Get F, , val

     Close F

     TempVars(sName) = val

     LoadTempVar = TempVars(sName)

End Function

Listing 2: Laden eines Variablenwerts aus der Datei cookie.txt im Datenbankverzeichnis

Dasselbe Feature soll unser lokaler Webshop bieten. In der Datei cookie.txt im Datenbankverzeichnis steht die ID des Kunden, der den Shop zuletzt geladen hatte. Die Aufgabe der Prozedur LoadTempVar ist es, diese ID für die aktuelle Sitzung in der TempVar KundeID zu speichern. Dazu öffnet sie die Datei und lädt aus ihr den einen vorhandenen Wert in die Variant-Variable val, um ihn anschließend an die im Prozedurparameter sName spezifizierte TempVar zu übergeben. TempVars(sName) enthält demzufolge nun die Kunden-ID. Der Kunde kann deshalb etwa unmittelbar auf seine früher getätigten Bestellungen zugreifen.

Natürlich muss dazu die Cookie-Datei schon vorhanden sein. Sie wird dann angelegt oder mit einem neuen Wert belegt, wenn das Shop-Formular frmShop geschlossen wird. In dessen Unload-Ereignisprozedur steht diese Zeile:

SaveTempVar "KundeID"

Die zugehörige Prozedur des Moduls mdlShop steht in Listing 3. Hier entnimmt die Routine den Wert der über sName identifizierten TempVar und speichert ihn in der Variablen val zwischen. Nach dem Öffnen der Datei im Binärmodus – bei Variant-Variablen immer angeraten – speichert sie über die Put-Anweisung die Kunden-ID in sie. Auch wenn es sich scheinbar um eine Textdatei handelt, zeigt deren Inhalt keinesfalls die Kunden-ID im Klartext. Durch den Binärmodus wird der Long-Wert genau so abgespeichert, wie er im RAM-Speicher repräsentiert ist, also als 32-Bit-Zahl.

Sub SaveTempVar(ByVal sName As String)

     Dim val As Variant

     Dim F As Integer

     

     val = TempVars(sName)

     F = FreeFile

     Open CurrentProject.Path & _

"\cookie.txt" For Binary As F

     Put F, , val

     Close F

End Sub

Listing 3: Speichern eines Variablenwerts in die Datei cookie.txt im Datenbankverzeichnis

Sie haben das Ende des frei verfügbaren Teil dieses Artikels erreicht!

Wenn Sie mehr lesen und auf viele weitere Artikel zugreifen möchten, melden Sie sich als Abonnent unter Login an. Falls nicht, bestellen Sie doch einfach ein Jahresabonnement!