André Minhorst, Duisburg
Im ersten Teil dieser Beitragsreihe haben Sie das Datenmodell und die wesentlichen Formulare der Artikelverwaltung kennen gelernt. Nun beschäftigen Sie sich mit den Inventuren: Welche Faktoren sind dafür wichtig Wie werden bereits in einer Inventur berücksichtigte Daten bei folgenden Inventuren ausgeschlossen Außerdem erfahren Sie in dieser Folge, wie Sie Berichte für die Ausgabe von Artikel- und Inventurdaten erstellen.
Hinweis
Die Musterlösung zu diesem Beitrag heißt Artikelverwaltung00.mdb und funktioniert mit Access 2000 und höher. Sie finden die Datenbank auf der Heft-CD oder im Internet unter http://www.access-im-unternehmen.de (Shortlink 280). Der erste Teil dieses Beitrags steht an gleicher Stelle unter dem Shortlink 273 bereit.
Eine Inventur ist die Aufnahme des Bestands aller Vermögenswerte eines Unternehmens, also auch der vorhandenen Artikel. Die in die Artikelverwaltung integrierte Inventurfunktion ermöglicht dies ausschließlich für die Artikel; die Aufnahme aller für eine Inventur notwendigen Informationen würde den Umfang des Beitrags leider sprengen. Diese Inventurfunktion berücksichtigt lediglich die in der Datenbank gespeicherten Artikel. Die Menge der vorhandenen Artikel sowie deren Maßeinheiten sollten regelmäßig zum Bilanzstichtag bekannt sein, um eine ordnungsgemäße Buchhaltung zu ermöglichen. Der Artikelbestand muss allerdings nicht genau an diesem Tag gezählt werden.
Inventurarten
Bild 1: Aufruf der Inventur
Es gibt mehrere Varianten von Inventuren, unter anderem die folgenden:
Die Anwendung unterstützt alle genannten Inventurarten. Der einzige Unterschied zwischen den Arten ist der Zeitpunkt, an dem Soll- und Istwert des Bestands eines Artikels ermittelt werden müssen. Mit der Artikelverwaltung können Sie den Zeitpunkt der Zählung für jeden einzelnen Artikel festlegen.
Wie arbeitet die Inventurfunktion
Die Inventurfunktion kann für jeden Artikel einzeln aufgerufen werden. Der Aufruf erfolgt über die Schaltfläche Inventur starten der Detailansicht eines Artikels im Formular frmArtikel (siehe Bild 1).
Die dadurch ausgelöste Ereignisprozedur öffnet das Formular frmInventur für den aktuell im Artikelformular angezeigten Artikel (siehe Bild 2).
Bild 2: Eingabe des gezählten Bestands
Dieses Formular zeigt folgende Informationen an:
Die mit einem roten Rahmen versehenen Informationen kann der Benutzer selbst ändern. Das Datum wird automatisch mit dem aktuellen Datum vorgelegt, es kann aber nachträglich geändert werden.
Bei der Programmierung des Formulars frmInventur und der enthaltenen Funktionen sind einige Punkte zu beachten, die im nachfolgenden Abschnitt geklärt werden. Anschließend finden Sie eine Beschreibung der Funktion selbst.
Wichtige Voraussetzungen
Die Inventurfunktion startet mit dem Mausklick auf die Schaltfläche Inventur starten des Artikelformulars. Das Formular frmInventur zur Eingabe der Inventurdaten bezieht seine Daten über diverse Zugriffe auf die in der Datenbank enthaltenen Tabellen.
So durchsucht das Formular die Tabelle tblInventuren, ob es bereits eine Inventur zu dem zu untersuchenden Artikel gibt, und liest gegebenenfalls das Datum der Inventur und den Bestand in die entsprechenden Felder des Formulars ein.
Ist noch kein Inventurdatensatz vorhanden und dementsprechend noch keine Zählung des Artikels erfolgt, verwendet das Formular Standardwerte: als Datum den 1.1.1900 und als Bestand den Wert 0.
Der im Feld Berechneter Bestand angezeigte Wert ist die Anzahl des Artikels, die sich aus dem Stand bei der letzten Inventur und den seitdem erfolgten Zu- und Abgängen berechnet.
Dabei spielt natürlich das Datum der aktuellen Inventur eine wichtige Rolle: Beim öffnen des Formulars zeigt dieses das aktuelle Datum und den Bestand zu diesem Datum an. Wenn der Benutzer das Datum verändert, wird auch der berechnete Bestand angepasst. Neben dem Inventurdatum muss der Benutzer auch die tatsächliche Anzahl des Artikels angeben. Erst dann wird die Differenz berechnet und die Inventurdaten können gespeichert werden.
Private Sub Form_Current() Dim datLetzteInventur As Date Dim lngBestandLetzteInventur As Long Me!txtArtikel = Artikelbezeichnung(lngArtikelID) Call LetzteInventur(lngArtikelID, datLetzteInventur, lngBestandLetzteInventur) Me!txtDatumLetzteInventur = datLetzteInventur Me!txtBestandLetzteInventur = lngBestandLetzteInventur Me!txtDatumAktuelleInventur = Date Me!txtBestandAktuelleInventurBerechnet = lngBestandLetzteInventur + _ BestandsaenderungSumme(lngArtikelID, Date) End Sub
Quellcode 1
Private Function Artikelbezeichnung(lngArtikelID As Long) As String Dim db As DAO.Database Dim rstArtikel As DAO.Recordset Set db = CurrentDb Set rstArtikel = db.OpenRecordset("SELECT Bezeichnung FROM tblArtikel " _ & "WHERE ArtikelID = " & lngArtikelID, dbOpenDynaset) Artikelbezeichnung = rstArtikel!Bezeichnung Set db = Nothing End Function
Quellcode 2
Technische Umsetzung
Die erste Prozedur, die bei der Verwendung des Formulars frmInventuren zum Zuge kommt, ist die Ereignisprozedur, die beim öffnen des Formulars ausgelöst wird. Sie weist der modulweit deklarierten Variablen lngArtikel die per öffnungsargument von der aufrufenden Instanz übergebene Artikelnummer zu.
Private Sub Form_Open(Cancel As Integer) lngArtikelID = Me.OpenArgs End Sub
Die Ermittlung der im Formular angezeigten Werte erfolgt in der Ereignisprozedur, die durch das Ereignis Beim Anzeigen ausgelöst wird (s. Quellcode 1). Die Prozedur ermittelt zunächst anhand des Werts des Feldes ArtikelID die Artikelbezeichnung und trägt diese in das Textfeld txtArtikel ein. Dazu verwendet sie die Prozedur aus Quellcode 2. Diese Prozedur liefert die Artikelbezeichnung für die als Parameter übergebene Artikel-ID. Dabei instanziert sie ein Database-Objekt und ein Recordset-Objekt und ermittelt über eine entsprechende Abfrage eine Datensatzgruppe mit dem gesuchten Artikeldatensatz.
Performance statt Dlookup
Statt dieser Funktion hätte man auch einen entsprechenden Aufruf der Dlookup-Funktion verwenden können (in einer Zeile):
Me!txtArtikel = DLookup("Bezeichnung", "tblArtikel", "ArtikelID = " & lngArtikelID)
Das wäre zwar wesentlich platzsparender und damit auch übersichtlicher gewesen, allerdings ist die Performance der Domänenfunktionen relativ schlecht. Ein konkreter Test mit je 1000 Aufrufen jeder Funktion in einer Schleife brachte ein eindeutiges Ergebnis: über mehrere Läufe hinweg war die DAO-Variante circa 50-100% schneller als die Dlookup-Funktion.
Private Sub LetzteInventur(lngArtikelID As Long, datLetzteInventur As Date, _ lngBestandLetzteInventur As Long) Dim db As DAO.Database Dim rstInventuren As DAO.Recordset Dim rstBestandsaenderungen As DAO.Recordset Set db = CurrentDb Set rstInventuren = db.OpenRecordset("SELECT TOP 1 Inventurdatum, " _ & "Lagerbestand FROM tblInventuren WHERE ArtikelID = " & lngArtikelID _ & " ORDER BY Inventurdatum DESC", dbOpenDynaset) If Not rstInventuren.EOF Then datLetzteInventur = rstInventuren!Inventurdatum lngBestandLetzteInventur = rstInventuren!Lagerbestand Else Set rstBestandsaenderungen = db.OpenRecordset("SELECT Datum " _ & "FROM tblBestandsaenderungen WHERE ArtikelID = " & lngArtikelID _ & " ORDER BY Datum DESC", dbOpenDynaset) If Not rstBestandsaenderungen.EOF Then datLetzteInventur = rstBestandsaenderungen!Datum Else datLetzteInventur = "1.1.1900" End If lngBestandLetzteInventur = 0 rstBestandsaenderungen.Close End If rstInventuren.Close Set rstInventuren = Nothing Set rstBestandsaenderungen = Nothing End Sub
Quellcode 3
Daten der letzten Inventur ermitteln
In einer weiteren externen Routine ermittelt die Prozedur Form_Current das Datum der letzten Inventur und den Bestand bei der letzten Inventur.
Da die beiden zu ermittelnden Werte aus der gleichen Tabelle stammen und gleichzeitig mit einer Routine ermittelt werden sollen, können nicht beide per Rückgabewert an die aufrufende Prozedur übermittelt werden.
Die betroffene Routine heißt LetzteInventur und hat drei Parameter, von denen einer als Eingangs- und zwei als Ausgangsparameter verwendet werden. Die Ausgangsparameter werden bereits in der aufrufenden Prozedur Form_Current deklariert und als Variable übergeben (in einer Zeile):
Call LetzteInventur(lngArtikelID, datLetzteInventur, lngBestandLetzteInventur)
Die beiden letzten Parameter dieses Aufrufs werden in der Prozedur LetzteInventur mit den gewünschten Daten gefüllt (s. Quellcode 3).
Um diese Daten zu ermitteln, greift die Prozedur auf zwei Tabellen zu. Zunächst sucht sie in der Tabelle tblInventuren nach der aktuellsten Inventur für diesen Artikel. Die dazu verwendete Abfrage ist nach dem Inventurdatum sortiert und gibt durch die Option TOP 1 nur den ersten Eintrag der Datensatzgruppe zurück. Ist dieser Datensatz vorhanden, liest die Prozedur das Inventurdatum und den Lagerbestand aus der Tabelle aus und schreibt sie in die entsprechenden Rückgabeparameter.
Private Function BestandsaenderungSumme(lngArtikelID As Long, _ datAktuelleInventur As Date) As Long Dim db As DAO.Database Dim rstBestandsaenderungSumme As DAO.Recordset Set db = CurrentDb Set rstBestandsaenderungSumme = _ db.OpenRecordset("SELECT Sum(Anzahl * Vorzeichen) AS LagerbestandGesamt " _ & "FROM tblBestandsaenderungen WHERE ArtikelID = " & lngArtikelID _ & " AND Inventur = False AND Datum <= " _ & ISODatum(Me.txtDatumAktuelleInventur), dbOpenDynaset) BestandsaenderungSumme = Nz(rstBestandsaenderungSumme!LagerbestandGesamt, 0) rstBestandsaenderungSumme.Close Set rstBestandsaenderungSumme = Nothing Set db = Nothing End Function
Quellcode 4
Wenn die Datensatzgruppe leer ist, wurde noch keine Inventur für diesen Artikel durchgeführt. In dem Fall sind weitere Untersuchungen erforderlich, um halbwegs sinnvolle Werte zurückzugeben. Wenn noch keine Inventur stattgefunden hat, wäre der Stand vor der ersten Bestandsänderung der letzte bestätigte Stand – und der ist immer 0. Als Datum stellt die Prozedur in dem Fall das Datum der ersten Bestandsänderung ein. Wenn weder eine Bestandsänderung noch eine Inventur für einen Artikel stattgefunden hat, ist Fantasie gefragt: Als Datum wird dann der 1.1.1900 und als Bestand der Wert 0 zurückgegeben.
Einstellen des Datums der aktuellen Inventur
Als Datum für die aktuelle Inventur trägt die Prozedur Form_Current das aktuelle Datum ein.
Bestandsänderung berechnen
Fehlt noch die berechnete Bestandsänderung seit der letzten Inventur. Für deren Ermittlung ist wiederum eine Funktion zuständig. Diese heißt BestandsaenderungSumme und erwartet die Artikel-ID und das Datum der aktuellen Inventur, das auch in diesem Fall mit der Date-Funktion ermittelt wird (s. Quellcode 4).
Diese Funktion berechnet mit der Sum-Funktion in einer Abfrage über die Datensätze der Tabelle tblBestandsaenderungen die Größe der änderung am Bestand des betroffenen Artikels.
Dabei offenbart sich die Technik, mit der eine Bestandsveränderung als inventarisiert gekennzeichnet wird: Die Tabelle tblBestandsaenderungen enthält ein Feld namens Inventur, das nach der Berücksichtigung in einer Inventur den Wert True erhält.