Abonnements verwalten, Teil 1

Wenn man an Abonnements denkt, fallen einem zunächst Zeitungen oder Magazine ein. Es gibt aber natürlich auch Abonnements für alle anderen Bereiche des Lebens – vom Sockenabo, das einem regelmäßig neue Socken garantiert, bis hin zum Abonnement über die regelmäßige Lieferung von Adressetiketten. Allen Abonnements ist gemeinsam, dass diese verwaltet werden müssen, damit jeder Kunde das richtige Produkt zum passenden Zeitpunkt erhält. Dieser Beitrag zeigt Möglichkeiten zum Verwalten von Abonnements auf.

Wenn Sie sich ein einfaches Zeitschriften-Abonnement vorstellen, bei dem der Leser monatlich eine Ausgabe der Zeitschrift erhält, gibt es grundsätzlich mindestens zwei Möglichkeiten, um dies abzuhandeln:

  • Sie legen den Zeitraum fest, innerhalb dessen der Kunde das Produkt erhält – beispielsweise vom 1.1.2012 bis zum 31.12.2012. Befindet sich der Lieferzeitpunkt der aktuellen Ausgabe innerhalb dieses Zeitraumes, erhält der Kunde ein Exemplar, sonst nicht.
  • Sie verwalten die einzelnen Ausgaben in einer Tabelle und weisen dem Abonnement die entsprechenden Datensätze dieser Tabelle zu, also beispielsweise 1/2012 bis 12/2012.

Die Variante mit dem Zeitraum hat einen Nachteil: Es kann ja einmal vorkommen, dass die erste Ausgabe des Jahres schon im Vormonat erscheint, also beispielsweise am 31.12.2011, oder dass die letzte Ausgabe des Zeitraums etwas später veröffentlicht wird – etwa am 2.1.2013. In diesem Fall müssen Sie zu jeder Ausgabe noch weitere Angaben festlegen, zum Beispiel einen generischen Erscheinungstermin wie etwa immer den ersten Tag des Monats.

Bei dieser vom Datenmodell her einfacheren Lösung müssten Sie aber dennoch eine Möglichkeit schaffen, die pro Kunde versendeten Ausgaben zu speichern – und dann können Sie auch gleich zu der Variante mit der Verwaltung der einzelnen Ausgaben übergehen.

Produkt oder Abonnement

Ein interessantes Thema ist die Vermengung von Begriffen wie Abonnement, Bestellung, Produkt et cetera. Wenn ein Kunde ein Abonnement bestellt, wie wird dies dann in der Datenbankanwendung behandelt Eines steht fest: Der Kunde wird als Datensatz in einer Kundentabelle gespeichert (egal, ob dieser bereits vorhanden ist oder neu angelegt werden muss). Eine Bestellung hat mit dem Abonnement eigentlich nicht viel gemein, denn eine Bestellung entspricht ja der Zuordnung eines Produktes zu einem Kunden (mit entsprechendem Zeitpunkt, Konditionen et cetera). Das Abonnement lässt sich eher mit einem einzelnen Produkt gleichsetzen beziehungsweise mit einer Bestellposition. Dabei unterschlagen wir zunächst, dass ein Abonnement ja aus mehreren Lieferungen besteht. Während das Abonnement beziehungsweise das Produkt als Bestellposition Teil einer kompletten Bestellung sein kann, soll es doch während der Verwaltung des Abonnements als dem jeweiligen Kunden zugeordnetes Element und somit losgelöst von der Bestellung oder einer Bestellposition betrachtet werden.

Also enthält das Abonnement zumindest ein Fremdschlüsselfeld zum Herstellen einer Beziehung zum betroffenen Kunden.

Der Einfachheit halber lassen wir aus dieser Anwendung die Bestellungen komplett aus und schauen nur auf die reine Abonnementverwaltung.

Datenmodell

