Abonnements verwalten, Teil 2

Im ersten Teil dieser Beitragsreihe haben Sie das Datenmodell der Abonnementverwaltung und einige Formulare zur Eingabe von Kunden, Produkten und Abonnements erstellt. Im zweiten Teil kümmern wir uns um weitere Funktionen – zum Beispiel das Ermitteln abgelaufener Abonnements und die Verlängerung, Kündigung und Stornierung von Abonnements.

Abonnements kündigen und stornieren

Kunden können Abonnements kündigen und stornieren. Dies sind zwei unterschiedliche Vorgänge. Bei einer Kündigung wird das laufende Abonnement noch durchgeführt, die automatische Verlängerung bleibt jedoch aus.

Bei einer Stornierung wird das aktuelle Abonnement storniert. Dies ist in der Regel nur innerhalb der ersten zwei bis vier Wochen nach der Bestellung möglich – je nach Kulanz des Anbieters.

Für beide Fälle enthält die Tabelle tblAbonnements je ein Datumsfeld. Das erste heißt GekuendigtAm, das zweite StorniertAm. Für den Bearbeiter des Abonnements ist es wohl am einfachsten, wenn er das Datum der Kündigung beziehungsweise der Stornierung manuell eingibt. Dies soll – neben dem Speichern des jeweiligen Datums – gleichzeitig noch eine weitere Aktion auslösen, nämlich den Versand einer E-Mail mit der Bestätigung der Kündigung oder Stornierung.

Wenn Sie nicht komplett digital arbeiten, können Sie natürlich auch einen Bericht mit den Kundendaten und den Daten der Kündigung oder Stornierung erzeugen, diesen ausdrucken und per Briefpost versenden.

Kündigung eintragen

Die Kündigung tragen Sie beispielsweise in das Textfeld txtGekuendigtAm des Unterformulars sfmAbonnements des Formulars frmKunden ein (s. Bild 1). Dies sorgt nach dem Bestätigen des eingetragenen Wertes für die Anzeige eines Meldungsfensters, mit dem der Benutzer festlegt, ob der Kunde eine E-Mail mit einer Kündigungsbestätigung erhalten soll.

pic003.png

Bild 1: Eintragen des Kündigungsdatums

Klickt der Benutzer anschließend auf Ja, erstellt die Anwendung eine E-Mail auf Basis der Benutzerdaten und öffnet diese (s. Bild 2).

pic002.png

Bild 3: Kündigungsbestätigung per Ereignisprozedur auslösen

Das Erstellen dieser E-Mail erfolgt mit Outlook. Der dazu benötigte Code ist überschaubar, da wir eine Klasse namens clsMail verwenden, die das Erstellen von E-Mails mit Outlook erheblich vereinfacht.

Diese Klasse und ihre Beschreibung finden Sie im Beitrag Outlook-Mails mit Klasse (www.access-im-unternehmen.de/859).

Um die Klasse clsMail in der Beispieldatenbank Abonnementverwaltung.mdb verfügbar zu machen, importieren Sie diese aus der Beispieldatenbank des Artikels Outlook-Mails mit Klasse. Dies gelingt am einfachsten, indem Sie die VBA-Projekte beider Datenbankanwendungen öffnen und die Klasse clsMails per Drag and Drop von einem Projekt-Explorer in den anderen ziehen.

Damit die Klasse funktioniert, fügen Sie außerdem noch einen Verweis auf die Bibliothek Microsoft Outlook x.0 Object Library zum VBA-Projekt hinzu (Menüeintrag Extras|Verweise im VBA-Editor).

Danach legen Sie die entsprechende Ereignisprozedur an. Öffnen Sie das Formular sfmAbonnements in der Entwurfsansicht und markieren Sie das Textfeld txtGekuendigtAm (die gebundenen Steuerelemente wurden mit entsprechenden Präfixen wie txt, cbo et cetera versehen).

Wählen Sie für die Eigenschaft Nach Aktualisierung den Eintrag [Ereignisprozedur] aus und klicken Sie auf die Schaltfläche mit den drei Punkten (s. Bild 3). Danach ergänzen Sie die im VBA-Editor erscheinende Ereignisprozedur wie in Listing 1.

Listing 1: Erstellen einer Kündigungsbestätigung per E-Mail

