Food Hub - Source Code

← Back to Project

This is the core order distribution algorithm from my 2017 WooCommerce multi-vendor marketplace:

<?php
/**
 * Food Hub - Order Distribution System (2017)
 * 
 * This recreates the core logic from my custom WooCommerce multi-vendor marketplace
 * that aggregated agricultural products from multiple producers into unified orders.
 * 
 * The key challenge: A customer orders "10 quarts of strawberries" but we need to
 * distribute this across multiple vendors based on inventory, pricing,
 * and delivery logistics.
 */

class MarketplaceOrderDistributor {
    
    private $vendors;
    private $delivery_zones;
    
    public function __construct() {
        // Sample vendor data (in production, this came from WooCommerce vendor tables)
        $this->vendors = [
            [
                'id' => 101,
                'name' => 'Sunrise Farms',
                'products' => [
                    'strawberries' => [
                        'available_qty' => 15,
                        'price_per_unit' => 4.50,
                        'quality_grade' => 'A',
                        'harvest_date' => '2017-06-15'
                    ]
                ],
                'delivery_zone' => 'north',
                'commission_rate' => 0.12  // Platform takes 12%
            ],
            [
                'id' => 102,
                'name' => 'Valley Fresh Co-op',
                'products' => [
                    'strawberries' => [
                        'available_qty' => 8,
                        'price_per_unit' => 4.25,
                        'quality_grade' => 'A',
                        'harvest_date' => '2017-06-14'
                    ]
                ],
                'delivery_zone' => 'central',
                'commission_rate' => 0.10
            ],
            [
                'id' => 103,
                'name' => 'Heritage Gardens',
                'products' => [
                    'strawberries' => [
                        'available_qty' => 12,
                        'price_per_unit' => 5.00,
                        'quality_grade' => 'Premium',
                        'harvest_date' => '2017-06-16'
                    ]
                ],
                'delivery_zone' => 'south',
                'commission_rate' => 0.15
            ]
        ];
        
        // Delivery zones and their logistics costs
        $this->delivery_zones = [
            'north' => ['base_cost' => 5.00, 'per_mile' => 0.50],
            'central' => ['base_cost' => 3.00, 'per_mile' => 0.40],
            'south' => ['base_cost' => 7.00, 'per_mile' => 0.60]
        ];
    }
    
    /**
     * Main distribution algorithm
     * Takes a customer order and distributes it across vendors
     * considering inventory, pricing, quality, and logistics
     */
    public function distributeOrder($product, $requested_qty, $customer_location, $max_vendors = 3) {
        echo "<h3>Processing Order: {$requested_qty} quarts of {$product}</h3>\n";
        echo "<p><strong>Customer Location:</strong> {$customer_location}</p>\n";
        
        // Step 1: Find available vendors for this product
        $available_vendors = $this->getAvailableVendors($product);
        
        if (empty($available_vendors)) {
            return ['error' => 'No vendors available for this product'];
        }
        
        // Step 2: Calculate total cost for each vendor (including logistics)
        $vendor_costs = $this->calculateVendorCosts($available_vendors, $customer_location);
        
        // Step 3: Sort vendors by cost-effectiveness (price + logistics + quality)
        $sorted_vendors = $this->rankVendors($vendor_costs);
        
        // Step 4: Distribute the order using our smart allocation algorithm
        $distribution = $this->allocateOrder($sorted_vendors, $requested_qty, $max_vendors);
        
        // Step 5: Generate vendor order slips
        $order_slips = $this->generateOrderSlips($distribution, $product);
        
        return [
            'distribution' => $distribution,
            'order_slips' => $order_slips,
            'total_cost' => array_sum(array_column($distribution, 'total_cost')),
            'total_commission' => array_sum(array_column($distribution, 'commission'))
        ];
    }
    
    /**
     * Find vendors who have the requested product in stock
     */
    private function getAvailableVendors($product) {
        $available = [];
        
        foreach ($this->vendors as $vendor) {
            if (isset($vendor['products'][$product]) && 
                $vendor['products'][$product]['available_qty'] > 0) {
                $available[] = $vendor;
            }
        }
        
        return $available;
    }
    
    /**
     * Calculate total cost per unit for each vendor (product + logistics)
     */
    private function calculateVendorCosts($vendors, $customer_location) {
        $costs = [];
        
        foreach ($vendors as $vendor) {
            $delivery_cost = $this->calculateDeliveryFee($vendor['delivery_zone'], $customer_location);
            $base_price = $vendor['products']['strawberries']['price_per_unit'];
            
            // Total cost per unit = base price + (delivery cost / quantity)
            // This spreads delivery cost across all units from this vendor
            $costs[] = [
                'vendor' => $vendor,
                'base_price' => $base_price,
                'delivery_cost' => $delivery_cost,
                'cost_per_unit' => $base_price + ($delivery_cost / max(1, $vendor['products']['strawberries']['available_qty'])),
                'quality_score' => $this->getQualityScore($vendor['products']['strawberries']['quality_grade'])
            ];
        }
        
        return $costs;
    }
    
