'GET', 'callback' => [__CLASS__, 'get_overview'], 'permission_callback' => [Permissions::class, 'check_admin_permission'], ]); // Revenue analytics register_rest_route('woonoow/v1', '/analytics/revenue', [ 'methods' => 'GET', 'callback' => [__CLASS__, 'get_revenue'], 'permission_callback' => [Permissions::class, 'check_admin_permission'], 'args' => [ 'granularity' => [ 'required' => false, 'default' => 'day', 'validate_callback' => function($param) { return in_array($param, ['day', 'week', 'month']); }, ], ], ]); // Orders analytics register_rest_route('woonoow/v1', '/analytics/orders', [ 'methods' => 'GET', 'callback' => [__CLASS__, 'get_orders'], 'permission_callback' => [Permissions::class, 'check_admin_permission'], ]); // Products analytics register_rest_route('woonoow/v1', '/analytics/products', [ 'methods' => 'GET', 'callback' => [__CLASS__, 'get_products'], 'permission_callback' => [Permissions::class, 'check_admin_permission'], ]); // Customers analytics register_rest_route('woonoow/v1', '/analytics/customers', [ 'methods' => 'GET', 'callback' => [__CLASS__, 'get_customers'], 'permission_callback' => [Permissions::class, 'check_admin_permission'], ]); // Coupons analytics register_rest_route('woonoow/v1', '/analytics/coupons', [ 'methods' => 'GET', 'callback' => [__CLASS__, 'get_coupons'], 'permission_callback' => [Permissions::class, 'check_admin_permission'], ]); // Taxes analytics register_rest_route('woonoow/v1', '/analytics/taxes', [ 'methods' => 'GET', 'callback' => [__CLASS__, 'get_taxes'], 'permission_callback' => [Permissions::class, 'check_admin_permission'], ]); } /** * Get overview/dashboard analytics * * @param WP_REST_Request $request * @return WP_REST_Response|WP_Error */ public static function get_overview(WP_REST_Request $request) { try { $period = $request->get_param('period') ?: '30'; $cache_key = 'woonoow_overview_' . md5($period); $cached_data = get_transient($cache_key); if (false !== $cached_data) { return new WP_REST_Response($cached_data, 200); } $data = self::calculate_overview_metrics($period); set_transient($cache_key, $data, 5 * MINUTE_IN_SECONDS); return new WP_REST_Response($data, 200); } catch (\Exception $e) { return new WP_Error( 'analytics_error', $e->getMessage(), ['status' => 500] ); } } /** * Get revenue analytics * * @param WP_REST_Request $request * @return WP_REST_Response|WP_Error */ public static function get_revenue(WP_REST_Request $request) { try { $granularity = $request->get_param('granularity') ?: 'day'; $period = $request->get_param('period') ?: '30'; // Cache key based on parameters $cache_key = 'woonoow_revenue_' . md5($granularity . '_' . $period); $cached_data = get_transient($cache_key); if (false !== $cached_data) { return new WP_REST_Response($cached_data, 200); } $data = self::calculate_revenue_metrics($granularity, $period); // Cache for 5 minutes set_transient($cache_key, $data, 5 * MINUTE_IN_SECONDS); return new WP_REST_Response($data, 200); } catch (\Exception $e) { return new WP_Error( 'analytics_error', $e->getMessage(), ['status' => 500] ); } } /** * Get orders analytics * * @param WP_REST_Request $request * @return WP_REST_Response|WP_Error */ public static function get_orders(WP_REST_Request $request) { try { $period = $request->get_param('period') ?: '30'; // Cache key $cache_key = 'woonoow_orders_' . md5($period); $cached_data = get_transient($cache_key); if (false !== $cached_data) { return new WP_REST_Response($cached_data, 200); } $data = self::calculate_orders_metrics($period); // Cache for 5 minutes set_transient($cache_key, $data, 5 * MINUTE_IN_SECONDS); return new WP_REST_Response($data, 200); } catch (\Exception $e) { return new WP_Error( 'analytics_error', $e->getMessage(), ['status' => 500] ); } } /** * Get products analytics * * @param WP_REST_Request $request * @return WP_REST_Response|WP_Error */ public static function get_products(WP_REST_Request $request) { try { $period = $request->get_param('period') ?: '30'; $cache_key = 'woonoow_products_' . md5($period); $cached_data = get_transient($cache_key); if (false !== $cached_data) { return new WP_REST_Response($cached_data, 200); } $data = self::calculate_products_metrics($period); set_transient($cache_key, $data, 5 * MINUTE_IN_SECONDS); return new WP_REST_Response($data, 200); } catch (\Exception $e) { return new WP_Error( 'analytics_error', $e->getMessage(), ['status' => 500] ); } } /** * Get customers analytics * * @param WP_REST_Request $request * @return WP_REST_Response|WP_Error */ public static function get_customers(WP_REST_Request $request) { try { $period = $request->get_param('period') ?: '30'; $cache_key = 'woonoow_customers_' . md5($period); $cached_data = get_transient($cache_key); if (false !== $cached_data) { return new WP_REST_Response($cached_data, 200); } $data = self::calculate_customers_metrics($period); set_transient($cache_key, $data, 5 * MINUTE_IN_SECONDS); return new WP_REST_Response($data, 200); } catch (\Exception $e) { return new WP_Error( 'analytics_error', $e->getMessage(), ['status' => 500] ); } } /** * Get coupons analytics * * @param WP_REST_Request $request * @return WP_REST_Response|WP_Error */ public static function get_coupons(WP_REST_Request $request) { try { $period = $request->get_param('period') ?: '30'; $cache_key = 'woonoow_coupons_' . md5($period); $cached_data = get_transient($cache_key); if (false !== $cached_data) { return new WP_REST_Response($cached_data, 200); } $data = self::calculate_coupons_metrics($period); set_transient($cache_key, $data, 5 * MINUTE_IN_SECONDS); return new WP_REST_Response($data, 200); } catch (\Exception $e) { return new WP_Error( 'analytics_error', $e->getMessage(), ['status' => 500] ); } } /** * Get taxes analytics * * @param WP_REST_Request $request * @return WP_REST_Response|WP_Error */ public static function get_taxes(WP_REST_Request $request) { try { $period = $request->get_param('period') ?: '30'; $cache_key = 'woonoow_taxes_' . md5($period); $cached_data = get_transient($cache_key); if (false !== $cached_data) { return new WP_REST_Response($cached_data, 200); } $data = self::calculate_taxes_metrics($period); set_transient($cache_key, $data, 5 * MINUTE_IN_SECONDS); return new WP_REST_Response($data, 200); } catch (\Exception $e) { return new WP_Error( 'analytics_error', $e->getMessage(), ['status' => 500] ); } } // ======================================== // PRIVATE HELPER METHODS (Future Implementation) // ======================================== /** * Calculate overview metrics */ private static function calculate_overview_metrics($period = '30') { global $wpdb; $orders_table = $wpdb->prefix . 'wc_orders'; $days = $period === 'all' ? 365 : intval($period); // Get sales chart data $salesChart = $wpdb->get_results($wpdb->prepare(" SELECT DATE(date_created_gmt) as date, SUM(total_amount) as revenue, COUNT(*) as orders FROM {$orders_table} WHERE status IN ('wc-completed', 'wc-processing') AND date_created_gmt >= DATE_SUB(NOW(), INTERVAL %d DAY) GROUP BY DATE(date_created_gmt) ORDER BY date ASC ", $days)); // Create a map of existing data $data_map = []; foreach ($salesChart as $row) { $data_map[$row->date] = [ 'revenue' => round(floatval($row->revenue), 2), 'orders' => intval($row->orders), ]; } // Fill in ALL dates in the range (including dates with no data) $formatted_sales = []; $total_revenue = 0; $total_orders = 0; for ($i = $days - 1; $i >= 0; $i--) { $date = date('Y-m-d', strtotime("-{$i} days")); if (isset($data_map[$date])) { $revenue = $data_map[$date]['revenue']; $orders = $data_map[$date]['orders']; } else { // No data for this date, fill with zeros $revenue = 0.00; $orders = 0; } $total_revenue += $revenue; $total_orders += $orders; $formatted_sales[] = [ 'date' => $date, 'revenue' => $revenue, 'orders' => $orders, ]; } // Get order status distribution $orderStatusDistribution = $wpdb->get_results(" SELECT status, COUNT(*) as count FROM {$orders_table} WHERE date_created_gmt >= DATE_SUB(NOW(), INTERVAL {$days} DAY) GROUP BY status ORDER BY count DESC "); // Format status distribution and calculate conversion rate $formatted_status = []; $status_colors = [ 'wc-completed' => '#10b981', 'wc-processing' => '#3b82f6', 'wc-pending' => '#f59e0b', 'wc-on-hold' => '#6b7280', 'wc-cancelled' => '#ef4444', 'wc-refunded' => '#8b5cf6', 'wc-failed' => '#dc2626', ]; $total_all_orders = 0; $completed_orders = 0; foreach ($orderStatusDistribution as $status) { $status_name = str_replace('wc-', '', $status->status); $count = intval($status->count); $total_all_orders += $count; if ($status->status === 'wc-completed') { $completed_orders = $count; } $formatted_status[] = [ 'status' => ucfirst($status_name), 'count' => $count, 'color' => $status_colors[$status->status] ?? '#6b7280', ]; } // Calculate metrics $avg_order_value = $total_orders > 0 ? round($total_revenue / $total_orders, 2) : 0; // Calculate conversion rate: (Completed Orders / Total Orders) × 100 $conversion_rate = $total_all_orders > 0 ? round(($completed_orders / $total_all_orders) * 100, 2) : 0.00; // Get top products $products_table = $wpdb->prefix . 'wc_order_product_lookup'; $top_products = $wpdb->get_results($wpdb->prepare(" SELECT product_id, SUM(product_qty) as items_sold, SUM(product_gross_revenue) as revenue FROM {$products_table} WHERE date_created >= DATE_SUB(NOW(), INTERVAL %d DAY) GROUP BY product_id ORDER BY revenue DESC LIMIT 5 ", $days)); $formatted_products = []; foreach ($top_products as $product) { $wc_product = wc_get_product($product->product_id); if ($wc_product) { $formatted_products[] = [ 'product_id' => intval($product->product_id), 'name' => $wc_product->get_name(), 'items_sold' => intval($product->items_sold), 'revenue' => round(floatval($product->revenue), 2), ]; } } // Get top customers $top_customers = $wpdb->get_results($wpdb->prepare(" SELECT customer_id, billing_email, COUNT(*) as order_count, SUM(total_amount) as total_spent FROM {$orders_table} WHERE status IN ('wc-completed', 'wc-processing') AND date_created_gmt >= DATE_SUB(NOW(), INTERVAL %d DAY) AND customer_id > 0 GROUP BY customer_id ORDER BY total_spent DESC LIMIT 5 ", $days)); $formatted_customers = []; foreach ($top_customers as $customer) { $user = get_user_by('id', $customer->customer_id); $formatted_customers[] = [ 'customer_id' => intval($customer->customer_id), 'name' => $user ? $user->display_name : 'Guest', 'email' => $customer->billing_email, 'order_count' => intval($customer->order_count), 'total_spent' => round(floatval($customer->total_spent), 2), ]; } return [ 'metrics' => [ 'revenue' => [ 'today' => $total_revenue, 'yesterday' => 0, // TODO: Calculate 'change' => 0, ], 'orders' => [ 'today' => $total_orders, 'yesterday' => 0, // TODO: Calculate 'change' => 0, ], 'avgOrderValue' => [ 'today' => $avg_order_value, 'yesterday' => 0, // TODO: Calculate 'change' => 0, ], 'conversionRate' => [ 'today' => $conversion_rate, 'yesterday' => 0, // TODO: Calculate previous period 'change' => 0, ], ], 'salesChart' => $formatted_sales, 'orderStatus' => $formatted_status, 'orderStatusDistribution' => $formatted_status, // Keep for backward compatibility 'topProducts' => $formatted_products, 'topCustomers' => $formatted_customers, 'lowStock' => [], // TODO: Implement ]; } /** * Calculate revenue metrics */ private static function calculate_revenue_metrics($granularity = 'day', $period = '30') { global $wpdb; // Determine date range if ($period === 'all') { $date_query = "1=1"; // All time $days = 365; // Limit to 1 year for performance } else { $days = intval($period); $date_query = "date_created_gmt >= DATE_SUB(NOW(), INTERVAL {$days} DAY)"; } // Query HPOS orders table $orders_table = $wpdb->prefix . 'wc_orders'; // Get chart data grouped by date $chart_data_raw = $wpdb->get_results($wpdb->prepare(" SELECT DATE(date_created_gmt) as date, SUM(total_amount) as gross, SUM(total_amount - tax_amount) as net, SUM(tax_amount) as tax, 0 as refunds, 0 as shipping FROM {$orders_table} WHERE status IN ('wc-completed', 'wc-processing') AND {$date_query} GROUP BY DATE(date_created_gmt) ORDER BY date ASC LIMIT %d ", $days)); // Calculate overview metrics and format chart data $total_gross = 0; $total_net = 0; $total_tax = 0; $total_refunds = 0; $order_count = 0; $chart_data = []; foreach ($chart_data_raw as $row) { $gross = round(floatval($row->gross), 2); $net = round(floatval($row->net), 2); $tax = round(floatval($row->tax), 2); $total_gross += $gross; $total_net += $net; $total_tax += $tax; // Format for frontend $chart_data[] = [ 'date' => $row->date, 'gross' => $gross, 'net' => $net, 'tax' => $tax, 'refunds' => 0, 'shipping' => 0, ]; } // Get order count $order_count = $wpdb->get_var($wpdb->prepare(" SELECT COUNT(*) FROM {$orders_table} WHERE status IN ('wc-completed', 'wc-processing') AND {$date_query} ")); $avg_order_value = $order_count > 0 ? round($total_gross / $order_count, 2) : 0; // Get top products by revenue $products_table = $wpdb->prefix . 'wc_order_product_lookup'; $by_product = $wpdb->get_results($wpdb->prepare(" SELECT product_id, SUM(product_gross_revenue) as revenue, SUM(product_qty) as quantity FROM {$products_table} WHERE date_created >= DATE_SUB(NOW(), INTERVAL %d DAY) GROUP BY product_id ORDER BY revenue DESC LIMIT 10 ", $days)); // Format product data $formatted_products = []; foreach ($by_product as $product) { $wc_product = wc_get_product($product->product_id); if ($wc_product) { $formatted_products[] = [ 'product_id' => intval($product->product_id), 'name' => $wc_product->get_name(), 'revenue' => round(floatval($product->revenue), 2), 'quantity' => intval($product->quantity), ]; } } return [ 'overview' => [ 'gross_revenue' => round($total_gross, 2), 'net_revenue' => round($total_net, 2), 'tax' => round($total_tax, 2), 'shipping' => 0, // TODO: Calculate 'refunds' => round($total_refunds, 2), 'change_percent' => 0, // TODO: Calculate 'previous_gross_revenue' => 0, // TODO: Calculate 'previous_net_revenue' => 0, // TODO: Calculate ], 'chart_data' => $chart_data, 'by_product' => $formatted_products, 'by_category' => [], // TODO: Implement 'by_payment_method' => [], // TODO: Implement 'by_shipping_method' => [], // TODO: Implement ]; } /** * Calculate products metrics */ private static function calculate_products_metrics($period = '30') { global $wpdb; $products_table = $wpdb->prefix . 'wc_order_product_lookup'; $days = $period === 'all' ? 365 : intval($period); // Get top products $top_products = $wpdb->get_results($wpdb->prepare(" SELECT product_id, SUM(product_qty) as items_sold, SUM(product_gross_revenue) as revenue, SUM(product_net_revenue) as net_revenue FROM {$products_table} WHERE date_created >= DATE_SUB(NOW(), INTERVAL %d DAY) GROUP BY product_id ORDER BY revenue DESC LIMIT 20 ", $days)); // Format products $formatted_products = []; $total_revenue = 0; $total_items = 0; foreach ($top_products as $product) { $wc_product = wc_get_product($product->product_id); if ($wc_product) { $revenue = round(floatval($product->revenue), 2); $items_sold = intval($product->items_sold); $total_revenue += $revenue; $total_items += $items_sold; $formatted_products[] = [ 'id' => intval($product->product_id), 'product_id' => intval($product->product_id), 'name' => $wc_product->get_name(), 'image' => '📦', 'sku' => $wc_product->get_sku() ?: 'N/A', 'items_sold' => $items_sold, 'revenue' => $revenue, 'net_revenue' => round(floatval($product->net_revenue), 2), 'stock' => $wc_product->get_stock_quantity() ?: 0, 'stock_status' => $wc_product->get_stock_status(), 'stock_quantity' => $wc_product->get_stock_quantity() ?: 0, 'views' => 0, 'conversion_rate' => 0.0, ]; } } // Calculate average price $avg_price = $total_items > 0 ? round($total_revenue / $total_items, 2) : 0; // Get low stock and out of stock counts $low_stock_count = $wpdb->get_var(" SELECT COUNT(*) FROM {$wpdb->prefix}posts p INNER JOIN {$wpdb->prefix}postmeta pm ON p.ID = pm.post_id WHERE p.post_type = 'product' AND p.post_status = 'publish' AND pm.meta_key = '_stock_status' AND pm.meta_value = 'onbackorder' "); $out_of_stock_count = $wpdb->get_var(" SELECT COUNT(*) FROM {$wpdb->prefix}posts p INNER JOIN {$wpdb->prefix}postmeta pm ON p.ID = pm.post_id WHERE p.post_type = 'product' AND p.post_status = 'publish' AND pm.meta_key = '_stock_status' AND pm.meta_value = 'outofstock' "); return [ 'overview' => [ 'items_sold' => $total_items, 'revenue' => $total_revenue, 'avg_price' => $avg_price, 'low_stock_count' => intval($low_stock_count), 'out_of_stock_count' => intval($out_of_stock_count), 'change_percent' => 0, // TODO: Calculate ], 'top_products' => $formatted_products, 'by_category' => [], // TODO: Implement 'stock_analysis' => [ 'low_stock' => [], 'out_of_stock' => [], 'slow_movers' => [], ], ]; } /** * Calculate taxes metrics */ private static function calculate_taxes_metrics($period = '30') { global $wpdb; $orders_table = $wpdb->prefix . 'wc_orders'; $days = $period === 'all' ? 365 : intval($period); // Get tax data over time - ONLY orders with tax > 0 $chart_data = $wpdb->get_results($wpdb->prepare(" SELECT DATE(date_created_gmt) as date, SUM(tax_amount) as tax, COUNT(*) as orders FROM {$orders_table} WHERE status IN ('wc-completed', 'wc-processing') AND date_created_gmt >= DATE_SUB(NOW(), INTERVAL %d DAY) AND tax_amount > 0 GROUP BY DATE(date_created_gmt) ORDER BY date ASC ", $days)); // Format chart data $formatted_chart = []; $total_tax = 0; $total_orders_with_tax = 0; foreach ($chart_data as $row) { $tax = round(floatval($row->tax), 2); $orders = intval($row->orders); $total_tax += $tax; $total_orders_with_tax += $orders; $formatted_chart[] = [ 'date' => $row->date, 'tax' => $tax, 'orders' => $orders, ]; } // Calculate average tax per order (only for orders with tax) $avg_tax_per_order = $total_orders_with_tax > 0 ? round($total_tax / $total_orders_with_tax, 2) : 0; return [ 'overview' => [ 'total_tax' => $total_tax, 'avg_tax_per_order' => $avg_tax_per_order, 'orders_with_tax' => $total_orders_with_tax, ], 'chart_data' => $formatted_chart, 'by_rate' => [], // TODO: Implement if needed 'by_location' => [], // TODO: Implement if needed ]; } /** * Calculate customers metrics */ private static function calculate_customers_metrics($period = '30') { global $wpdb; $orders_table = $wpdb->prefix . 'wc_orders'; $days = $period === 'all' ? 365 : intval($period); // Get top customers by revenue $top_customers = $wpdb->get_results($wpdb->prepare(" SELECT customer_id, billing_email, COUNT(*) as order_count, SUM(total_amount) as total_spent FROM {$orders_table} WHERE status IN ('wc-completed', 'wc-processing') AND date_created_gmt >= DATE_SUB(NOW(), INTERVAL %d DAY) AND customer_id > 0 GROUP BY customer_id ORDER BY total_spent DESC LIMIT 20 ", $days)); // Format customers $formatted_customers = []; $total_customers = 0; $total_revenue = 0; $formatted_customers = []; foreach ($top_customers as $customer) { $user = get_user_by('id', $customer->customer_id); $total_spent = round(floatval($customer->total_spent), 2); $order_count = intval($customer->order_count); $total_customers++; $total_revenue += $total_spent; $formatted_customers[] = [ 'id' => intval($customer->customer_id), 'customer_id' => intval($customer->customer_id), 'name' => $user ? $user->display_name : 'Guest', 'email' => $customer->billing_email, 'orders' => $order_count, 'order_count' => $order_count, 'total_spent' => $total_spent, 'avg_order_value' => round($total_spent / $order_count, 2), 'last_order_date' => '', // TODO: Implement 'segment' => 'returning', // TODO: Calculate 'days_since_last_order' => 0, // TODO: Calculate ]; } // Get new vs returning $new_customers = $wpdb->get_var($wpdb->prepare(" SELECT COUNT(DISTINCT customer_id) FROM {$orders_table} WHERE customer_id > 0 AND date_created_gmt >= DATE_SUB(NOW(), INTERVAL %d DAY) AND customer_id IN ( SELECT customer_id FROM {$orders_table} WHERE customer_id > 0 GROUP BY customer_id HAVING MIN(date_created_gmt) >= DATE_SUB(NOW(), INTERVAL %d DAY) ) ", $days, $days)); // Calculate additional metrics $returning_customers = $total_customers - intval($new_customers); $avg_ltv = $total_customers > 0 ? round($total_revenue / $total_customers, 2) : 0; $avg_orders_per_customer = $total_customers > 0 ? round(array_sum(array_column($formatted_customers, 'order_count')) / $total_customers, 2) : 0; return [ 'overview' => [ 'total_customers' => $total_customers, 'new_customers' => intval($new_customers), 'returning_customers' => $returning_customers, 'avg_ltv' => $avg_ltv, 'retention_rate' => 0, // TODO: Calculate 'avg_orders_per_customer' => $avg_orders_per_customer, 'change_percent' => 0, // TODO: Calculate ], 'segments' => [ 'new' => intval($new_customers), 'returning' => $returning_customers, 'vip' => 0, // TODO: Calculate 'at_risk' => 0, // TODO: Calculate ], 'top_customers' => $formatted_customers, 'acquisition_chart' => [], // TODO: Implement 'ltv_distribution' => [], // TODO: Implement ]; } /** * Calculate orders metrics */ private static function calculate_orders_metrics($period = '30') { global $wpdb; $orders_table = $wpdb->prefix . 'wc_orders'; // Determine date range if ($period === 'all') { $date_query = "1=1"; $days = 365; } else { $days = intval($period); $date_query = "date_created_gmt >= DATE_SUB(NOW(), INTERVAL {$days} DAY)"; } // Get orders by status $by_status = $wpdb->get_results(" SELECT status, COUNT(*) as count, SUM(total_amount) as total FROM {$orders_table} WHERE {$date_query} GROUP BY status ORDER BY count DESC "); // Format status data $formatted_status = []; $total_orders = 0; $total_revenue = 0; foreach ($by_status as $status) { $status_name = str_replace('wc-', '', $status->status); $count = intval($status->count); $total = round(floatval($status->total), 2); $total_orders += $count; if (in_array($status->status, ['wc-completed', 'wc-processing'])) { $total_revenue += $total; } $formatted_status[] = [ 'status' => $status_name, 'status_label' => ucfirst($status_name), 'count' => $count, 'total' => $total, ]; } // Get orders over time $chart_data = $wpdb->get_results($wpdb->prepare(" SELECT DATE(date_created_gmt) as date, COUNT(*) as orders, SUM(CASE WHEN status = 'wc-completed' THEN 1 ELSE 0 END) as completed, SUM(CASE WHEN status = 'wc-processing' THEN 1 ELSE 0 END) as processing, SUM(CASE WHEN status = 'wc-pending' THEN 1 ELSE 0 END) as pending, SUM(CASE WHEN status = 'wc-cancelled' THEN 1 ELSE 0 END) as cancelled, SUM(CASE WHEN status = 'wc-refunded' THEN 1 ELSE 0 END) as refunded, SUM(CASE WHEN status = 'wc-failed' THEN 1 ELSE 0 END) as failed FROM {$orders_table} WHERE {$date_query} GROUP BY DATE(date_created_gmt) ORDER BY date ASC LIMIT %d ", $days)); // Format chart data $formatted_chart = []; foreach ($chart_data as $row) { $formatted_chart[] = [ 'date' => $row->date, 'orders' => intval($row->orders), 'completed' => intval($row->completed), 'processing' => intval($row->processing), 'pending' => intval($row->pending), 'cancelled' => intval($row->cancelled), 'refunded' => intval($row->refunded), 'failed' => intval($row->failed), ]; } // Calculate average order value and rates $avg_order_value = $total_orders > 0 ? round($total_revenue / $total_orders, 2) : 0; // Calculate fulfillment and cancellation rates $completed_orders = 0; $cancelled_orders = 0; foreach ($formatted_status as $status) { if ($status['status'] === 'completed') { $completed_orders = $status['count']; } if (in_array($status['status'], ['cancelled', 'failed', 'refunded'])) { $cancelled_orders += $status['count']; } } $fulfillment_rate = $total_orders > 0 ? round(($completed_orders / $total_orders) * 100, 2) : 0; $cancellation_rate = $total_orders > 0 ? round(($cancelled_orders / $total_orders) * 100, 2) : 0; // Calculate average processing time (time from pending/processing to completed) $avg_processing_time_seconds = $wpdb->get_var($wpdb->prepare(" SELECT AVG(TIMESTAMPDIFF(SECOND, date_created_gmt, date_updated_gmt)) FROM {$orders_table} WHERE status = 'wc-completed' AND {$date_query} AND date_updated_gmt > date_created_gmt ")); // Convert to human-readable format $avg_processing_time = '0 hours'; if ($avg_processing_time_seconds > 0) { $hours = floor($avg_processing_time_seconds / 3600); $minutes = floor(($avg_processing_time_seconds % 3600) / 60); if ($hours > 24) { $days = floor($hours / 24); $remaining_hours = $hours % 24; $avg_processing_time = $days . ' day' . ($days > 1 ? 's' : ''); if ($remaining_hours > 0) { $avg_processing_time .= ' ' . $remaining_hours . ' hour' . ($remaining_hours > 1 ? 's' : ''); } } elseif ($hours > 0) { $avg_processing_time = $hours . ' hour' . ($hours > 1 ? 's' : ''); if ($minutes > 0) { $avg_processing_time .= ' ' . $minutes . ' min'; } } else { $avg_processing_time = $minutes . ' minutes'; } } // Add color and percentage to status data - SORT BY IMPORTANCE $status_colors = [ 'completed' => '#10b981', 'processing' => '#3b82f6', 'pending' => '#f59e0b', 'cancelled' => '#ef4444', 'refunded' => '#8b5cf6', 'failed' => '#dc2626', 'on-hold' => '#6b7280', ]; // Define sort order (most important first) $status_order = ['completed', 'processing', 'pending', 'on-hold', 'cancelled', 'refunded', 'failed']; foreach ($formatted_status as &$status) { $status['color'] = $status_colors[$status['status']] ?? '#6b7280'; $status['percentage'] = $total_orders > 0 ? round(($status['count'] / $total_orders) * 100, 2) : 0; } // Sort by predefined order usort($formatted_status, function($a, $b) use ($status_order) { $pos_a = array_search($a['status'], $status_order); $pos_b = array_search($b['status'], $status_order); $pos_a = $pos_a !== false ? $pos_a : 999; $pos_b = $pos_b !== false ? $pos_b : 999; return $pos_a - $pos_b; }); // Get orders by hour of day $by_hour = $wpdb->get_results($wpdb->prepare(" SELECT HOUR(date_created_gmt) as hour, COUNT(*) as orders FROM {$orders_table} WHERE {$date_query} GROUP BY HOUR(date_created_gmt) ORDER BY hour ASC ")); $formatted_by_hour = []; for ($h = 0; $h < 24; $h++) { $formatted_by_hour[] = [ 'hour' => $h, 'orders' => 0, ]; } foreach ($by_hour as $row) { $hour = intval($row->hour); $formatted_by_hour[$hour]['orders'] = intval($row->orders); } // Get orders by day of week $by_day = $wpdb->get_results($wpdb->prepare(" SELECT DAYOFWEEK(date_created_gmt) as day_number, COUNT(*) as orders FROM {$orders_table} WHERE {$date_query} GROUP BY DAYOFWEEK(date_created_gmt) ORDER BY day_number ASC ")); $day_names = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; $formatted_by_day = []; for ($d = 1; $d <= 7; $d++) { $formatted_by_day[] = [ 'day' => $day_names[$d - 1], 'day_number' => $d, 'orders' => 0, ]; } foreach ($by_day as $row) { $day_num = intval($row->day_number); $formatted_by_day[$day_num - 1]['orders'] = intval($row->orders); } return [ 'overview' => [ 'total_orders' => $total_orders, 'avg_order_value' => $avg_order_value, 'fulfillment_rate' => $fulfillment_rate, 'cancellation_rate' => $cancellation_rate, 'avg_processing_time' => $avg_processing_time, 'change_percent' => 0, // TODO: Calculate 'previous_total_orders' => 0, // TODO: Calculate ], 'by_status' => $formatted_status, 'chart_data' => $formatted_chart, 'by_hour' => $formatted_by_hour, 'by_day_of_week' => $formatted_by_day, ]; } /** * Calculate coupons metrics */ private static function calculate_coupons_metrics($period = '30') { global $wpdb; $orders_table = $wpdb->prefix . 'wc_orders'; $order_items_table = $wpdb->prefix . 'woocommerce_order_items'; $order_itemmeta_table = $wpdb->prefix . 'woocommerce_order_itemmeta'; $days = $period === 'all' ? 365 : intval($period); // Get coupon usage data $coupon_data = $wpdb->get_results($wpdb->prepare(" SELECT oi.order_item_name as coupon_code, COUNT(DISTINCT oi.order_id) as uses, SUM(oim.meta_value) as total_discount FROM {$order_items_table} oi INNER JOIN {$order_itemmeta_table} oim ON oi.order_item_id = oim.order_item_id INNER JOIN {$orders_table} o ON oi.order_id = o.id WHERE oi.order_item_type = 'coupon' AND oim.meta_key = 'discount_amount' AND o.status IN ('wc-completed', 'wc-processing') AND o.date_created_gmt >= DATE_SUB(NOW(), INTERVAL %d DAY) GROUP BY oi.order_item_name ORDER BY total_discount DESC ", $days)); // Format coupon performance data $formatted_coupons = []; $total_discount = 0; $total_uses = 0; foreach ($coupon_data as $coupon) { $discount = round(floatval($coupon->total_discount), 2); $uses = intval($coupon->uses); $total_discount += $discount; $total_uses += $uses; // Get coupon details from posts table $coupon_post = $wpdb->get_row($wpdb->prepare(" SELECT ID, post_title FROM {$wpdb->prefix}posts WHERE post_title = %s AND post_type = 'shop_coupon' LIMIT 1 ", $coupon->coupon_code)); $coupon_id = $coupon_post ? intval($coupon_post->ID) : 0; $wc_coupon = $coupon_id ? new \WC_Coupon($coupon_id) : null; // Get revenue generated from orders with this coupon $revenue_generated = $wpdb->get_var($wpdb->prepare(" SELECT SUM(o.total_amount) FROM {$order_items_table} oi INNER JOIN {$orders_table} o ON oi.order_id = o.id WHERE oi.order_item_type = 'coupon' AND oi.order_item_name = %s AND o.status IN ('wc-completed', 'wc-processing') AND o.date_created_gmt >= DATE_SUB(NOW(), INTERVAL %d DAY) ", $coupon->coupon_code, $days)); $revenue_generated = round(floatval($revenue_generated), 2); // Calculate ROI: (Revenue Generated - Discount Given) / Discount Given * 100 // ROI shows how much revenue was generated per dollar of discount $roi = $discount > 0 ? round((($revenue_generated - $discount) / $discount) * 100, 2) : 0; $formatted_coupons[] = [ 'id' => $coupon_id, 'code' => $coupon->coupon_code, 'type' => $wc_coupon ? $wc_coupon->get_discount_type() : 'fixed_cart', 'amount' => $wc_coupon ? floatval($wc_coupon->get_amount()) : 0, 'uses' => $uses, 'discount_amount' => $discount, 'revenue_generated' => $revenue_generated, 'roi' => $roi, 'usage_limit' => $wc_coupon ? $wc_coupon->get_usage_limit() : null, 'expiry_date' => $wc_coupon && $wc_coupon->get_date_expires() ? $wc_coupon->get_date_expires()->format('Y-m-d') : null, ]; } // Get usage chart data over time $usage_chart = $wpdb->get_results($wpdb->prepare(" SELECT DATE(o.date_created_gmt) as date, COUNT(DISTINCT oi.order_id) as uses, SUM(oim.meta_value) as discount, SUM(o.total_amount) as revenue FROM {$order_items_table} oi INNER JOIN {$order_itemmeta_table} oim ON oi.order_item_id = oim.order_item_id INNER JOIN {$orders_table} o ON oi.order_id = o.id WHERE oi.order_item_type = 'coupon' AND oim.meta_key = 'discount_amount' AND o.status IN ('wc-completed', 'wc-processing') AND o.date_created_gmt >= DATE_SUB(NOW(), INTERVAL %d DAY) GROUP BY DATE(o.date_created_gmt) ORDER BY date ASC ", $days)); $formatted_chart = []; $revenue_with_coupons = 0; foreach ($usage_chart as $row) { $revenue = round(floatval($row->revenue), 2); $revenue_with_coupons += $revenue; $formatted_chart[] = [ 'date' => $row->date, 'uses' => intval($row->uses), 'discount' => round(floatval($row->discount), 2), 'revenue' => $revenue, ]; } $avg_discount_per_order = $total_uses > 0 ? round($total_discount / $total_uses, 2) : 0; return [ 'overview' => [ 'total_discount' => $total_discount, 'coupons_used' => $total_uses, 'revenue_with_coupons' => $revenue_with_coupons, 'avg_discount_per_order' => $avg_discount_per_order, 'change_percent' => 0, // TODO: Calculate ], 'usage_chart' => $formatted_chart, 'coupons' => $formatted_coupons, ]; } }