Ohne Kunden gibt es kein Abonnement, deshalb beginnen wir hier mit der Tabelle tblKunden. Diese sieht im Entwurf wie in Bild 1 aus. Zu dieser Tabelle gehört noch eine Lookup-Tabelle, welche die Anreden beisteuert. Die Tabelle heißt tblAnreden, enthält die Felder AnredeID und Anrede und wird über das Fremdschlüsselfeld AnredeID der Tabelle tblKunden referenziert. Dieses Fremdschlüsselfeld wurde als Nachschlagefeld ausgelegt.

pic001.png

Bild 1: Die Tabelle tblKunden in der Entwurfsansicht

Die Produkte, also beispielsweise Access im Unternehmen, Access [basics] oder Access-Newsletter speichern wir in einer Tabelle namens tblProdukte. Diese Tabelle enthält neben dem Primärschlüsselfeld ProduktID noch ein Feld mit der Produktbezeichnung sowie ein Feld namens AnzahlAusgaben, das die Anzahl der Ausgaben pro Jahr kennzeichnet (s. Bild 2). Außerdem finden Sie im Datenmodell dieser Tabelle noch ein Feld namens Laufzeit, das die Laufzeit eines Abonnements zu diesem Produkt in Monaten aufnimmt.

pic003.png

Bild 2: Die Tabelle tblProdukte mit einigen Daten

Jedes Abonnement wird selbstverständlich genau einem Kunden zugewiesen. Deshalb steht schon fest: Die Tabelle tblAbonnements muss neben dem Primärschlüsselfeld AbonnementID noch ein Fremdschlüsselfeld zur Tabelle tblKunden enthalten (s. Bild 3). Welche Informationen sollen noch zu einem Abonnement gespeichert werden Ein Abonnement führt auf jeden Fall einen Kunden und ein Produkt zusammen. Daher erhält die Tabelle zwei Fremdschlüsselfelder namens ProduktID und KundeID, die beide als Nachschlagefeld ausgeführt werden. Das Nachschlagefeld ProduktID soll dabei das Feld Produkt der Tabelle tblProdukte zur Auswahl anbieten, das Nachschlagefeld KundeID das Feld Kundenbezeichnung der Tabelle tblKunden – beides in alphabetischer Reihenfolge. Dies erreichen Sie am schnellsten durch den Einsatz des Nachschlage-Assistenten.

pic004.png

Bild 3: Entwurfsansicht der Tabelle tblAbonnements

Normalerweise sollte man annehmen, dass Sie hier einen zusammengesetzten, eindeutigen Index über die beiden Felder ProduktID und KundeID anlegen sollten. Allerdings kann es ja vorkommen, dass ein Kunde ein Abonnement zum gleichen Produkt verlängert oder dieses später nochmals ordert – Eindeutigkeit bezüglich der Kombination aus Produkten und Kunden ist also nicht gefragt.

Welche Informationen soll das Abonnement noch enthalten Interessant ist sicher das Startdatum, das wir im gleichnamigen Feld mit dem Datentyp Datum/Uhrzeit speichern. Außerdem soll noch vermerkt werden, ob ein Abonnement gekündigt oder storniert wurde. Eine Kündigung führt dazu, dass das Abonnement – so dies vorgesehen ist – nicht automatisch verlängert wird.

Eine Stornierung kann beispielsweise innerhalb eines festgelegten Zeitraums (der Rücktrittsfrist) erfolgen und führt dazu, dass das Abonnement nicht weiter ausgeführt wird. Die Felder GekuendigtAm und StorniertAm nehmen die entsprechenden Datumsangaben auf.

Und da wir bereits die Verlängerung eines Abonnements angesprochen haben: Die geschieht in der Regel, wenn der Abonnent das Abonnement nicht bis zu einem bestimmten Termin kündigt. Es gibt sicher auch Abonnements, die automatisch auslaufen, gängig ist aber das Modell der automatischen Verlängerung. Was geschieht bei einer Verlängerung eines Abonnements

