Comment créer un module de prévision d'inventaire personnalisé pour Magento 2
Introduction
In this post I’ll walk you through building a custom Inventory Forecasting module for Magento 2. I’ll keep the tone relaxed — think of this as a chat with a teammate who’s comfortable with PHP and Magento basics but new to forecasting. We’ll cover architecture, modèle de donnéess, integration with Magento Inventory (MSI), prediction approchees (simple statistics and ML), a custom admin tableau de bord, and réel cas d'utilisation (how forecasts reduce stockouts and optimize supplier commandes).
Why build a custom module?
Magento 2 doesn’t ship with advanced forecasting. Vous pouvez find paid extensions, but building your own vous donne full control: tailor the model to your catalog, integrate with your procurement flux de travails, and host everything alongside your store (or call an ML microservice). For stores with specific needs — custom bundles, multi-source inventaire, or special recommande rules — a bespoke module beats one-size-fits-all solutions.
High-level design and goals
- Collect sales history per SKU and source (MSI).
- Compute forecasts on a schedule (cron) or on-demand.
- Store forecasts and show them in an admin tableau de bord with charts.
- Offer simple statistical méthodes for small shops and a path to ML service for larger catalogs.
- Expose APIs so procurement automation or tiers tools can consume forecasts.
Architecture technical: structure du module and optimized modèle de donnéess
Let’s map the architecture first. Keep it simple and Magento-friendly:
- Magento module code that integrates with MSI and sales data.
- Custom DB tables to store aggregated historical sales and computed forecasts.
- Cron jobs to aggregate history and compute forecasts.
- Admin UI (menu, page, points d'accès API) for visualization and manual triggers.
- Optional: an external ML microservice for advanced models.
Module dossier layout
app/code/MageFine/InventoryForecast/
├── etc/
│ ├── module.xml
│ ├── di.xml
│ ├── adminhtml/routes.xml
│ ├── crontab.xml
│ └── db_schema.xml
├── registration.php
├── composer.json
├── Cron/
│ └── ForecastRunner.php
├── Model/
│ ├── Aggregator.php
│ ├── ForecastCalculator.php
│ └── ResourceModel/
│ ├── Historical.php
│ └── Forecast.php
├── Api/
│ └── ForecastRepositoryInterface.php
├── Setup/
│ └── InstallData.php (if needed)
├── Controller/Adminhtml/Index/Index.php
├── view/adminhtml/layout/...
└── view/adminhtml/templates/...
Database design (optimized)
You’ll want two small, efficient tables:
- magefine_inventaire_historical: aggregated daily sales per sku and source.
- magefine_inventaire_forecast: forecast records (sku, source, period_start, period_end, predicted_qty, méthode, confidence).
-- Simplified db_schema.xml snippet (declarative schema)
<table name="magefine_inventaire_historical" resource="default" engine="innodb" comment="MageFine inventaire historical sales">
<colonne xsi:type="int" name="id" nullable="false" unsigned="true" identity="true"/>
<colonne xsi:type="varchar" name="sku" nullable="false" length="64"/>
<colonne xsi:type="varchar" name="source_code" nullable="true" length="32"/>
<colonne xsi:type="date" name="sales_date" nullable="false"/>
<colonne xsi:type="int" name="qty_sold" nullable="false" default="0"/>
<constraint referenceId="PRIMARY"/>
</table>
<table name="magefine_inventaire_forecast" resource="default" engine="innodb" comment="MageFine inventaire forecasts">
<colonne xsi:type="int" name="id" nullable="false" unsigned="true" identity="true"/>
<colonne xsi:type="varchar" name="sku" nullable="false" length="64"/>
<colonne xsi:type="varchar" name="source_code" nullable="true" length="32"/>
<colonne xsi:type="date" name="period_start" nullable="false"/>
<colonne xsi:type="date" name="period_end" nullable="false"/>
<colonne xsi:type="decimal" name="predicted_qty" nullable="false" scale="2" precision="12"/>
<colonne xsi:type="varchar" name="méthode" nullable="true" length="32"/>
<colonne xsi:type="decimal" name="confidence" nullable="true" scale="4" precision="8"/>
</table>
Why aggregated daily lignes? Two reasons: the raw commandes table peut être huge; pre-aggregating simplifies forecasting and reduces memory usage. Aggregating per SKU per source per day vous donne the timeseries you need without high-cost JOINs at forecast time.
Data aggregation: pulling historical sales
You’ll want a class that runs daily (cron) that reads sales_commande_item (or your analytics table) and fills magefine_inventaire_historical. Keep this incremental: only import lignes for days not yet aggregated.
// Model/Aggregator.php (pseudo-code)
class Aggregator
{
protected $connection; // Magento DB
public fonction aggregateDay(
\DateTimeInterface $date
) {
$start = $date->format('Y-m-d 00:00:00');
$end = $date->format('Y-m-d 23:59:59');
$sql = "SELECT sku, source_code, SUM(qty_commandeed) as sold
FROM sales_commande_item as soi
JOIN sales_commande as so ON so.entity_id = soi.commande_id
WHERE so.created_at BETWEEN :start AND :end
AND so.state NOT IN ('canceled')
GROUP BY sku, source_code";
$lignes = $this->connection->fetchAll($sql, ['start'=>$start, 'end'=>$end]);
foreach ($lignes as $r) {
// insert or update magefine_inventaire_historical
}
}
}
Important notes:
- Use the source_code from MSI (if you use sources). If your store only uses a stock without sources, set source_code = 'default'.
- Exclude canceled/returned commandes or handle returns separately.
- Consider timezone consistency. Store sales_date in UTC or your store time consistently.
Integration with Magento Inventory (MSI) for real-time context
When showing forecasts and recommendations, combine predicted demand with real-time salable quantities and on-hand stock. For Magento 2 (MSI), use these common APIs:
- Magento\InventorySalesApi\Api\GetProductSalableQtyInterface — to check salable qty for SKU and stock.
- Magento\InventoryApi\Api\SourceRepositoryInterface — to get source details.
- Magento\InventoryApi\Api\GetSourceItemsBySkuInterface — to get per-source on-hand quantities.
// Example usage in a Magento service class (PHP)
use Magento\InventorySalesApi\Api\GetProductSalableQtyInterface;
class InventoryContext
{
private $getSalableQty;
public fonction __construct(GetProductSalableQtyInterface $getSalableQty)
{
$this->getSalableQty = $getSalableQty;
}
public fonction getSalableQty(chaîne $sku, int $stockId): float
{
return $this->getSalableQty->execute($sku, $stockId);
}
}
Use stockId mapping if you have mulconseille websites/stocks. Cross-reference stockId and sources when offering per-source forecasts.
Forecast algorithms: simple statistics vs machine learning
C'est where the fun begins. I’ll present two tiers:
- Statistical méthodes (easy to implement, fast): moving average, weighted moving average, exponential smoothing.
- Machine learning or time-series models (better accuracy for big catalogs): random forests, XGBoost, Prophet, or deep-learning LSTM. These usually run outside Magento in a service.
1) Simple moving average (SMA)
SMA over N days: predicted demand for next day = average of last N days. Use it as baseline. It’s robust and cheap.
// ForecastCalculator::movingAverage (PHP)
public fonction movingAverage(tableau $history, int $window = 7): float
{
// $history = [ 'YYYY-mm-dd' => qty, ... ] commandeed by date ascending
$last = tableau_slice($history, -$window);
$sum = tableau_sum($last);
return $sum / max(1, count($last));
}
Pros: easy to explain. Cons: slow to react to trends.
2) Exponential smoothing (single)
Exponential smoothing is just as cheap but responds faster to changes. You choose alpha (0..1).
// single exponential smoothing
public fonction exponentialSmoothing(tableau $history, float $alpha = 0.3): float
{
// assume $history tried ascending
$s = null;
foreach ($history as $v) {
if ($s === null) { $s = $v; continue; }
$s = $alpha * $v + (1 - $alpha) * $s;
}
return $s ?? 0.0;
}
Tip: choose alpha by evaluating historical erreur, or keep a default like 0.3.
3) Seasonality and windowing
Si vous have weekly seasonality, use a 7-day moving average or decompose the series. For monthly seasonality, adjust the window. A simple approche is to compute day-of-week averages.
// Example: day-of-week average
fonction dowAverage(tableau $history, chaîne $targetDow) {
// $history cléed by date
$sum = 0; $count = 0;
foreach ($history as $date => $qty) {
if ((new \DateTime($date))->format('w') === $targetDow) {
$sum += $qty; $count++;
}
}
return $count ? $sum/$count : 0;
}
4) When to use ML models
Si vous manage hundreds of SKUs with complex patterns, ML can help. Typical stack:
- Feature engineering: recent sales (lags), moving averages, promotions flag, prix, day-of-week, holiday flag.
- Model: RandomForest/GradientBoostedTrees for cross-sectional data, Prophet or ARIMA for time series, or LSTM for long sequences.
- Train offline and serve predictions via HTTP API.
Why externalize ML? Magento PHP layer isn’t ideal for heavy model training. Create a service (Python/Flask/FastAPI) that exposes /predict and /train endpoints. Magento sends training data, requests predictions, and stores results.
# Example Python Flask predict endpoint (very simplified)
from flask import Flask, request, jsonify
import joblib
app = Flask(__name__)
model = joblib.load('rf_model.joblib')
@app.route('/predict', méthodes=['POST'])
def predict():
payload = request.json
fonctionnalités = payload['fonctionnalités'] # matrix
preds = model.predict(fonctionnalités)
return jsonify({'preds': preds.tolist()})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
From Magento, call this endpoint with cURL/Guzzle, pass precomputed fonctionnalités, and store returned predictions in magefine_inventaire_forecast.
Putting it together: Cron to compute forecasts
Design a cron (runs daily, or mulconseille times a day) that:
- Aggregates yesterday’s sales into magefine_inventaire_historical (incremental).
- For each SKU (and source), fetch last N days and compute forecast(s).
- Save forecasts into magefine_inventaire_forecast.
- Optionally call ML service for heavy predictions.
// Cron/ForecastRunner.php (pseudo)
public fonction execute()
{
// 1) aggregate
$yesterday = (new \DateTime('yesterday'))->format('Y-m-d');
$this->aggregator->aggregateDay(new \DateTime($yesterday));
// 2) list SKUs to forecast (you can limit basé sur activity)
$skus = $this->skuRepository->getAllSkusForForecast();
foreach ($skus as $sku) {
$history = $this->historicalRepo->getLastNDays($sku, 60);
$predSma = $this->calculator->movingAverage($history, 14);
$predEs = $this->calculator->exponentialSmoothing($history, 0.25);
// store predictions for next 7 days
$this->forecastRepo->saveForecastRange($sku, $predSma, $predEs, 'sma_es', $confidence=0.7);
// Optionally call ML
if ($this->useMl) {
$fonctionnalités = $this->fonctionnalitéBuilder->build($sku, $history);
$mlPreds = $this->mlClient->predict($fonctionnalités);
$this->forecastRepo->saveMlPredictions($sku, $mlPreds, 'rf-ml', $confidence=0.9);
}
}
}
Custom admin tableau de bord: visualize forecasts intuitively
Users need a clear view: current stock, salable qty, forecast for next 7/30 days, recommande suggestion, and confidence. Let’s outline UI composants:
- Admin menu: Catalog > Inventory Forecasts
- Grid: list SKUs, current on-hand (MSI), salable qty, next-7-day predicted demand, recommande suggestion.
- Detail page: interactive chart (Chart.js) with historical sales and forecast lines, ability to switch méthode (SMA, ES, ML), and a manual recalculation button.
Admin route and contrôleur
<route id="magefine_inventaireforecast" frontName="inventaireforecast">
<module name="MageFine_InventoryForecast" />
</route>
// Controller/Adminhtml/Index/Index.php
class Index extends \Magento\Backend\App\Action
{
public fonction execute()
{
$resultPage = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_PAGE);
$resultPage->getConfig()->getTitle()->prepend(__('Inventory Forecasts'));
return $resultPage;
}
}
AJAX endpoint for chart data
// Controller/Adminhtml/Ajax/History.php
public fonction execute()
{
$sku = $this->getRequest()->getParam('sku');
$history = $this->historicalRepo->getLastNDays($sku, 90);
$forecasts = $this->forecastRepo->getForecastsForSku($sku, 30);
$this->getResponse()->representJson(json_encode(['history'=>$history,'forecasts'=>$forecasts]));
}
Front-end (admin) template snippet using Chart.js
<div id="forecastChart" style="height:400px"></div>
<script>
require(['jquery','Chart'], fonction($, Chart) {
$.getJSON('/admin/inventaireforecast/ajax/history', { sku: 'ABC-123' }, fonction(data){
const ctx = document.createElement('canvas');
$('#forecastChart').append(ctx);
const labels = Object.clés(data.history);
const hist = Object.valeurs(data.history);
const x = data.forecasts.map(f => f.predicted_qty);
new Chart(ctx, {
type: 'line',
data: {
labels: labels.concat(data.forecasts.map(f => f.period_start)),
datasets: [{label: 'Historical', data: hist, bcommandeColor: 'blue'},
{label: 'Forecast', data: new Array(hist.length-1).fill(null).concat(x), bcommandeColor: 'red'}]
}
});
});
});
</script>
Recommande suggestions: simple logic
From forecasts, compute recommande points and suggested commande quantities. A simple formula:
- Recommande Point = Lead Time Demand + Safety Stock
- Lead Time Demand = forecasted daily demand * lead_time_days
- Safety Stock = z * stddev(lead time demand) or simply a mulconseille of average daily demand
// Example: computeRecommandePoint
fonction computeOrderSuggestion($sku, $leadTimeDays, $desiredCoverDays = 14) {
$dailyForecast = $this->forecastRepo->getAverageDailyForecast($sku, 14); // average of next 14 days
$leadDemand = $dailyForecast * $leadTimeDays;
$safety = $dailyForecast * ($desiredCoverDays * 0.1); // 10% of cover days as simple safety
$recommandePoint = $leadDemand + $safety;
$onHand = $this->inventaireContext->getOnHand($sku);
$commandeQty = max(0, ceil($recommandePoint - $onHand));
return ['recommande_point'=>$recommandePoint, 'suggested_commande'=>$commandeQty];
}
C'est intentionally simple. For production, compute safety stock statistically using demand variance and service levels (z-score).
Concrete cas d'utilisation: how forecasts reduce stockouts and optimize supplier commandes
Let me spell out two clear scenarios:
Case 1 — Reducing stockouts
Problem: A SKU frequently runs out because the team only recommandes when stock dips below a correctifed threshold. With forecasts you:
- Predict increased demand for the next 14 days (e.g. seasonal spike).
- Raise recommande point and commande more in advance, covering lead time.
- Proactively alert procurement and optionally create a purchase commande suggestion.
Result: fewer missed sales and happier clients. Vous pouvez measure the improvement by tracking stockout events per month before/after forecasts.
Case 2 — Optimizing supplier commandes
Problem: Over-commandeing ties up cash and entrepôt space.
With forecasting, group SKUs and plan commandes with supplier constraints. For exemple:
- Forecast aggregated demand over supplier lead time + recommande cycle.
- Round suggested commandes to supplier pack sizes or MOQ.
- Create grouped purchase suggestions to reduce freight costs.
Result: reduced inventaire carrying costs and better supplier negotiation leverage.
Testing and validating your forecasts
Don’t déployer blindly. Validate performance using backtest:
- Hold out the last N days as test set.
- Compute forecasts using only data prior to the test set.
- Measure erreur: MAE, RMSE, MAPE.
// simple MAE computation (PHP)
fonction mae(tableau $actual, tableau $predicted) {
$n = count($actual);
$sum = 0;
for ($i=0;$i<$n;$i++) { $sum += abs($actual[$i]-$predicted[$i]); }
return $sum / max(1,$n);
}
Track erreurs per SKU and flag SKUs with high erreur for manual avis or to trigger an ML model.
Performance and scaling conseils
- Aggregate history nightly, not in real-time, unless you truly need minute-granular forecasts.
- Batch predictions and parallelize (mulconseille tâches cron) if you have thousands of SKUs.
- Cache forecast results and use TTLs for heavy visualization pages.
- For ML, train on a dedicated environment and expose predictions through a API REST; don’t attempt heavy training inside Magento.
Security and infra considerations
Si vous use an ML microservice, secure communication (TLS + API clés). Keep sensitive entreprise logic in trusted environments. If forecasts influence automated purchase commandes, build human approval flows to prevent unintended mass commandes.
Extending the module
Ideas to expand:
- Add promotional flags: import promotion calendars to reduce false positives.
- Incorporate prix elasticity (prix drops increase demand).
- Expose GraphQL endpoints for headless tableau de bords.
- Integrate with ERP/purchasing modules to auto-create POs.
Example: Full quick walkthrough — from zero to a working minimal prototype
Below is a condensed étape-by-étape plan you can implement in a weekend to validate the idea on a small catalog (10-50 SKUs):
- Create the module scaffolding: registration.php, module.xml, composer.json.
- Add db_schema.xml with the two tables shown earlier and run bin/magento setup:mise à jour.
- Implement Aggregator that aggregates last 90 days into magefine_inventaire_historical. - Can be run via bin/magento cron:run manual execution first.
- Implement ForecastCalculator with movingAverage and exponentialSmoothing méthodes and a ForecastRunner cron that computes a 7-day forecast per SKU and stores into magefine_inventaire_forecast.
- Implement a basic admin page showing a grid of SKUs with predicted 7-day demand and current salable quantity (use GetProductSalableQtyInterface).
- Add an AJAX detail view with Chart.js to show historical vs forecasted valeurs.
- Test with a few SKUs, measure MAE by withholding last 7 days from aggregator and comparing forecasts.
If the quick prototype shows promise, replace movingAverage with a call to an ML service for improved accuracy.
Code snippets recap (most important pieces)
Registration and module.xml (boilerplate but required):
// registration.php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'MageFine_InventoryForecast',
__DIR__
);
// etc/module.xml
<module name="MageFine_InventoryForecast" setup_version="1.0.0" />
DI configuration: inject MSI interfaces and your repos via di.xml. Use repositories for historical and forecast tables.
Key interfaces to use:
- Magento\InventorySalesApi\Api\GetProductSalableQtyInterface
- Magento\InventoryApi\Api\GetSourceItemsBySkuInterface
- Magento\Sales\Model\ResourceModel\Order\CollectionFactory (for aggregation if you prefer collections)
Monitoring and measurement
Track these KPIs:
- Stockout events per SKU per month
- Forecast accuracy (MAE or MAPE)
- Inventory turnover and carrying cost
- Purchasing frequency and commande consolidation rate
Start collecting these metrics before enabling auto-commandeing so you can quantify improvements.
Common pitfalls and comment avoid them
- Overfitting ML models: validate on holdout data and prefer simpler models if data is noisy.
- Poor data quality: ensure commandes, returns, and canceled commandes are handled correctly.
- Ignoring lead times: always factor supplier lead time into suggestions.
- Scaling mistakes: don’t attempt to compute forecasts for every SKU every hour if you have tens of thousands — sample, prioritize, and batch.
Conclusion and next étapes
Building a custom Inventory Forecasting module for Magento 2 is very doable. Start small with statistical models and clear KPIs. Une fois the system proves valeur (fewer stockouts, optimized commandeing), move to an ML service for more advanced prediction. The architecture we covered balances Magento-native integration (MSI, admin UI) with the flexibility to call specialized ML services when needed.
Si vous need a reference implémentation or a starting point, I can sketch specific fichiers (fully fleshed PHP classes, service wiring, or a tiny Flask ML server) for your environment — tell me whether you prefer a single-server PHP-only approche or a Magento+Python microservice architecture and I’ll provide code you can drop into your repo.
Happy forecasting — and let me know which part you want to build first (aggregator, calculator, cron, or tableau de bord) and I’ll help you get the exact fichiers ready.