Private Sub txtGekuendigtAm_AfterUpdate()
    Dim objMail As clsMail
    If MsgBox("Bestätigung per E-Mail versenden", vbYesNo) = vbYes Then
        Set objMail = New clsMail
        With objMail
            .AnHinzufuegen Me.Parent!EMail
            .Betreff = "Kündigung ''" & Me!cboProduktID.Column(1) & "''"
            .Inhalt = "Hallo " & Me.Parent!cboAnredeID.Column(1) & " " & Me.Parent!txtNachname _
                & ", " & vbCrLf & vbCrLf _
                & "hiermit bestätigen wir Ihnen die Kündigung Ihres Abonnements von ''" _
                & Me!cboProduktID.Column(1) & "''" & vbCrLf & vbCrLf _
                & "Mit freundlichen Grüßen" & vbCrLf & vbCrLf _
                & "Ihr Abo-Service"
            .Anzeigen
        End With
    End If
End Sub

pic004.png

Bild 2: Kündigungsbestätigung per E-Mail

Die Prozedur deklariert ein Objekt des Typs clsMail mit dem Namen objMail. Danach fragt sie mit einer entsprechenden MsgBox-Anweisung, ob die Kündigung per E-Mail versendet werden soll. Falls ja, wird objMail mit einem neuen Objekt der Klasse clsMail instanziert. Mit der Funktion AnHinzufuegen wird der Empfänger hinzugefügt, der aus dem Feld EMail des übergeordneten Formulars (also frmKunden) gewonnen wird.

Den Betreff stellt die Prozedur aus dem Ausdruck Kündigung und dem in Hochkommata eingefassten Namen des Produkts zusammen, der aus der zweiten Spalte des Kombinationfeldes cboProduktID des aktuellen Datensatzes im Unterformular sfmAbonnements gewonnen wird.

Den Inhalt der E-Mail stellt die Prozedur ebenfalls aus einer Mischung aus Literalen und dynamischen Elementen zusammen. Dazu gehören die Anrede und der Nachname aus dem übergeordneten Formular und nochmals die Produktbezeichnung.

Weitere Kunden- und Abonnementdaten verwenden

Sie erkennen bereits, dass es relativ müßig ist, sich die Daten für die Kündigungsbestätigung aus dem Haupt- und dem Unterformular zusammenzuklauben. Noch aufwendiger und wartungsunfreundlicher wird es, wenn Sie noch andere Möglichkeiten zum Eintragen von Kündigungs- und Stornierungsdaten vorsehen möchten.

Sie werden gleich beispielsweise noch ein Formular kennenlernen, das einen Überblick über alle Abonnements gewährleistet und verschiedene Aktionen erlauben soll – auch hier soll das Eintragen von Kündigungs- und Stornierungsdaten möglich sein.

Normalerweise würde man dort zum Versenden der Kündigungs- und Stornierungsbestätigung gern den gleichen Code verwenden, den Sie soeben kennengelernt haben – entweder per Copy and Paste des Codes und kleinere Anpassungen oder durch Refaktorieren. Das bedeutet, dass Sie die Hauptelemente des Codes in eine eigene Funktion füllen und diese von den verschiedenen Stellen aus aufrufen.

Das Problem ist das Zusammenstellen der in der Bestätigung verwendeten Daten: Diese stammen teils aus dem Unterformular, in das Sie das Kündigungs- oder Stornierungsdatum eintragen, teils aus dem übergeordneten Hauptformular. Dieses ist aber vielleicht an anderer Stelle gar nicht vorhanden. Also müssen wir einen flexibleren Weg für den Zugriff auf die Daten des zu bearbeitenden Abonnements und des entsprechenden Kunden finden.

Dies wäre beispielsweise über eine Klasse möglich, die alle relevanten Daten des Kunden und des Abonnements enthält.

Diese wird vor einer Operation, welche auf die Daten des Abonnements zugreifen muss, erzeugt und mit den entsprechenden Stammdaten gefüllt und stellt diese dann über eine einfache Schnittstelle bereit.

Klasse für den einfachen Zugriff auf Abonnementdaten

Die Klasse soll einige der Daten der Tabellen des Datenmodells enthalten. Diese finden Sie in Bild 4 in einer Abfrage namens qryAbonnementsKlasse, die wir speziell für diesen Fall erstellt haben. Um diese Klasse zu erstellen – und zusätzlich eine kleine Prozedur, welche ein Objekt auf Basis dieser Klasse erstellt und diese mit den Daten eines angegebenen Datensatzes füllt -, verwenden wir das Add-In aiuKlassengenerator, das im Beitrag Klassengenerator (www.access-im-unternehmen.de/871) vorgestellt wird.

