0 ) { return (float) $configured; } return $avg_daily * $safety_days; } /** * Reorder point (manual override wins). * * @param int|null $manual Manual reorder point, or null for auto. * @param float $avg_daily Average daily sales. * @param int $lead_time Supplier lead time in days. * @param float $safety_stock Safety stock. */ public function reorder_point( ?int $manual, float $avg_daily, int $lead_time, float $safety_stock ): float { if ( null !== $manual ) { return (float) $manual; } return ( $avg_daily * $lead_time ) + $safety_stock; } /** * Suggested reorder quantity, net of stock already on hand and on order, * rounded up to the supplier's minimum order quantity. * * @param float $avg_daily Average daily sales. * @param int $lead_time Lead time in days. * @param int $coverage_days Days of forward cover to buy. * @param float $safety_stock Safety stock. * @param float $current_stock Current stock. * @param float $on_open_pos Quantity already on open purchase orders. * @param int $moq Supplier minimum order quantity. */ public function suggested_qty( float $avg_daily, int $lead_time, int $coverage_days, float $safety_stock, float $current_stock, float $on_open_pos, int $moq ): int { $target = ( $avg_daily * ( $lead_time + $coverage_days ) ) + $safety_stock; $need = $target - $current_stock - $on_open_pos; if ( $need <= 0 ) { return 0; } $qty = (int) ceil( $need ); if ( $moq > 0 && $qty < $moq ) { $qty = $moq; } elseif ( $moq > 0 ) { // Round up to a whole multiple of the MOQ. $qty = (int) ( ceil( $qty / $moq ) * $moq ); } return $qty; } /** * Classify urgency. * * @param float $avg_daily Average daily sales. * @param float $current_stock Current stock. * @param float $days_remaining Days remaining. * @param int $lead_time Lead time in days. * @param int $coverage_days Coverage window in days. * @param float $reorder_point Reorder point. * @return string 'critical' | 'warning' | 'recommended' | 'ok' */ public function severity( float $avg_daily, float $current_stock, float $days_remaining, int $lead_time, int $coverage_days, float $reorder_point ): string { // No demand: nothing to reorder, regardless of stock level. if ( $avg_daily <= 0.0 ) { return 'ok'; } if ( $days_remaining < $lead_time ) { return 'critical'; } if ( $current_stock <= $reorder_point ) { return 'warning'; } if ( $days_remaining < ( $lead_time + $coverage_days ) ) { return 'recommended'; } return 'ok'; } }