Es gibt grundsätzlich zwei Möglichkeiten:

  • Sie ändern die Daten des zu verlängernden Abonnements so, dass der Kunde weiterhin die gewohnten Lieferungen erhält.
  • Sie legen ein neues Abonnement an, dass sich auf den gleichen Kunden und das gleiche Produkt bezieht. Außerdem sollte hier festgehalten werden, welches Abonnement hier verlängert wurde.

Wir wählen die zweite Variante, da sie besser nachvollziehbar scheint. Damit Sie erkennen können, ob es sich bei dem Abonnement um ein Folge-Abonnement handelt, legen wir in der Tabelle tblAbonnements auch noch ein Feld namens FolgeaboVon an. Dieses wird, wie in Bild 4 zu erkennen, mit dem Primärschlüsselfeld der Tabelle selbst verknüpft. Dies erreichen Sie, indem Sie die Tabelle zwei Mal in das Beziehungsfenster ziehen und dann das Feld FolgeaboVon einer der beiden Repräsentationen der Tabelle auf das Feld AbonnementID der anderen ziehen. Legen Sie für diese Beziehung referentielle Integrität fest.

pic005.png

Bild 4: Zuordnung von Folge-Abonnements

Ausgaben und Versendungen

Es fehlen noch die Ausgaben, die mit jedem Abonnement an die Kunden versendet werden sollen. Das ist komplizierter, als es zunächst den Anschein hat. Immerhin gibt es ja für jedes Produkt verschiedene Ausgaben: Für Access im Unternehmen die Ausgaben 1 bis 6 für jedes Jahr, für Access [basics] etwa 1 bis 12. Diese sollen nun je nach Startzeitpunkt einem Abonnements zugeordnet werden. Wenn der Kunde beispielsweise am 1.7. eines Jahres einsteigt, soll er bei einem Magazin mit zwölf Ausgaben mit der Ausgabe 7 beginnen. Das spezielle Abonnement des Kunden soll also etwa die Ausgaben 7/2012 bis 6/2013 enthalten. Ein anderer Kunde steigt vielleicht einen Monat später ein, sein Abonnement sieht also bezüglich der Versendungen gleich ganz anders aus. Um diese Ausgaben zum jeweiligen Abonnement zuweisen zu können, wollen wir zunächst einmal die Ausgaben je Abonnement definieren. Dies erledigen wir in einer Tabelle namens tblAusgaben, welche die vier Felder AusgabeID, AusgabeBezeichnung, AusgabeNummer und AusgabeJahr enthält. Diese Tabelle könnte beispielsweise Daten wie in Bild 5 enthalten. Wo aber befindet sich hier der Verweis auf das Produkt – und zwar in Form eines Fremdschlüsselfeldes namens ProduktID mit einer Referenz auf das Primärschlüsselfeld der Tabelle tblProdukte Dieses gehört eigentlich in diese Tabelle. Wir möchten aber noch einen Spezialfall abfangen, der durch verschiedene Abonnement-Formen mit den gleichen Ausgaben resultiert: Es ist ja möglich, dass Sie zusätzlich zu einem zwölfmonatigen Abonnement noch ein dreimonatiges Testabo anbieten. Der Kunde mit dem Testabo soll aber etwa die gleiche Ausgabe 5/2012 erhalten wie der Kunde mit dem regulären Abonnement. Deshalb kann ein Eintrag der Tabelle tblAusgaben mehreren Datensätzen der Tabelle tblProdukte zugeteilt werden.

pic002.png

Bild 6: Das komplette Datenmodell der Beispieldatenbank

Damit hätten wir festgelegt, welche Ausgaben es zu einem Produkt gibt. Nun müssen wir nur noch die im Rahmen eines Abonnements zu verschickenden Ausgaben festlegen. Dies wiederum geschieht in der Tabelle tblVersendungen. Diese verknüpft die Tabelle tblAbonnements mit der Tabelle tblAusgaben. Das Feld VersendungID dient als Primärschlüsselfeld, das Feld AusgabeID ist ein Fremdschlüsselfeld zum Herstellen der Beziehung zur Tabelle tblAusgaben und das Feld AbonnementID weist die Ausgabe schließlich einem der Datensätze der Tabelle tblAbonnements zu. Schließlich müssen wir noch irgendwo vermerken, wann die Ausgabe an den über die Tabelle tblAbonnements referenzierten Kunden verschickt wurde. Dies erledigen wir mit einem Datumsfeld namens Versanddatum.