pic005.png

Bild 4: Felder, die in der Abonnement-Klasse abgebildet werden sollen

Starten Sie das Add-In mit dem Eintrag aiuKlassengenerator des Add-In-Menüs (s. Bild 5). Wählen Sie im Kombinationsfeld Tabelle oder Abfrage den Eintrag qryAbonnementsKlasse aus. Stellen Sie die Eigenschaften Klassenname auf clsAbonnement und Primärschlüsselfeld auf AbonnementID ein. Aktivieren Sie die Option Ladeprozedur hinzufügen und klicken Sie auf Erzeugen, um den Code zu erstellen.

pic006.png

Bild 5: Erstellen der Klasse für den einfachen Zugriff auf die Daten eines Abonnements

Kopieren Sie den oberen Teil des erstellten Codes (bis zur Prozedur ErzeugeAbonnement) in ein neues Klassenmodul namens clsAbonnement. Die Prozedur ErzeugeAbonnement kopieren Sie in das vorhandene Modul mdlAbonnements.

Das Klassenmodul enthält für jedes Feld der zugrunde liegenden Abfrage drei Elemente – eine Membervariable, eine Set/Let-Prozedur und eine Get-Prozedur:

Dim m_AbonnementID As Long
Public Property Get AbonnementID() As Long
    AbonnementID = m_AbonnementID
End Property
Public Property Let AbonnementID(lngAbonnementID As Long)
    m_AbonnementID = lngAbonnementID
End Property

Die Prozedur zum Erzeugen der Klasse auf Basis eines Datensatzes der Abfrage qryAbonnementsKlasse sieht wie in Listing 2 aus. Vereinzelt sind dort noch Anpassungen nötig – so kann es beispielsweise vorkommen, dass die Felder GekuendigtAm oder StorniertAm leer sind. Für diesen Fall fügen Sie dort noch die Nz-Funktion ein, die Nullwerte in diesem Fall durch 0 ersetzt:

objAbonnementsKlasse.StorniertAm = Nz(rst!StorniertAm, 0)

Listing 2: Erstellen eines Objekts auf Basis der Klasse clsAbonnements und Füllen der Eigenschaften der Klasse

Public Function ErzeugeAbonnementsKlasse(lngID As Long) As clsAbonnement
    Dim db As DAO.Database
    Dim rst As DAO.Recordset
    Dim objAbonnementsKlasse As clsAbonnement
    Set db = CurrentDb
    Set rst = db.OpenRecordset("SELECT * FROM qryAbonnementsKlasse WHERE AbonnementID = " _
        & lngID, dbOpenDynaset)
    Set objAbonnementsKlasse = New clsAbonnementsKlasse
    objAbonnementsKlasse.AbonnementID = rst!AbonnementID
    objAbonnementsKlasse.ProduktID = rst!ProduktID
    objAbonnementsKlasse.Produkt = rst!Produkt
    ''... weitere Zuweisungen
    Set ErzeugeAbonnementsKlasse = objAbonnementsKlasse
    Set db = Nothing
End Function

Alternativ können Sie auch den Datentyp für eine Eigenschaft in der Klasse clsAbonnement auf Variant einstellen – dieser nimmt auch Nullwerte entgegen. In diesem Fall ist dies die bessere Variante – hier umgesetzt für das Feld GekuendigtAm:

Dim m_GekuendigtAm As Variant
Public Property Get GekuendigtAm() As Variant
    GekuendigtAm = m_GekuendigtAm
End Property
Public Property Let GekuendigtAm(datGekuendigtAm As Variant)
    m_GekuendigtAm = datGekuendigtAm
End Property

Mit dieser Klasse können Sie etwa das Zusammenstellen der Kündigungsbestätigung viel einfacher gestalten – siehe Listing 3. Diese Prozedur können Sie flexibel aufrufen – also beispielsweise von der Ereignisprozedur txtGekuendigtAm_AfterUpdate aus, in der sich die Funktionalität zuvor befunden hat. Sie müssen nur die Nummer des Abonnements übergeben:

Private Sub txtGekuendigtAm_AfterUpdate()
    KuendigungSenden Me!AbonnementID
    End Sub

Listing 3: Erstellen der Kündigungsbestätigung, verbesserte Variante

