Verschil rotatie en leeftijd
In dit artikel bespreken we dus de leeftijd van de voorraad, maar in een ander artikel hebben we het ook gehad over de voorraadrotatie. Het het kort kijk je bij de rotatie hoe vaak voorraad wordt omgezet en kijk je bij een leeftijdsanalyse naar hoe oud de huidige voorraad is.
Voorraadrotatie is nuttig als je wilt weten hoe efficiënt de voorraad in de loop van het jaar is gebruikt. Je kijkt naar doorstroom en omloopsnelheid. Dit is vooral handig bij bedrijven met veel artikelen en continue verkopen.
Leeftijdsanalyse is nuttig als je wilt weten hoe oud de huidige voorraad is op de balansdatum. Je kijkt dus meer naar de actuele risico’s op incourantheid of afwaardering. Dit is vooral belangrijk bij artikelen met een beperkte houdbaarheid, die seizoensgebonden zijn of waarbij de de branch trendgevoelig is.
In de praktijk vullen beide analyses elkaar aan: rotatie laat de dynamiek over de tijd zien, terwijl leeftijd de foto van het moment geeft. Het kan dus best zijn dat de rotatie het begin van het jaar behoorlijk goed was, maar dat de restanten op de balansdatum al behoorlijk oud zijn.
Wat hebben we nodig?
Goed, laten we dan beginnen met de analyse zelf. Voor deze analyse gebruiken we Python met pandas en de dummy dataset met voorraadmutaties:
De dataset bevat per product alle bewegingen (inkopen, verkopen, correcties en begin-/eindbalansen).
import pandas as pd
from datetime import datetime
# We laden de data direct vanaf de website.
# Hierdoor is de code direct uitvoerbaar.
url = "https://www.theauditanalytics.com/data/movement/dummy_stock_movements.csv"
df = pd.read_csv(url, sep="|")
# Zorg ervoor dat de 'Date' kolom van het type datetime is
df['Date'] = pd.to_datetime(df['Date'])
FIFO-logica
Hierna implementeren we de FIFO-logica. Immers, we willen de leeftijd weten van de actuele voorraad. Wanneer een product een half jaar geleden is ingekocht, maar inmiddels alweer verkocht, mag dat niet als oude voorraad meetellen. Het gaat tenslotte alleen om de voorraadlagen die nog aanwezig zijn op balansdatum. Om dit goed te verwerken gebruiken we een vereenvoudigde FIFO (First In, First Out) aanpak:
- Elke inkoop creëert een voorraadlaag met de ontvangstdatum.
- Elke verkoop vermindert eerst de oudste beschikbare laag.
- Op balansdatum blijven alleen de resterende lagen over. Alleen die tellen mee in de leeftijdsanalyse.
Stap 1: Bouw de FIFO-lagen
We simuleren inkopen en verkopen in chronologische volgorde en houden bij welke aantallen er op balansdatum nog over zijn.
# Sorteer de transacties op datum om de FIFO-logica correct toe te passen
df = df.sort_values(by='Date')
# We gebruiken een deque voor efficiënte 'pop' operaties vanaf de linkerkant (oudste laag)
from collections import deque
# Dictionary om de voorraadlagen per product bij te houden
stock_layers = {}
# Loop door alle transacties
for _, row in df.iterrows():
item = row['ItemID']
qty = row['Quantity']
date = row['Date']
price = row.get('UnitPrice', 0) # Gebruik .get() voor het geval de prijs ontbreekt
# Als het een inkoop is, voegen we een nieuwe laag toe
if row['MutationType'] == 'Purchase':
stock_layers.setdefault(item, deque()).append([qty, date, price])
# Als het een verkoop is, werken we de oudste lagen bij
elif row['MutationType'] == 'Sales':
to_sell = qty
# Ga door zolang er verkocht moet worden en er voorraad is
while to_sell > 0 and stock_layers.get(item):
# Pak de oudste laag
layer_qty, layer_date, layer_price = stock_layers[item][0]
# Als de laag volledig wordt verkocht
if layer_qty <= to_sell:
to_sell -= layer_qty
stock_layers[item].popleft() # Verwijder de oudste laag
# Als de laag gedeeltelijk wordt verkocht
else:
stock_layers[item][0][0] -= to_sell # Verminder de hoeveelheid in de laag
to_sell = 0
# Verzamel alle resterende lagen na het verwerken van alle transacties
fifo_layers = []
for item, layers in stock_layers.items():
for layer_qty, layer_date, layer_price in layers:
fifo_layers.append({
'ItemID': item,
'Date': layer_date,
'Quantity': layer_qty,
'UnitPrice': layer_price
})
# Maak een DataFrame van de overgebleven FIFO-lagen
fifo_df = pd.DataFrame(fifo_layers)
Stap 2: Bereken leeftijd van de resterende voorraad
We analyseren alleen de lagen die nog bestaan op balansdatum.
# Stel de peildatum in voor de leeftijdsberekening (e.g., balansdatum)
peildatum = datetime(2024, 12, 31)
# Bereken de leeftijd in dagen voor elke voorraadlaag
fifo_df['AgeDays'] = (peildatum - fifo_df['Date']).dt.days
# Bereken de waarde van elke voorraadlaag
fifo_df['StockValue'] = fifo_df['Quantity'] * fifo_df['UnitPrice']
Stap 3: Indelen in leeftijdscategorieën
# Definieer de leeftijdscategorieën (buckets) in dagen
bins = [0, 30, 90, 180, 9999]
labels = ["0-30 dagen", "31-90 dagen", "91-180 dagen", ">180 dagen"]
# Wijs elke voorraadlaag toe aan een leeftijdscategorie
fifo_df['AgeBucket'] = pd.cut(fifo_df['AgeDays'], bins=bins, labels=labels, right=True)
# Groepeer per categorie en sommeer de voorraadwaarde
age_summary = fifo_df.groupby('AgeBucket')['StockValue'].sum().reset_index()
# Toon de samenvatting
print(age_summary)
Voorbeeldresultaat (dummy data):
Leeftijdscategorie | Totale waarde |
---|---|
0–30 dagen | € 48.200 |
31–90 dagen | € 21.400 |
91–180 dagen | € 5.100 |
>180 dagen | € 12.600 |
Stap 4: Visualisatie
import matplotlib.pyplot as plt
# Maak een bar chart om de resultaten te visualiseren
plt.figure(figsize=(8,5)) # Bepaal de grootte van de figuur
plt.bar(age_summary['AgeBucket'], age_summary['StockValue'])
# Voeg titels en labels toe voor de duidelijkheid
plt.title('Leeftijdsanalyse voorraad (FIFO)')
plt.xlabel('Leeftijdscategorie')
plt.ylabel('Totale voorraadwaarde (€)')
# Toon de grafiek
plt.show()
Conclusie
Met deze leeftijdsanalyse zie je snel welke producten lang in de voorraad blijven hangen. Grote voorraden in de categorie >180 dagen kunnen duiden op incourante voorraad en een risico op afwaardering. Hopelijk helpt dit bij de verwerking van jouw audit, veel succes!