Bild 6 zeigt die Zusammenhänge zwischen den einzelnen Tabellen im Überblick. Auch der noch zu programmierende Ablauf beim Anlegen eines Abonnements lässt sich hier ablesen: Wenn ein neuer Datensatz zur Tabelle tblAbonnements hinzugefügt wird, legt man dort auch das Produkt fest, auf dem das Abonnement basiert. Im nächsten Schritt prüft man, welche Ausgaben angelegt werden sollen. Dazu wird im Feld AktuelleAusgabeID der Tabelle tblProdukte, das ebenfalls mit der Tabelle tblAusgaben verknüpft ist, die aktuelle Ausgabe referenziert. Im Folgenden sollen in der Tabelle tblVersendungen die entsprechenden Einträge vorgenommen werden: Wenn das Feld AnzahlAusgaben der Tabelle tblProdukte also etwa den Wert 12 enthält, sollen in der Tabelle tblVersendungen zwölf Datensätze erstellt werden. Diese enthalten jeweils einen Verweis auf den neu angelegten Datensatz der Tabelle tblAbonnements und auf die zwölf Einträge der Tabelle tblAusgaben, die zur aktuellen Ausgabe und zum gewählten Produkt passen. Wie dies technisch abläuft, schauen wir uns weiter unten an.

pic006.png

Bild 5: Tabelle der Ausgaben

Damit die Tabelle tblVersendungen jede Ausgabe nur einmal je Abonnement aufnimmt, legen wir einen zusammengesetzten eindeutigen Schlüssel für die beiden Felder AbonnementID und AusgabeID fest.

Formulare zur Datenverwaltung

Nachdem die Tabellenstruktur steht, benötigen wir eine Reihe Formulare zum Verwalten der Abonnements und der dazu benötigten Daten.

Die Kunden sollen in einem Formular frmKunden verwaltet werden. Über dieses Formular werden schließlich auch die Abonnements eines jeden Kunden angezeigt und die zu versendenden Ausgaben. Das Formular frmKunden verwendet die Tabelle tblKunden als Datenherkunft und zeigt die Details zu einem Kunden in der Formularansicht an. An dieser Stelle gibt es noch keine Besonderheiten – ziehen Sie einfach alle benötigten Felder aus der Datenherkunft in den Detailbereich der Entwurfsansicht und ordnen Sie diese etwa wie in Bild 7 an.

pic007.png

Bild 7: Das Formular zur Verwaltung der Kunden in der Entwurfsansicht

Produkte verwalten

Bevor wir die Unterformulare zur Darstellung der Abonnements eines Kunden sowie der Versendungen zu dem jeweils aktivierten Abonnement einfügen, sollen zunächst die Möglichkeiten der dazu benötigten Daten geschaffen werden.

Da wären zunächst die Produkte, ohne die nichts geht: Sie können kein Abonnement anlegen ohne ein Produkt und auch keine Ausgaben.

Das Formular frmProdukte verwendet die Tabelle tblProdukte als Datenherkunft und zeigt auch alle Felder dieser Tabelle an (s. Bild 8). Sie können damit zwar nun Daten für die Felder Produkt und AnzahlAusgaben hinzufügen, aber noch nicht für das Fremdschlüsselfeld AktuelleAusgabeID – dafür liegen ebenfalls noch keine Daten vor.

pic008.png

Bild 8: Das Formular zum Anlegen der Produkte

Um eine oder mehrere Ausgaben anzulegen, fügen Sie dem Formular gleich neben dem Feld AnzahlAusgaben eine Schaltfläche namens cmdNeueAusgaben hinzu. Dieses ruft ein Formular auf, mit dem Sie neue Ausgaben zum aktuellen Produkt hinzufügen können – dazu später mehr.