Public Sub KuendigungSenden(lngAbonnementID As Long)
    Dim objMail As clsMail
    Dim objAbonnement As clsAbonnement
    If MsgBox("Bestätigung der Kündigung per E-Mail versenden", vbYesNo + vbExclamation, _
            "Kündigungsbestätigung") = vbYes Then
        Set objMail = New clsMail
        Set objAbonnement = ErzeugeAbonnementsKlasse(lngAbonnementID)
        With objMail
            .AnHinzufuegen objAbonnement.EMail
            .Betreff = "Kündigung ''" & objAbonnement.Produkt & "''"
            .Inhalt = "Hallo " & objAbonnement.Anrede & " " & objAbonnement.Nachname & ", " _
                & vbCrLf & vbCrLf _
                & "hiermit bestätigen wir Ihnen die Kündigung Ihres Abonnements von ''" _
                & objAbonnement.Produkt & "''." & vbCrLf & vbCrLf _
                & "Das Abonnement endet am " & Format(DateAdd("m", objAbonnement.Laufzeit, _
                objAbonnement.Startdatum), "dd.mm.yyyy") & "." & vbCrLf & vbCrLf _
                & "Mit freundlichen Grüßen" & vbCrLf & vbCrLf _
                & "Ihr Abo-Service"
            .Anzeigen
        End With
    End If
End Sub

Auf die gleiche Weise könnten Sie diese Prozedur nun von anderen Formularen aus aufrufen – dazu später mehr.

Die Prozedur KuendigungSenden zeigt auch, wie einfach Sie mit den Eigenschaften der Klasse etwa das Kündigungsdatum ermitteln können. Dazu greifen Sie direkt auf die Laufzeit und das Startdatum zu:

Format(DateAdd("m", objAbonnement.Laufzeit, _
objAbonnement.Startdatum), "dd.mm.yyyy")

Natürlich könnten Sie dies auch erledigen, wenn Sie statt des Objekts objAbonnement einfach ein Recordset auf Basis des betroffenen Datensatzes verwenden. Allerdings lässt es sich mit der Klasse viel einfacher programmieren, weil diese alle Felder beziehungsweise Eigenschaften per IntelliSense zur Verfügung stellt.

Abgelaufene Abonnements ermitteln und verlängern

Wenn alle Ausgaben eines Abonnements versendet wurden, soll das Abonnement – so der Kunde dieses nicht gekündigt hat – in der Regel verlängert werden.

Mit steigender Kunden- und Abonnement-Zahl wird der Aufwand, alle zu verlängernden Abonnements von Hand zu ermitteln und zu verlängern, immer größer.

Also benötigen wir eine Prozedur, die alle Abonnements ermittelt, für die bereits alle Ausgaben versendet wurden. Diese muss natürlich die stornierten und gekündigten Abonnements ausklammern und – ganz wichtig – auch die bereits zuvor verlängerten Abonnements.

Solch eine Funktion sollte man nicht blind aufrufen, sondern sich zuvor optisch einen Überblick über die zu verlängernden Abonnements verschaffen. Dazu erstellen Sie ein Übersichtsformular, das die Abonnements diesmal nicht aus Kundensicht, sondern allgemein erfasst. Das heißt, dass einfach alle Abonnements untereinander angezeigt werden. Das Formular sieht wie in Bild 6 aus und bietet hier bereits einige Filtermöglichkeiten an:

pic001.png

Bild 6: Formular zum Verwalten der einzelnen Abonnements

  • Stornierte Abonnements
  • Gekündigte Abonnements
  • Abonnements, deren Ausgaben komplett versendet wurden
  • Abonnements, die verlängert wurden
  • Abonnements, deren Laufzeit erfüllt ist

Für alle Filtermöglichkeiten stehen die Einstellungen Alle anzeigen, Ja und Nein bereit.

Ein Doppelklick auf die ID des Abonnements soll dieses in Zusammenhang mit dem jeweiligen Kunden im Formular frmKunden anzeigen. Die beiden Schaltflächen rechts oben sollen alle Abonnements verlängern oder das aktuelle Abonnement verlängern, soweit möglich.

Wozu gibt es eine Filtermöglichkeit nach Abonnements, deren Ausgaben komplett versendet wurden, und eine für Abonnements, deren Laufzeit erfüllt ist

Dies ist für den Fall vorgesehen, dass ein neuer Abonnent etwa am 15. Januar 2012 in das Abonnement einsteigt und gleich die aktuelle Ausgabe erhält, die möglicherweise bereits am 1. Januar 2012 erschienen ist.

