Stockouts Cost More Than Overstocking — But Only One Gets Noticed
When a warehouse runs out of a best-selling product, the damage is immediate: lost sales, cancelled orders, broken promises to customers, and expedited shipping costs that eat into margins. Yet most Odoo implementations treat inventory replenishment as an afterthought — a manual "Buy" click when someone notices the shelf is empty.
Odoo 19's reorder rules (also called replenishment rules or orderpoints) automate this entirely. They monitor stock levels in real time, compute safety buffers based on lead times and demand variability, and generate purchase orders or manufacturing orders before you hit zero. The scheduler runs automatically, and when configured correctly, your warehouse team never has to guess "should we order more?"
This guide walks through the complete setup: from basic min/max reorder rules, through safety stock calculations, vendor lead time configuration, the MTO vs MTS decision, and demand forecasting. Every section includes the Python models and XML configuration you need to customize the defaults for your business.
Configuring Min/Max Reorder Rules: The Foundation of Automated Replenishment
A reorder rule (technical model: stock.warehouse.orderpoint) tells Odoo: "When the forecast quantity of product X at warehouse Y drops below the minimum, create a replenishment to bring it back up to the maximum." The scheduler checks every rule at a configurable interval (default: once per day via the stock.procurement cron job).
Step 1 — Enable Replenishment in Inventory Settings
Navigate to Inventory → Configuration → Settings. Under the Operations section, ensure Replenishment is enabled. This activates the reorder rules menu and the scheduler cron. Without this setting, the stock.warehouse.orderpoint model exists but the cron that processes rules is disabled.
Step 2 — Create a Reorder Rule via the UI
Go to Inventory → Operations → Replenishment. Click Create and fill in:
| Field | Value (Example) | What It Means |
|---|---|---|
| Product | [WIDGET-A] Premium Widget | The storable product to monitor |
| Location | WH/Stock | The specific stock location (warehouse-level or bin-level) |
| Route | Buy | Buy from vendor, Manufacture, or a custom route |
| Min Quantity | 50 | When forecast drops below 50, trigger replenishment |
| Max Quantity | 200 | Order enough to bring forecast up to 200 |
| Multiple Quantity | 25 | Round up order qty to nearest multiple of 25 (case packs) |
Step 3 — Create Reorder Rules Programmatically
For bulk setup across hundreds of products, use a data file or server action. Here is the XML to create a reorder rule in a module's data file:
<odoo>
<data noupdate="1">
<!-- Reorder rule: Premium Widget at main warehouse -->
<record id="orderpoint_widget_a" model="stock.warehouse.orderpoint">
<field name="product_id" ref="product.product_widget_a"/>
<field name="warehouse_id" ref="stock.warehouse0"/>
<field name="location_id" ref="stock.stock_location_stock"/>
<field name="product_min_qty">50</field>
<field name="product_max_qty">200</field>
<field name="qty_multiple">25</field>
<field name="route_id" ref="purchase_stock.route_warehouse0_buy"/>
</record>
<!-- Reorder rule: Deluxe Widget — higher buffer -->
<record id="orderpoint_widget_b" model="stock.warehouse.orderpoint">
<field name="product_id" ref="product.product_widget_b"/>
<field name="warehouse_id" ref="stock.warehouse0"/>
<field name="location_id" ref="stock.stock_location_stock"/>
<field name="product_min_qty">100</field>
<field name="product_max_qty">500</field>
<field name="qty_multiple">50</field>
<field name="route_id" ref="purchase_stock.route_warehouse0_buy"/>
</record>
</data>
</odoo>Step 4 — Bulk-Create Reorder Rules via Python
When migrating from another system, you often need to create hundreds of reorder rules from a spreadsheet. Here is a server action script that creates rules for all storable products in a category:
# Server action: Create reorder rules for all products in category
warehouse = env['stock.warehouse'].search([('company_id', '=', env.company.id)], limit=1)
buy_route = env.ref('purchase_stock.route_warehouse0_buy')
products = env['product.product'].search([
('categ_id.name', '=', 'Best Sellers'),
('type', '=', 'product'), # storable only
])
vals_list = []
for product in products:
# Skip if rule already exists
existing = env['stock.warehouse.orderpoint'].search([
('product_id', '=', product.id),
('warehouse_id', '=', warehouse.id),
], limit=1)
if existing:
continue
vals_list.append({{
'product_id': product.id,
'warehouse_id': warehouse.id,
'location_id': warehouse.lot_stock_id.id,
'product_min_qty': 50.0,
'product_max_qty': 200.0,
'qty_multiple': 1.0,
'route_id': buy_route.id,
}})
created = env['stock.warehouse.orderpoint'].create(vals_list)
log(f"Created {{len(created)}} reorder rules for Best Sellers category")Odoo's reorder rules check the forecast quantity, not on-hand. Forecast = on-hand + incoming - outgoing. This means a rule will NOT trigger if you have 40 on-hand but 100 incoming from a PO that's already confirmed. This prevents duplicate orders — but also means your reorder rules and purchase orders must be kept in sync.
Safety Stock Configuration: The Buffer Between "Just in Time" and "Just Too Late"
Safety stock is the extra inventory you hold above your expected demand to absorb variability — late vendor shipments, demand spikes, quality rejections. In Odoo 19, safety stock is not a separate field; it is built into the reorder rule's minimum quantity. Your min qty should equal your safety stock level.
The Safety Stock Formula
The classic formula for safety stock is:
Safety Stock = Z x σLT x Davg
Where Z is the service level factor (1.65 for 95%, 2.33 for 99%), σLT is the standard deviation of lead time in days, and Davg is the average daily demand. For products with variable demand, the extended formula accounts for both lead time and demand variability.
import math
from odoo import models, fields, api
class StockWarehouseOrderpoint(models.Model):
_inherit = 'stock.warehouse.orderpoint'
safety_stock_method = fields.Selection([
('manual', 'Manual'),
('computed', 'Auto-Compute from Demand History'),
], default='manual', string="Safety Stock Method")
service_level = fields.Float(
string="Service Level (%)",
default=95.0,
help="Target probability of not stocking out. "
"95% = Z of 1.65, 99% = Z of 2.33",
)
computed_safety_stock = fields.Float(
string="Computed Safety Stock",
compute='_compute_safety_stock',
store=True,
)
# Z-score lookup for common service levels
Z_SCORES = {{
90.0: 1.28,
95.0: 1.65,
97.5: 1.96,
99.0: 2.33,
99.5: 2.58,
}}
@api.depends('product_id', 'service_level', 'warehouse_id')
def _compute_safety_stock(self):
for rule in self:
if rule.safety_stock_method != 'computed' or not rule.product_id:
rule.computed_safety_stock = 0.0
continue
# Fetch 90 days of outgoing moves for demand stats
moves = self.env['stock.move'].search([
('product_id', '=', rule.product_id.id),
('location_id', '=', rule.location_id.id),
('state', '=', 'done'),
('date', '>=', fields.Datetime.subtract(
fields.Datetime.now(), days=90)),
])
if not moves:
rule.computed_safety_stock = 0.0
continue
# Daily demand calculation
daily_demand = {{}}
for move in moves:
day_key = move.date.date()
daily_demand.setdefault(day_key, 0.0)
daily_demand[day_key] += move.product_uom_qty
demands = list(daily_demand.values())
avg_demand = sum(demands) / len(demands) if demands else 0
std_demand = (
math.sqrt(sum((d - avg_demand) ** 2 for d in demands)
/ len(demands))
if len(demands) > 1 else 0
)
# Lead time from vendor
seller = rule.product_id.seller_ids[:1]
lead_days = seller.delay if seller else 7.0
# Z-score from service level
z = rule.Z_SCORES.get(rule.service_level, 1.65)
# Safety stock = Z * sqrt(LT * var_demand + D_avg^2 * var_LT)
# Simplified: assume LT is fixed, only demand varies
safety = z * std_demand * math.sqrt(lead_days)
rule.computed_safety_stock = round(safety, 0)
def action_apply_computed_safety(self):
"""Apply computed safety stock to the min quantity field."""
for rule in self.filtered(
lambda r: r.safety_stock_method == 'computed'
):
rule.product_min_qty = rule.computed_safety_stockA minimum of zero means "only replenish when forecast goes negative." By then, you already have backorders. Your min quantity IS your safety stock. Set it to the number of units you need to survive a worst-case lead time delay. For your top 20% SKUs (which typically generate 80% of revenue), set the service level to 99% — the carrying cost of extra stock is far less than the lost-sale cost.
Lead Time Configuration: Vendor Delays, Security Margins, and the Scheduler
Odoo 19 computes the total replenishment lead time by adding up four components. If any one is wrong, your reorder rules will trigger too late (stockout) or too early (overstocking):
| Component | Where to Configure | Default | What It Represents |
|---|---|---|---|
| Vendor Lead Time | Product → Purchase tab → Vendor pricelist → Delivery Lead Time | 0 days | Days from PO confirmation to goods arriving at your door |
| Purchase Security Lead Time | Inventory → Settings → Purchase Security Lead Time | 0 days | Global buffer added to ALL purchase lead times (vendor-agnostic safety margin) |
| Days to Purchase | Inventory → Settings → Days to Purchase | 0 days | Internal processing time to validate and send the PO to the vendor |
| Manufacturing Lead Time | Product → Inventory tab → Manufacturing Lead Time | 0 days | Days to manufacture (for Make routes only) |
The total lead time for a "Buy" route is: Vendor Lead Time + Purchase Security Lead Time + Days to Purchase. The scheduler uses this to determine when to create the PO — it works backward from the date you need the stock.
<odoo>
<data noupdate="1">
<!-- Vendor pricelist with lead time -->
<record id="supplierinfo_widget_a_acme" model="product.supplierinfo">
<field name="product_tmpl_id" ref="product.product_template_widget_a"/>
<field name="partner_id" ref="base.res_partner_acme_corp"/>
<field name="min_qty">100</field>
<field name="price">12.50</field>
<field name="delay">14</field> <!-- 14 calendar days -->
</record>
<!-- Second vendor as backup (longer lead time, lower MOQ) -->
<record id="supplierinfo_widget_a_globex" model="product.supplierinfo">
<field name="product_tmpl_id" ref="product.product_template_widget_a"/>
<field name="partner_id" ref="base.res_partner_globex"/>
<field name="min_qty">25</field>
<field name="price">14.00</field>
<field name="delay">21</field> <!-- 21 calendar days -->
<field name="sequence">2</field>
</record>
</data>
</odoo>Overriding Lead Time Per Orderpoint
Sometimes you need a specific reorder rule to use a different lead time than the vendor default — for example, a rule that triggers air freight (3 days) instead of ocean freight (45 days). You can extend the orderpoint model to override the lead time calculation:
from odoo import models, fields
class StockWarehouseOrderpoint(models.Model):
_inherit = 'stock.warehouse.orderpoint'
override_lead_days = fields.Float(
string="Override Lead Time (days)",
help="If set, overrides the vendor lead time for this rule only. "
"Use for air freight vs ocean freight scenarios.",
)
def _get_lead_days_values(self):
"""Override to inject custom lead time per orderpoint."""
values = super()._get_lead_days_values()
if self.override_lead_days:
values['supplierinfo'] = {{
'delay': self.override_lead_days
}}
return valuesBy default, Odoo uses calendar days for lead times. If your vendor ships only on weekdays, a 5-day lead time set on Friday means Odoo expects delivery on Wednesday — but the vendor won't ship until Monday, so actual arrival is the following Friday. To use business days, configure a Resource Calendar on the company and link it to the vendor. Odoo 19's scheduler respects the calendar when computing expected receipt dates.
Make-to-Order vs Make-to-Stock: Choosing the Right Replenishment Strategy
This decision determines whether you hold finished goods inventory (MTS) or only procure/manufacture when a customer order arrives (MTO). Most businesses use a hybrid — and Odoo 19 supports this at the product level.
| Strategy | When to Use | Odoo Route | Reorder Rules Needed? |
|---|---|---|---|
| Make-to-Stock (MTS) | High-volume, predictable demand, short customer lead time expected | Default (no MTO route) | Yes — this is how stock gets replenished |
| Make-to-Order (MTO) | Custom/configured products, expensive items, unpredictable demand | Product → Inventory tab → "Replenish on Order (MTO)" | No — each SO triggers its own procurement |
| Hybrid (MTS + Safety) | Standard products with seasonal spikes | Default + reorder rule with high safety stock during peak | Yes — with seasonal min/max adjustments |
Configuring MTO in Odoo 19
In Odoo 19, the MTO route has been refactored. The old stock.route MTO route still exists but now works through the Replenish on Order checkbox on the product form. When enabled, confirming a sales order immediately creates a linked purchase order (Buy route) or manufacturing order (Manufacture route) — no reorder rule needed.
# Identify products that should be MTO but aren't configured
products = env['product.product'].search([
('type', '=', 'product'),
('categ_id.name', 'in', ['Custom Orders', 'Made to Spec']),
])
mto_route = env.ref('stock.route_warehouse0_mto', raise_if_not_found=False)
if not mto_route:
# Odoo 19: MTO route may be archived by default
mto_route = env['stock.route'].search([
('name', 'ilike', 'Replenish on Order'),
], limit=1)
misconfigured = products.filtered(
lambda p: mto_route not in p.route_ids
)
for product in misconfigured:
log(f"WARNING: {{product.display_name}} is in a custom category "
f"but missing MTO route")
product.route_ids = [(4, mto_route.id)]For products that are mostly MTO but have a handful of high-frequency orders, configure both the MTO route AND a reorder rule with a small min quantity. The reorder rule maintains a small buffer of pre-built stock. When a sales order arrives, it consumes from the buffer first (instant delivery). The reorder rule replenishes the buffer. The MTO route only kicks in when demand exceeds the buffer — giving you fast delivery for predictable demand and on-demand production for spikes.
Demand Forecasting in Odoo 19: Using Historical Data to Set Smarter Reorder Points
Static min/max values work for stable demand. But if your best-seller does 50 units/week in January and 200 units/week in December, a fixed min of 100 means you overstock for 10 months and stockout during the 2 months that matter most. Odoo 19's forecasting tools help you adjust dynamically.
Method 1 — Odoo's Built-In Demand Forecast (MRP module)
If you have the Manufacturing (MRP) module installed, navigate to Manufacturing → Planning → Master Production Schedule. The MPS lets you set monthly demand forecasts per product and generates replenishment suggestions. It integrates directly with reorder rules — the MPS adjusts the "demand" side of the forecast, which changes when reorder rules trigger.
Method 2 — Custom Seasonal Adjustment Cron
For businesses without the MRP module, you can create a scheduled action that adjusts reorder rule min/max values monthly based on historical demand:
import logging
from datetime import timedelta
from odoo import models, fields, api
_logger = logging.getLogger(__name__)
class StockWarehouseOrderpoint(models.Model):
_inherit = 'stock.warehouse.orderpoint'
seasonal_adjust = fields.Boolean(
string="Seasonal Auto-Adjust",
default=False,
help="If enabled, min/max are adjusted monthly "
"based on same-month demand from prior year.",
)
base_min_qty = fields.Float(
string="Base Min Qty",
help="The baseline minimum before seasonal adjustment.",
)
base_max_qty = fields.Float(
string="Base Max Qty",
help="The baseline maximum before seasonal adjustment.",
)
@api.model
def _cron_seasonal_adjustment(self):
"""Monthly cron: adjust min/max based on YoY demand."""
rules = self.search([('seasonal_adjust', '=', True)])
today = fields.Date.today()
current_month = today.month
for rule in rules:
if not rule.base_min_qty:
continue
# Get same-month demand from last year
last_year_start = today.replace(
year=today.year - 1, day=1)
if current_month == 12:
last_year_end = today.replace(
year=today.year, month=1, day=1)
else:
last_year_end = today.replace(
year=today.year - 1, month=current_month + 1, day=1)
moves = self.env['stock.move'].search([
('product_id', '=', rule.product_id.id),
('location_id', '=', rule.location_id.id),
('state', '=', 'done'),
('date', '>=', last_year_start),
('date', '<', last_year_end),
])
month_demand = sum(moves.mapped('product_uom_qty'))
# Get average monthly demand across the year
year_start = today.replace(
year=today.year - 1, month=1, day=1)
year_end = today.replace(year=today.year, month=1, day=1)
all_moves = self.env['stock.move'].search([
('product_id', '=', rule.product_id.id),
('location_id', '=', rule.location_id.id),
('state', '=', 'done'),
('date', '>=', year_start),
('date', '<', year_end),
])
yearly_demand = sum(all_moves.mapped('product_uom_qty'))
avg_monthly = yearly_demand / 12 if yearly_demand else 1
# Seasonal factor: this month's demand / average
factor = month_demand / avg_monthly if avg_monthly else 1.0
factor = max(0.5, min(factor, 3.0)) # Cap between 0.5x-3x
new_min = round(rule.base_min_qty * factor)
new_max = round(rule.base_max_qty * factor)
if new_min != rule.product_min_qty:
_logger.info(
"Seasonal adjust %s: min %s->%s, max %s->%s "
"(factor: %.2f)",
rule.product_id.display_name,
rule.product_min_qty, new_min,
rule.product_max_qty, new_max,
factor,
)
rule.write({{
'product_min_qty': new_min,
'product_max_qty': new_max,
}})<odoo>
<data noupdate="1">
<record id="cron_seasonal_orderpoint_adjust" model="ir.cron">
<field name="name">Seasonal Reorder Rule Adjustment</field>
<field name="model_id" ref="stock.model_stock_warehouse_orderpoint"/>
<field name="state">code</field>
<field name="code">model._cron_seasonal_adjustment()</field>
<field name="interval_number">1</field>
<field name="interval_type">months</field>
<field name="numbercall">-1</field>
<field name="active">True</field>
</record>
</data>
</odoo>Advanced Configuration: Multi-Warehouse, Preferred Vendors, and Qty Multiples
Multi-Warehouse Reorder Rules
Each reorder rule is bound to a specific warehouse + location combination. If you have three warehouses, you need three separate reorder rules for each product — each with min/max tuned to that warehouse's demand pattern. A central distribution warehouse might have a min of 500, while a regional satellite has a min of 50.
# Create reorder rules across all warehouses with proportional min/max
WAREHOUSE_RATIOS = {{
'Main Warehouse': 1.0, # Base ratio
'East Coast DC': 0.4, # 40% of main
'West Coast DC': 0.35, # 35% of main
'Returns Center': 0.0, # No replenishment
}}
product = env.ref('product.product_widget_a')
buy_route = env.ref('purchase_stock.route_warehouse0_buy')
base_min, base_max = 200, 800
for warehouse in env['stock.warehouse'].search([]):
ratio = WAREHOUSE_RATIOS.get(warehouse.name, 0)
if ratio <= 0:
continue
env['stock.warehouse.orderpoint'].create({{
'product_id': product.id,
'warehouse_id': warehouse.id,
'location_id': warehouse.lot_stock_id.id,
'product_min_qty': base_min * ratio,
'product_max_qty': base_max * ratio,
'qty_multiple': 25,
'route_id': buy_route.id,
}})Preferred Vendor Selection Logic
When a reorder rule triggers, Odoo selects the vendor based on the sequence field in product.supplierinfo and the minimum quantity. The first vendor in sequence whose min_qty is less than or equal to the order quantity wins. To override this — for example, to always use the fastest vendor when safety stock is breached — extend the vendor selection:
from odoo import models, fields
class StockWarehouseOrderpoint(models.Model):
_inherit = 'stock.warehouse.orderpoint'
preferred_vendor_id = fields.Many2one(
'res.partner',
string="Preferred Vendor",
help="Override automatic vendor selection for this rule. "
"Useful for emergency/air freight suppliers.",
)
def _get_orderpoint_procurement_values(self):
"""Inject preferred vendor into procurement context."""
values = super()._get_orderpoint_procurement_values()
if self.preferred_vendor_id:
# Filter supplierinfo to preferred vendor
supplier = self.product_id.seller_ids.filtered(
lambda s: s.partner_id == self.preferred_vendor_id
)[:1]
if supplier:
values['supplierinfo'] = supplier
return valuesQuantity Multiples and Rounding
The Multiple Quantity field on the reorder rule forces the replenishment quantity to round up to the nearest multiple. This is critical for products sold by the case, pallet, or container:
| Scenario | Qty Needed | Multiple | Qty Ordered |
|---|---|---|---|
| Need 137 widgets, cases of 24 | 137 | 24 | 144 (6 cases) |
| Need 80 bags, pallets of 50 | 80 | 50 | 100 (2 pallets) |
| Need 3 custom parts, MOQ 10 | 3 | 10 | 10 (vendor MOQ) |
The qty_multiple on the reorder rule and the min_qty on product.supplierinfo are different things. The vendor MOQ is the absolute minimum the vendor will accept. The qty multiple is the rounding increment. If your vendor MOQ is 100 and qty multiple is 25, an order for 137 becomes 150 (rounded up to 25, and above MOQ of 100). But an order for 60 becomes 100 (MOQ floor takes precedence).
5 Reorder Rule Mistakes That Cause Stockouts Despite Having Rules Configured
The Scheduler Cron Is Disabled or Running Too Infrequently
Reorder rules only work when the scheduler runs. Navigate to Settings → Technical → Scheduled Actions and search for "Orderpoint". The cron stock.ir_cron_scheduler_action should be active and running at least once per day. We've seen implementations where someone disabled it during testing and forgot to re-enable. For businesses with same-day shipping SLAs, run the scheduler every 2-4 hours.
Add a monitoring alert that checks ir.cron last execution time. If the scheduler hasn't run in 48 hours, trigger a notification to the operations team.
Product Type Is "Consumable" Instead of "Storable"
Reorder rules only work on storable products (type = 'product'). If someone changes a product to "Consumable" (type = 'consu'), Odoo stops tracking stock levels and reorder rules silently stop triggering. No error, no warning — the rule still exists in the database, it just never fires.
Add a SQL constraint or Python check that prevents changing a product to consumable if it has active reorder rules.
No Vendor Defined on the Product (Buy Route Fails Silently)
When a reorder rule with the "Buy" route triggers, Odoo tries to find a vendor on the product's Purchase tab. If no vendor is defined, the procurement silently fails — it creates an exception that's only visible in the Replenishment view with a red warning icon. No purchase order is created. No notification is sent by default. Your stock quietly drops to zero.
Create a scheduled action that checks for products with active reorder rules (Buy route) but no vendor defined. Send a daily digest to the purchasing manager listing these products.
Negative On-Hand Masks the Real Demand
If your warehouse allows negative stock (which is the default in Odoo), and on-hand drops to -50 with a min of 100 and max of 400, the reorder rule will order 450 units (to get from -50 to 400). This is correct mathematically but often surprises buyers who expected a "normal" order of 300. It also compounds if there are data entry errors causing false negatives.
Disable negative stock for critical product categories by enabling Inventory → Settings → No Negative Inventory. This forces users to fix receiving errors before they cascade into inflated purchase orders.
Multiple Reorder Rules for the Same Product + Location
Odoo allows multiple reorder rules for the same product at the same location — for example, one with a "Buy" route and another with a "Manufacture" route. When both trigger, you get double replenishment: a purchase order AND a manufacturing order for the same demand. The stock level overshoots the max, and you end up with dead inventory and a confused procurement team.
Add a unique constraint on (product_id, location_id) or at minimum a Python @api.constrains that warns when a duplicate rule is created. Review existing rules with a SQL query: SELECT product_id, location_id, COUNT(*) FROM stock_warehouse_orderpoint GROUP BY product_id, location_id HAVING COUNT(*) > 1.
What Automated Replenishment Saves Your Business
Properly configured reorder rules eliminate the two most expensive inventory problems: stockouts (lost revenue) and overstocking (tied-up capital). Here is the measured impact from our client implementations:
Automated safety stock computation catches demand spikes before they empty shelves. Clients move from reactive "emergency POs" to proactive replenishment.
Seasonal adjustments prevent overbuying during slow months. Max quantities cap replenishment instead of unchecked manual purchasing.
Warehouse managers stop checking spreadsheets and eyeballing stock levels. The scheduler does the math. Humans review and approve.
A mid-size distributor with 2,000 SKUs typically has a purchasing team spending 2-3 full days per week on manual replenishment planning. That's spreadsheet maintenance, vendor calls, and "gut feel" ordering. With reorder rules, the scheduler generates draft POs automatically. The buyer's job shifts from "what do we need?" to "does this PO look right?" — a 15-minute review instead of a 3-hour planning session.
For the CFO: if your average inventory value is $2M and you reduce it by 20% through smarter min/max values, that's $400,000 freed from working capital. At a 5% cost-of-capital, that saves $20,000/year in carrying costs alone — not counting the warehouse space, insurance, and obsolescence risk.
Optimization Metadata
Complete guide to Odoo 19 safety stock and reorder rules. Configure min/max orderpoints, compute safety stock from demand history, set vendor lead times, and automate replenishment.
1. "Configuring Min/Max Reorder Rules: The Foundation of Automated Replenishment"
2. "Safety Stock Configuration: The Buffer Between Just in Time and Just Too Late"
3. "5 Reorder Rule Mistakes That Cause Stockouts Despite Having Rules Configured"