    /**
     * Vendor ranking considering price, quality, and logistics
     */
    private function rankVendors($vendor_costs) {
        // Sort by a weighted score: 60% cost, 40% quality
        usort($vendor_costs, function($a, $b) {
            $score_a = ($a['cost_per_unit'] * 0.6) - ($a['quality_score'] * 0.4);
            $score_b = ($b['cost_per_unit'] * 0.6) - ($b['quality_score'] * 0.4);
            return $score_a <=> $score_b;
        });
        
        return $vendor_costs;
    }
    
    /**
     * Smart allocation algorithm
     */
    private function allocateOrder($sorted_vendors, $requested_qty, $max_vendors) {
        $distribution = [];
        $remaining_qty = $requested_qty;
        $vendors_used = 0;
        
        foreach ($sorted_vendors as $vendor_data) {
            if ($remaining_qty <= 0 || $vendors_used >= $max_vendors) {
                break;
            }
            
            $vendor = $vendor_data['vendor'];
            $available = $vendor['products']['strawberries']['available_qty'];
            
            // Take what we need or what's available, whichever is less
            $allocated_qty = min($remaining_qty, $available);
            
            if ($allocated_qty > 0) {
                $unit_price = $vendor['products']['strawberries']['price_per_unit'];
                $subtotal = $allocated_qty * $unit_price;
                $commission = $subtotal * $vendor['commission_rate'];
                $vendor_payment = $subtotal - $commission;
                
                $distribution[] = [
                    'vendor_id' => $vendor['id'],
                    'vendor_name' => $vendor['name'],
                    'quantity' => $allocated_qty,
                    'unit_price' => $unit_price,
                    'subtotal' => $subtotal,
                    'commission' => $commission,
                    'vendor_payment' => $vendor_payment,
                    'total_cost' => $subtotal + $vendor_data['delivery_cost'],
                    'delivery_cost' => $vendor_data['delivery_cost'],
                    'quality_grade' => $vendor['products']['strawberries']['quality_grade']
                ];
                
                $remaining_qty -= $allocated_qty;
                $vendors_used++;
            }
        }
        
        // Check if we could fulfill the entire order
        if ($remaining_qty > 0) {
            // In the real system, this would trigger backorder or waitlist logic
            echo "<div class='alert alert-warning'>Could only fulfill " . ($requested_qty - $remaining_qty) . " of {$requested_qty} requested units</div>\n";
        }
        
        return $distribution;
    }
    
    /**
     * Generate the actual order slips that vendors would receive
     * This was critical for the multi-vendor workflow
     */
    private function generateOrderSlips($distribution, $product) {
        $order_slips = [];
        $order_id = 'ORD-' . date('Ymd') . '-' . rand(1000, 9999);
        
        foreach ($distribution as $allocation) {
            $order_slips[] = [
                'slip_id' => $order_id . '-V' . $allocation['vendor_id'],
                'vendor_name' => $allocation['vendor_name'],
                'product' => $product,
                'quantity_to_fulfill' => $allocation['quantity'],
                'payment_amount' => $allocation['vendor_payment'],
                'delivery_instructions' => $this->generateDeliveryInstructions($allocation),
                'fulfillment_deadline' => date('Y-m-d', strtotime('+2 days')),
                'quality_requirements' => 'Grade: ' . $allocation['quality_grade']
            ];
        }
        
        return $order_slips;
    }
    
    /**
     * Helper functions for the distribution logic
     */
    private function calculateDeliveryFee($vendor_zone, $customer_location) {
        // Simplified delivery calculation
        $zone_data = $this->delivery_zones[$vendor_zone] ?? $this->delivery_zones['central'];
        $estimated_miles = rand(5, 25); // In reality, this used Google Maps API
        
        return $zone_data['base_cost'] + ($zone_data['per_mile'] * $estimated_miles);
    }
    
    private function getQualityScore($grade) {
        $scores = ['Premium' => 5, 'A' => 4, 'B' => 3, 'C' => 2];
        return $scores[$grade] ?? 2;
    }
    
    private function generateDeliveryInstructions($allocation) {
        return "Deliver to central pickup point by 10 AM on fulfillment date. " .
               "Package in marketplace-branded containers. " .
               "Include quality certification sheet.";
    }
}