Das heißt, dass der Kunde – sofern alle Ausgaben am ersten Tag eines Monats erscheinen – am 1. Dezember 2012 alle zwölf Ausgaben erhalten hat. Eine Verlängerung sollte jedoch erst erfolgen, wenn auch der Zeitraum des Abonnements (hier ein Jahr) abgelaufen ist.

Der Grund ist, dass sich Kündigungsfristen in der Regel auf das Bestelldatum beziehen und nicht auf den Zeitpunkt, zu dem das Abonnement erfüllt ist.

Erhält der Kunde also bei Beginn des Abonnements gleich die aktuelle Ausgabe, sollte vor der Verlängerung dennoch der Abozeitraum abgelaufen sein.

Datenherkunft des Formulars

Als Datenherkunft des Unterformulars der Abonnementübersicht, sfmAbonnementsVerwalten, dient die Abfrage aus Bild 7. Die Abfrage führt die Daten aus den Tabellen tblAbonnements, tblVersendungen und tblProdukte zusammen.

pic007.png

Bild 7: Datenherkunft des Unterformulars sfmAbonnementsVerwalten

Die Verknüpfung zur Tabelle tblProdukte erlaubt das Berücksichtigen der Produktdaten zum jeweiligen Abonnement. Die Tabelle tblAbonnements taucht gleich zweimal auf. Dies hat den Grund, dass die Abfrage auch die Möglichkeit bieten soll, verlängerte Abonnements zu identifizieren.

Zu diesem Zweck ist die Tabelle tblAbonnements_1 über das Feld FolgeAboVon mit der Tabelle tblAbonnements verknüpft. Die Beziehung ist so ausgelegt, dass alle Datensätze der Tabelle tblAbonnements angezeigt werden, aber nur solche der Tabelle tblAbonnements_1, die mit tbl-Abonnements verknüpft sind.

Sprich: Wenn das Feld AbonnementID der Tabelle tblAbonnements_1 der Abfrage den Wert Null hat, gibt es für das Abonnement des entsprechenden Datensatzes der Tabelle tblAbonnemtens kein Folgeabonnement – das Abonnement wurde also noch nicht verlängert.

Auch zur Tabelle tblVersendungen gibt es eine solche Verknüpfung. Dies stellt sicher, dass auch solche Abonnements angezeigt werden, für die noch keine Versendungen in der Tabelle tblVersendungen hinterlegt wurden.

Da die Tabelle tblVersendungen in der Regel mehrere Datensätze enthält, die mit einem Datensatz der Tabelle tblAbonnements verknüpft sind, zeigt die Abfrage standardmäßig jeden Datensatz der Tabelle tblAbonnements entsprechend der Anzahl der verknüpften Datensätze in der Tabelle tblVersendungen an. Es soll jedoch jedes Abonnement nur einmal erscheinen.

Deshalb aktivieren Sie die Gruppierungsebene für Abfragen. Für die meisten Felder behalten Sie die Einstellung Gruppierung in der Zeile Funktion bei – jedoch nicht für die beiden Felder VersendungID und Versanddatum der Tabelle tblVersendungen.

Für diese beiden Felder soll die Abfrage die Anzahl der Werte für jede Gruppierung (also für jedes Abonnement) liefern. Praktischerweise ermittelt die Anzahl-Funktion in Gruppierungen nur die Anzahl für Datensätze, die im betroffenen Feld einen Wert ungleich Null enthalten.

Das bedeutet, dass die Anzahl-Funktion für das Feld VersendungID die Gesamtzahl der zu versendenden Ausgaben für ein Abonnement liefert (das Primärschlüsselfeld ist immer gefüllt).

Das Feld Versanddatum hingegen enthält nur einen Wert, wenn die Versendung bereits erfolgt ist.

Sind also etwa zwölf Datensätze in der Tabelle tblVersendungen für ein Abonnement enthalten, von denen drei bereits versendet wurden, liefern die beiden mit der Anzahl-Funktion versehenen Felder genau diese Werte zurück.

Wozu das Ganze Nun: Das ist ein einfacher Weg, um zu ermitteln, ob bereits alle Versendungen zu einem Abonnement durchgeführt wurden – in diesem Fall muss die Anzahl der Werte im Feld VersendetAm gleich der Anzahl der Werte im Feld VersendungID sein.

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