Das Unterformular sfmAusgaben soll alle Ausgaben zum aktuell im Hauptformular frmProdukte angezeigten Produkt anzeigen. Dazu verwendet das Formular eine Abfrage als Datenherkunft, die auf den beiden Tabellen tblProdukteAusgaben und tblAusgaben basiert und die unter dem Namen qryFrmProdukteSfmAusgaben gespeichert wird (s. Bild 10).

pic010.png

Bild 9: Datenherkunft des Unterformulars sfmAusgaben

pic011.png

Bild 10: Produkte und Ausgaben im Entwurf

Das Unterformular zeigt somit automatisch nur noch Ausgaben an, die zum aktuellen Produkt im Hauptformular gehören.

Eine Besonderheit ergibt sich noch bezüglich des Kombinationsfeldes zur Auswahl der aktuellen Ausgabe, das wir in cboAktuelleAusgabeID umbenennen. Es soll genau wie das Unterformular sfmAusgaben nur die zum aktuellen Produkt gehörenden Ausgaben zur Auswahl anbieten.Dazu weisen Sie diesem Kombinationsfeld als Datensatzherkunft eine Abfrage zu, die genauso aussieht wie die Datenherkunft des Unterformulars sfmAusgaben, aber unter dem Namen qryFrmProdukteCboAktuelleAusgabeID als eigene Abfrage gespeichert wird. Man weiß nie, ob nicht eine der Abfragen einmal angepasst werden muss, daher werden diese gleich als eigene Objekte gespeichert.

Wenn nun bereits einige Datensätze in den Tabellen tblAusgaben und tblProdukteAusgaben vorhanden wären, würde das Formular frmProdukte diese wie in Bild 11 anzeigen.

pic012.png

Bild 11: Formularansicht der Produkte und Ausgaben

Also kümmern wir uns doch darum, dass einige Ausgaben angelegt werden. Dies erledigen Sie, in dem Sie die weiter oben angelegte Schaltfläche cmdNeueAusgaben mit Leben füllen.

Legen Sie dazu die folgende Ereignisprozedur an, die durch das Ereignis Beim Klicken der Schaltfläche ausgelöst wird:

Private Sub cmdNeueAusgaben_Click()
    DoCmd.OpenForm "frmNeueAusgaben", WindowMode:=acDialog, OpenArgs:=Me!ProduktID
    Me!cboAktuelleAusgabeID.Requery
    Me!sfmAusgaben.Form.Requery
End Sub

Die Prozedur öffnet ein weiteres Formular namens frmNeueAusgaben und übergibt den Wert des Feldes ProduktID des aktuellen Datensatzes per Öffnungsargument an das Formular.

Dieses wird außerdem als modaler Dialog geöffnet und sieht wie in Bild 12 aus. Das Formular zeigt alle kommenden und noch nicht erfassten Ausgaben in einem Listenfeld an.

pic013.png

Bild 13: frmNeueAusgaben in der Entwurfsansicht

Der Benutzer kann die gewünschten Einträge unter Verwendung der Strg– und der Umschalt-Taste markieren und die Ausgaben mit der Schaltfläche OK hinzufügen oder das Formular ohne Ônderung der Ausgaben mit der Schaltfläche Abbrechen schließen. Die Prozedur cmdNeueAusgaben_Click aktualisiert jeweils mit der Requery-Methode noch die Datenherkunft des Unterformulars sfmAusgaben sowie die Datensatzherkunft des Kombinationsfeldes cboAktuelleAusgabe.

Erstellen des Formulars frmNeueAusgaben

Unabhängig von Abonnements, also der Zuordnung von zu versendenden Ausgaben zu einem Kunden, müssen die Ausgaben für das betroffene Produkt zunächst einmal angelegt werden.

Bei Access im Unternehmen heißen solche Ausgaben 1/2012, 2/2012 und so weiter. Die können Sie entweder von Hand anlegen oder Sie ziehen ein Formular hinzu, das Ihnen diese Arbeit abnimmt.

