Een geld-goederenbeweging is niet bij alle bedrijven direct toepasbaar. Los van de datakwaliteit en de beschikbare data, worden dergelijk analyses ook voornamelijk uitgevoerd bij handelsorganisaties, productiebedrijven en groothandels, omdat bij deze organisaties de goederenstroom direct verband houdt met omzet en inkoop. Als je geen voorraad hebt, is er ook weinig om te volgen immers.
Benodigdheden
Voor deze controle gebruiken we:
We gebruiken Python (pandas) om de bestanden in te laden en de analyse stap voor stap uit te voeren. Nog geen python geinstalleerd? Ontdek hier hoe je python en packages voor de audit kan installeren.
Aannames
In onze analyses gaan we er vanuit dat beide bestanden betrouwbaar zijn. Dit is echter wel belangrijk om altijd goed vast te stellen. Heb je bijvoorbeeld een voorraadtelling gedaan en deze aangesloten op de begin- of eindbalans van je voorraad? En sluiten de mutaties ook op de saldibalans aan? Dan heb je in ieder geval wat meer zekerheid.
Stap 1: Bestanden inladen
Allereerst gaan we de bestanden inladen. We gebruiken voor deze analyse de dummy data.
import pandas as pd
# Importing financial data
fin = pd.read_csv("dummy_financial_bookings.csv", sep=";")
# Importing stock data
stock = pd.read_csv("dummy_stock_movements.csv", sep="|")
Stap 2: Bereken de formule vanuit de voorraadmutaties
Dan gaan we nu voor alle artikelen de hele flow in kaart brengen. Alleereerst groeperen we de verschillende mutaties in de volgende categorien:
- Beginvoorraad
- Inkopen
- Verkopen
- Eindvoorraad
Je zou verwachten dat als je de beginstand hebt en daar de ingekochte goederen bij neemt, het aantal groter wordt (+) en als je dan producten verkoopt de voorraad naar beneden gaat (-) om uiteindelijk tot een eindstand te komen. Voor de controle kunnen we dit ook uitwerken:
Eindvoorraad = Beginvoorraad + Inkopen - Verkopen.
Als we dan echter de volledigheid van de verkopen te willen testen om straks aan te sluiten met de financiele mutaties, passen we de vergelijking iets aan:
Verwachtte verkopen = Beginvoorraad + Inkopen - Eindvoorraad.
Dat werken we hieronder uit:
# Get the total quantity per item at the start
start_inventory = stock[stock['MutationType'] == 'Balance Begin'].groupby('ItemID')['Quantity'].sum()
# Get the total quantity per item at the end
end_inventory = stock[stock['MutationType'] == 'Balance End'].groupby('ItemID')['Quantity'].sum()
# Get the total purchases per item
purchases = stock[stock['MutationType'] == 'Purchase'].groupby('ItemID')['Quantity'].sum()
# Get the total sales er item
sales = stock[stock['MutationType'] == 'Sales'].groupby('ItemID')['Quantity'].sum()
# Perform check
controle = start_inventory + purchases - end_inventory
controle = controle.rename('Calculated_Sales')
# Merge the check with reality
result = pd.concat([controle, sales.rename('Registered_Sales')], axis=1)
# Calculate differences
result['Difference'] = result['Calculated_Sales'] - result['Registered_Sales']
print(result)
Uiteindelijk is dit dan het resultaat:
Je ziet dat er dan nog wel wat verschillen zijn.. Dat heeft er mee te maken dat in de dataset van de voorraadmutaties ook andere stromen te zien zijn:
- Voorraad verplaatsing
- Vernietiging van producten
Dit is best gebruikelijk en zal in de praktijk niet de enige stroom zijn in de gegevens. De verplaatsing maakt geen verschil voor het eindtotaal, maar de vernietiging uiteraard wel. Als we die stroom ook inzichtelijk maken kunnen kijken of dat de verschillen verklaart.
Nu sluit het perfect aan en weten we dat de stroom voor verkopen volledig is. Nu is de goederenbeweging in kaart gebracht, maar willen we er een geld-goederenbeweging van maken, dan zullen we de auditfile er ook bij moeten betrekken.
Stap 3: Aansluiting maken met de financiële administratie
Dat kan op twee manieren gedaan worden: op factuurniveau of totaalniveau. Bij die eerste moet deze informatie wel beschikbaar zijn in beide datasets. Dat is niet altijd het geval. Op totaalniveau kan ook:
# Create an amount column
fin['Debit'] = pd.to_numeric(fin['Debit'], errors='coerce').fillna(0)
fin['Credit'] = pd.to_numeric(fin['Credit'], errors='coerce').fillna(0)
fin['Amount'] = fin['Debit'] - fin['Credit']
# Get the total revenue of goods (account 8000 in this case)
revenue = fin[fin['Account Number'] == 8000]['Amount'].sum()
print(f"Total revenue (financial system): {revenue}")
Dan kunnen we die vergelijken met de waarde volgens de voorraadmutaties. In dit geval zit de unitprice in de data, maar mogelijk moet je deze uit een andere bron halen (bijvoorbeeld de verkoopfacturen).
stock['SalesValue'] = stock['Quantity'] * stock['UnitPrice']
sales_value = stock[stock['MutationType'] == 'Sales']['SalesValue'].sum()
print(f"Totale revenue (goods movement) {sales_value}")
Total revenue (financial system): -805962.1699999999
Totale revenue (goods movement) 805961.5499999999
Dat sluit in dit geval bijna perfect aan, maar niet helemaal. Het is ongetwijfeld ver onder iedere materialiteit. Waarschijnlijk heeft dit met afrondingen te maken.
Op deze manier kun je controleren of de omzet in de administratie aansluit op de berekende omzet vanuit de voorraadmutaties.