Dieses sieht im Entwurf wie in Bild 13 aus und enthält ein Listenfeld namens lstAusgaben, das erst beim Öffnen des Formulars mit Daten gefüllt wird. Da die Daten als Zeichenkette übergeben werden, stellen Sie die Eigenschaft Herkunftsart auf Wertliste ein. Damit der Benutzer wie im Windows Explorer mehrere Einträge mit der Strg– und der Umschalt-Fläche auswählen kann, stellen Sie die Eigenschaft Mehrfachauswahl auf Erweitert ein.

pic014.png

Bild 12: Formular zum Anlegen neuer Ausgaben

Damit die noch nicht angelegten Ausgaben im Listenfeld lstAusgaben angezeigt werden, legen Sie die Ereignisprozedur aus Listing 1 an, die durch das Ereignis Beim Öffnen des Formulars frmNeueAusgaben ausgelöst wird.

Listing 1: Neu hinzuzufügende Ausgaben in einer Liste anzeigen

Private Sub Form_Open(Cancel As Integer)
    Dim i As Integer
    Dim strAusgaben As String
    Dim intAnzahlAusgaben As Integer
    Dim intJahrAusgabe As Integer
    Dim intNummerAusgabe As Integer
    lngProduktID = Nz(Me.OpenArgs)
    If lngProduktID = 0 Then
        Cancel = True
    Else
        intAnzahlAusgaben = DLookup("AnzahlAusgaben", "tblProdukte", "ProduktID = " & lngProduktID)
        intJahrAusgabe = Nz(DMax("AusgabeJahr", "qryProdukteAusgaben", _
            "ProduktID = " & lngProduktID), Year(Date))
        intNummerAusgabe = Nz(DMax("AusgabeNummer", "qryProdukteAusgaben", _
            "ProduktID = " & lngProduktID & " AND AusgabeJahr = " & intJahrAusgabe), 1)
        For i = 1 To 100
            If Not intNummerAusgabe = intAnzahlAusgaben Then
                intNummerAusgabe = intNummerAusgabe + 1
            Else
                intNummerAusgabe = 1
                intJahrAusgabe = intJahrAusgabe + 1
            End If
            strAusgaben = strAusgaben & intNummerAusgabe & ";" & intJahrAusgabe & ";" _
                & intNummerAusgabe & "/" & intJahrAusgabe & ";"
        Next i
        Me!lstAusgaben.RowSource = strAusgaben
        Me!lstAusgaben = Me!lstAusgaben.ItemData(0)
    End If
End Sub

Diese Prozedur füllt und verwendet eine Variable namens lngProduktID, deren Wert auch noch in einer weiteren Prozedur verwendet werden soll. Daher deklarieren Sie die entsprechende Variable wie folgt im Kopf des Klassenmoduls Form_frmNeueAusgaben:

Dim lngProduktID As Long

Die Prozedur Form_Open prüft zunächst, ob die Eigenschaft OpenArgs des Formulars einen Wert enthält. Dieser wird beim Aufruf vom Formular frmProdukte aus als Wert des Feldes ProduktID des aktuellen Datensatzes übergeben.

Hat diese den Wert NULL, wird lngProduktID durch die Funktion Nz mit dem Wert 0 gefüllt. In diesem Fall wird das Öffnen des Formulars durch Einstellen des Rückgabeparameters Cancel auf den Wert False abgebrochen.

Anderenfalls ermittelt die Prozedur zunächst die Anzahl der Ausgaben pro Kalenderjahr für dieses Produkt. Dies erledigt ein entsprechender Aufruf der DLookup-Funktion, das Ergebnis landet in der Variablen intAnzahlAusgaben. Warum benötigen wir diesen Wert Weil wir damit festlegen, welche Ausgaben überhaupt zur Liste der Ausgaben hinzugefügt werden. Wenn ein Magazin nur sechs Ausgaben pro Jahr liefert, sollen dementsprechend nur die Nummern 1/2012 bis 6/2012 erscheinen und nicht etwa die 7/2012.

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar