__('Shipment Cost Reports', 'ai-eshop-optimizer'),
'description' => __('Per-order shipping margin analysis. Catches under-priced shipping zones, sub-par carrier mixes, and lost-margin SKUs you did not know you were absorbing — the single biggest hidden cost on most WooCommerce stores.', 'ai-eshop-optimizer'),
'bullets' => array(
__('Per-order P&L: shipping fees charged vs the free-shipping value you absorbed, broken down by zone, weight, and method.', 'ai-eshop-optimizer'),
__('Carrier comparison — see which courier is winning on price for which zone.', 'ai-eshop-optimizer'),
__('CSV export ready for the accountant.', 'ai-eshop-optimizer'),
),
);
}
public function register_hooks() {
add_action('wp_ajax_aieo_dmm_shipping_pnl', array($this, 'ajax_shipping_pnl'));
add_action('wp_ajax_aieo_dmm_shipping_pnl_export', array($this, 'ajax_shipping_pnl_export'));
}
public function render_subtab() {
$nonce = wp_create_nonce(AIEO_DMM::NONCE_ACTION);
$ajax = admin_url('admin-ajax.php');
$this->render_retrospective_scan_card($nonce, $ajax);
$this->render_shipping_pnl_card($nonce, $ajax);
}
private function render_retrospective_scan_card($nonce, $ajax) {
list($aieo_scr_open, $aieo_scr_close) = AIEO_DMM_UI::card(array(
'anchor' => 'shipment-cost-reports-free-shipping-cost-retrospective-analysis',
'icon' => 'truck',
'title' => __('Cost of past free shipping', 'ai-eshop-optimizer'),
'description' => __('Scans past orders where shipping was free, re-runs WooCommerce shipping calculation (using each order\'s shipping postcode + cart weight + line subtotal — same inputs as table-rate shipping at checkout) and records the rate the customer would have been charged. That is a re-quote off your own shipping table, not the courier\'s invoice, so treat it as the value you gave away rather than a bill you paid. Lets you size historical free-shipping campaigns. The same job runs nightly via cron; this button is for ad-hoc analysis.', 'ai-eshop-optimizer'),
'footnote' => __('You can close this page once the run starts — the daily cron will pick up where you stopped at 03:30 the next morning.', 'ai-eshop-optimizer'),
));
echo $aieo_scr_open;
$aieo_scr_days = \Rooster\Admin\Ui::field_row(array(
'id' => 'aieo-fs-cost-days',
'label' => __('Look-back window (days):', 'ai-eshop-optimizer'),
'control' => \Rooster\Admin\Ui::input('', '90', array('id' => 'aieo-fs-cost-days', 'type' => 'number', 'min' => 1, 'max' => 9999, 'step' => 1, 'xs' => true)),
));
echo '
' . $aieo_scr_days . '
';
$aieo_scr_job = \Rooster\Admin\States::job(array(
'id' => 'aieo-fs-cost-job',
'state' => 'idle',
'run' => __('Run now', 'ai-eshop-optimizer'),
'run_attrs' => array('id' => 'aieo-fs-cost-run'),
));
echo $aieo_scr_job;
echo $aieo_scr_close;
echo '';
}
private function render_shipping_pnl_card($nonce, $ajax) {
list($aieo_scr_open, $aieo_scr_close) = AIEO_DMM_UI::card(array(
'anchor' => 'shipment-cost-reports-shipping-p-l-by-carrier-zone-monthly',
'icon' => 'table',
'title' => __('Shipping profit and loss by carrier and zone', 'ai-eshop-optimizer'),
'description' => __('The shipping money that moved through each method per month, broken down by shipping zone (mainland / islands / EU / rest of world). "Customer revenue" = shipping fees customers paid you on paid orders. "Free shipping cost" = the carrier rate you absorbed for free-shipping orders (estimated by the resolver above). "Shipping revenue + absorbed value" = both summed. The "Free %" column tells you what fraction of that total was eaten by free-shipping promos.', 'ai-eshop-optimizer'),
'flush' => true,
));
echo $aieo_scr_open;
echo AIEO_DMM_UI::notice(esc_html__('This card does not show what the couriers invoiced you.', 'ai-eshop-optimizer'), 'warning');
echo \Rooster\Admin\Guidance::text(__('Its revenue side is what the customer paid and its free-shipping side is a re-computed quote, so the total tracks your shipping price list, not the courier\'s. For the invoiced figure go to Fulfillment → Delivery Statistics, which reads the reconciled per-voucher cost written by the month-end carrier invoice import.', 'ai-eshop-optimizer'));
echo '';
$aieo_scr_month_options = array(
'current' => __('Current month', 'ai-eshop-optimizer'),
'last' => __('Last month', 'ai-eshop-optimizer'),
);
foreach (array(3, 6, 12, 24, 36) as $m) {
$aieo_scr_month_options[(string) $m] = sprintf(_n('Last %d month', 'Last %d months', $m, 'ai-eshop-optimizer'), $m);
}
$aieo_scr_months = \Rooster\Admin\Ui::select('', $aieo_scr_month_options, '12', array('id' => 'aieo-sp-months'));
echo \Rooster\Admin\Ui::filter(__('Period:', 'ai-eshop-optimizer'), $aieo_scr_months, 'aieo-sp-months');
$aieo_scr_group = \Rooster\Admin\Ui::select('', array(
'month_method_zone' => __('Month × Method × Zone', 'ai-eshop-optimizer'),
'month_method' => __('Month × Method', 'ai-eshop-optimizer'),
'month_zone' => __('Month × Zone', 'ai-eshop-optimizer'),
'method_zone' => __('Method × Zone (all months)', 'ai-eshop-optimizer'),
'month' => __('Month total', 'ai-eshop-optimizer'),
'method' => __('Method total', 'ai-eshop-optimizer'),
'zone' => __('Zone total', 'ai-eshop-optimizer'),
), 'month_method_zone', array('id' => 'aieo-sp-group'));
echo \Rooster\Admin\Ui::filter(__('Group by:', 'ai-eshop-optimizer'), $aieo_scr_group, 'aieo-sp-group');
$aieo_scr_all = array('' => __('All', 'ai-eshop-optimizer'));
echo \Rooster\Admin\Ui::filter(__('Method:', 'ai-eshop-optimizer'), \Rooster\Admin\Ui::select('', $aieo_scr_all, '', array('id' => 'aieo-sp-method')), 'aieo-sp-method');
echo \Rooster\Admin\Ui::filter(__('Zone:', 'ai-eshop-optimizer'), \Rooster\Admin\Ui::select('', $aieo_scr_all, '', array('id' => 'aieo-sp-zone')), 'aieo-sp-zone');
echo '
'
. '' . esc_html__('Refresh', 'ai-eshop-optimizer') . ' '
. ' '
. '' . esc_html__('Export XLSX', 'ai-eshop-optimizer') . ' '
. '' . esc_html__('Nothing to export yet.', 'ai-eshop-optimizer') . ' '
. '
';
echo '
';
echo '
';
\Rooster\Admin\States::enqueue();
echo \Rooster\Admin\States::result_head('', array('id' => 'aieo-sp-result', 'attrs' => array('hidden' => true)));
$aieo_scr_states = array(
'loading' => \Rooster\Admin\States::state('loading'),
'setup' => \Rooster\Admin\States::state('not-set-up', array('title' => __('HPOS (wp_wc_orders) is required for the shipping P&L view. Enable WooCommerce → Settings → Advanced → Features → "High-Performance order storage".', 'ai-eshop-optimizer'))),
'first' => \Rooster\Admin\States::state('first-use', array('title' => __('No shipping data for this window. If you just deployed, run the retrospective scan above so historical free-shipping rows have method + zone populated.', 'ai-eshop-optimizer'))),
'none' => \Rooster\Admin\States::state('no-results', array('clear' => '#')),
'failed' => \Rooster\Admin\States::state('failed', array('text' => __('Unknown error.', 'ai-eshop-optimizer'))),
);
foreach ($aieo_scr_states as $aieo_scr_kind => $aieo_scr_state) {
echo '' . $aieo_scr_state . '
';
}
$aieo_scr_row_one = _n('%s row', '%s rows', 1, 'ai-eshop-optimizer');
$aieo_scr_row_many = _n('%s row', '%s rows', 2, 'ai-eshop-optimizer');
AIEO_Table::table_assets();
echo '
';
$aieo_money_cfg = wp_json_encode(array(
'symbol' => html_entity_decode(get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8'),
'pos' => get_option('woocommerce_currency_pos', 'left'),
'dec' => wc_get_price_decimal_separator(),
'thou' => wc_get_price_thousand_separator(),
'nd' => wc_get_price_decimals(),
), JSON_HEX_TAG);
if (!is_string($aieo_money_cfg) || $aieo_money_cfg === '') {
$aieo_money_cfg = '{"symbol":"","pos":"left","dec":".","thou":",","nd":2}';
}
echo '';
echo $aieo_scr_close;
}
public function ajax_shipping_pnl() {
AIEO_DMM::verify_ajax_request();
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(array('message' => __('Insufficient permissions.', 'ai-eshop-optimizer')));
}
$payload = $this->compute_shipping_pnl($_POST);
$payload['table'] = $this->sp_table($payload);
wp_send_json_success($payload);
}
private function sp_table($payload) {
$cols = isset($payload['columns']) && is_array($payload['columns']) ? $payload['columns'] : array();
$numeric = array('currency', 'currency_strong', 'integer', 'percent');
$columns = array();
$has = array_flip(array_map('strval', array_column($cols, 'key')));
foreach ($cols as $i => $col) {
$num = in_array($col['fmt'], $numeric, true);
$fig = array();
if ('currency' === $col['fmt'] || 'currency_strong' === $col['fmt']) {
$fig = array('format' => 'money');
} elseif ('percent' === $col['fmt']) {
$fig = array('total' => isset($has['free_orders'], $has['total_orders']) && 'pct_free' === $col['key']
? array('ratio' => array('free_orders', 'total_orders'), 'percent' => true, 'decimals' => 1)
: 'none');
}
$columns[] = array(
'key' => (string) $col['key'],
'label' => (string) $col['label'],
'type' => $num ? 'num' : 'text',
'sort_type' => $num ? 'number' : 'text',
'always' => 0 === $i,
) + $fig;
}
$rows = array();
foreach ((array) ($payload['rows'] ?? array()) as $i => $r) {
$row = array('_id' => (string) ($i + 1), '_s' => array());
foreach ($cols as $col) {
$k = (string) $col['key'];
$v = $r[$k] ?? '';
if ('currency' === $col['fmt'] || 'currency_strong' === $col['fmt']) {
$row[$k] = AIEO_Table_Columns::money($v);
if ('currency_strong' === $col['fmt']) {
$row[$k]['html'] = '' . $row[$k]['html'] . ' ';
}
$row['_s'][$k] = (float) $v;
} elseif ('integer' === $col['fmt']) {
$row[$k] = number_format_i18n((int) $v);
$row['_s'][$k] = (int) $v;
} elseif ('percent' === $col['fmt']) {
$pct = (float) $v;
$row[$k] = array(
'value' => number_format_i18n($pct * 100, 1) . ' %',
'tone' => $pct > 0.30 ? 'destructive' : ($pct > 0.15 ? 'warning' : 'success'),
);
$row['_s'][$k] = $pct;
} else {
$row[$k] = (string) $v;
$row['_s'][$k] = (string) $v;
}
}
$rows[] = $row;
}
return AIEO_Table::local_answer('aieo-sp-table', $columns, $rows, array(
'prefix' => 'sp_',
'size' => 0,
'caption' => __('Shipping P&L', 'ai-eshop-optimizer'),
'chooser' => true,
));
}
public function ajax_shipping_pnl_export() {
AIEO_DMM::verify_ajax_request();
if (!current_user_can('manage_woocommerce')) { wp_die(esc_html__('Insufficient permissions.', 'ai-eshop-optimizer')); }
$payload = $this->compute_shipping_pnl($_GET);
if (!class_exists('\Rooster\Admin\Export') && function_exists('rooster_admin_builders')) {
rooster_admin_builders();
}
if (!class_exists('\Rooster\Admin\Export')) { wp_die(esc_html__('XLSX writer unavailable.', 'ai-eshop-optimizer')); }
$columns = array();
foreach ($payload['columns'] as $col) {
$format = null;
if ($col['fmt'] === 'currency' || $col['fmt'] === 'currency_strong') { $format = 'money'; }
elseif ($col['fmt'] === 'integer') { $format = array('decimals' => 0); }
elseif ($col['fmt'] === 'percent') { $format = array('decimals' => 2); }
$columns[$col['key']] = null === $format ? array('label' => $col['label']) : array('label' => $col['label'], 'format' => $format);
}
$total = array();
foreach (array_keys($columns) as $i => $k) {
if ($i === 0) { $total[$k] = __('TOTAL', 'ai-eshop-optimizer'); }
elseif (isset($payload['totals'][$k])) { $total[$k] = $payload['totals'][$k]; }
elseif ($k === 'pct_free') {
$inv = (float) $payload['totals']['gross_shipping_value'];
$total[$k] = $inv > 0 ? ((float) $payload['totals']['free_cost'] / $inv) : 0;
}
}
$rows = $payload['rows'];
$rows[] = $total;
$months = (int) $payload['filters']['months'];
\Rooster\Admin\Export::xlsx('shipping-pnl-' . $months . 'mo-' . gmdate('Y-m-d') . '.xlsx', array(array('name' => 'Shipping P&L', 'columns' => $columns, 'rows' => $rows)));
exit;
}
private function period_window_sql($period_value, $date_col) {
$period = (string) $period_value;
$cur_bounds = function_exists('aieo_site_month_bounds_utc')
? aieo_site_month_bounds_utc('current')
: array('start' => gmdate('Y-m-01 00:00:00'), 'end' => gmdate('Y-m-01 00:00:00', strtotime('+1 month')));
$last_bounds = function_exists('aieo_site_month_bounds_utc')
? aieo_site_month_bounds_utc('last')
: array('start' => gmdate('Y-m-01 00:00:00', strtotime('-1 month')), 'end' => gmdate('Y-m-01 00:00:00'));
if ($period === 'current') {
return array(
'sql' => "{$date_col} >= '{$cur_bounds['start']}' AND {$date_col} < '{$cur_bounds['end']}'",
'tag' => 'current-month',
'months_for_compat' => 1,
);
}
if ($period === 'last') {
return array(
'sql' => "{$date_col} >= '{$last_bounds['start']}' AND {$date_col} < '{$last_bounds['end']}'",
'tag' => 'last-month',
'months_for_compat' => 1,
);
}
$n = max(1, min(60, (int) $period));
return array(
'sql' => "{$date_col} >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL {$n} MONTH)",
'tag' => $n . 'mo',
'months_for_compat' => $n,
);
}
private function compute_shipping_pnl($input) {
global $wpdb;
$prefix = $wpdb->prefix;
$period_in = isset($input['months']) ? sanitize_text_field((string) $input['months']) : '12';
$period = $this->period_window_sql($period_in, 'o.date_created_gmt');
$months = $period['months_for_compat'];
$group = isset($input['group_by']) ? sanitize_key((string) $input['group_by']) : 'month_method_zone';
$valid = array('month_method_zone','month_method','month_zone','method_zone','month','method','zone');
if (!in_array($group, $valid, true)) { $group = 'month_method_zone'; }
$f_method = isset($input['method']) ? sanitize_text_field((string) $input['method']) : '';
$f_zone = isset($input['zone']) ? sanitize_text_field((string) $input['zone']) : '';
$orders_tbl = $prefix . 'wc_orders';
$opdata_tbl = $prefix . 'wc_order_operational_data';
$items_tbl = $prefix . 'woocommerce_order_items';
$itemmeta_tbl = $prefix . 'woocommerce_order_itemmeta';
$zones_tbl = $prefix . 'woocommerce_shipping_zones';
$zonemeth_tbl = $prefix . 'woocommerce_shipping_zone_methods';
$attr_tbl = $prefix . 'aieo_dmm_offer_attributions';
$hpos_ok = AIEO_Order_Store::hpos_readable();
$unmatched_zone = __('Unmatched zone', 'ai-eshop-optimizer');
$unknown_method = __('Unknown method', 'ai-eshop-optimizer');
$empty = function() use ($months, $group, $f_method, $f_zone) {
return array(
'rows' => array(),
'columns' => array(),
'totals' => array('paid_orders'=>0,'free_orders'=>0,'total_orders'=>0,'shipping_revenue'=>0,'free_cost'=>0,'free_pending'=>0,'gross_shipping_value'=>0,'pct_free'=>0),
'methods' => array(),
'zones' => array(),
'filters' => array('months'=>$months,'group_by'=>$group,'method'=>$f_method,'zone'=>$f_zone),
'message' => '',
);
};
if (!$hpos_ok) {
$r = $empty();
$r['message'] = __('HPOS (wp_wc_orders) is required for the shipping P&L view. Enable WooCommerce → Settings → Advanced → Features → "High-Performance order storage".', 'ai-eshop-optimizer');
return $r;
}
$paid_sql = $wpdb->prepare(
"SELECT
DATE_FORMAT(o.date_created_gmt, %s) AS month_key,
COALESCE(z.zone_name, %s) AS zone_name,
COALESCE(NULLIF(oi.order_item_name, ''), %s) AS method_label,
COUNT(DISTINCT o.id) AS paid_orders,
COALESCE(SUM(od.shipping_total_amount), 0) AS shipping_revenue,
0 AS free_orders,
0 AS free_cost,
0 AS free_pending
FROM {$orders_tbl} o
INNER JOIN {$opdata_tbl} od ON od.order_id = o.id
LEFT JOIN {$items_tbl} oi ON oi.order_id = o.id AND oi.order_item_type = 'shipping'
LEFT JOIN {$itemmeta_tbl} mi ON mi.order_item_id = oi.order_item_id AND mi.meta_key = 'instance_id'
LEFT JOIN {$zonemeth_tbl} zm ON zm.instance_id = CAST(mi.meta_value AS UNSIGNED)
LEFT JOIN {$zones_tbl} z ON z.zone_id = zm.zone_id
WHERE o.status IN ('wc-processing','wc-completed','wc-on-hold','processing','completed','on-hold')
AND o.type = 'shop_order'
AND od.shipping_total_amount > 0
AND ({$period['sql']})
GROUP BY month_key, zone_name, method_label",
'%Y-%m', $unmatched_zone, $unknown_method
);
$free_sql = $wpdb->prepare(
"SELECT
DATE_FORMAT(o.date_created_gmt, %s) AS month_key,
COALESCE(NULLIF(a.shipping_zone, ''), %s) AS zone_name,
COALESCE(NULLIF(a.shipping_method, ''), %s) AS method_label,
0 AS paid_orders,
0 AS shipping_revenue,
COUNT(*) AS free_orders,
COALESCE(SUM(a.actual_cost), 0) AS free_cost,
SUM(a.actual_cost IS NULL) AS free_pending
FROM {$attr_tbl} a
INNER JOIN {$orders_tbl} o ON o.id = a.order_id
WHERE a.offer_type = 'free_shipping'
AND o.type = 'shop_order'
AND ({$period['sql']})
GROUP BY month_key, zone_name, method_label",
'%Y-%m', $unmatched_zone, $unknown_method
);
$select_dims = array();
$group_dims = array();
$columns = array();
if (in_array($group, array('month_method_zone','month_method','month_zone','month'), true)) {
$select_dims[] = 'month_key';
$group_dims[] = 'month_key';
$columns[] = array('key'=>'month_key', 'label'=>__('Month', 'ai-eshop-optimizer'), 'fmt'=>'string');
}
if (in_array($group, array('month_method_zone','month_method','method_zone','method'), true)) {
$select_dims[] = 'method_label';
$group_dims[] = 'method_label';
$columns[] = array('key'=>'method_label', 'label'=>__('Shipping method', 'ai-eshop-optimizer'), 'fmt'=>'string');
}
if (in_array($group, array('month_method_zone','month_zone','method_zone','zone'), true)) {
$select_dims[] = 'zone_name';
$group_dims[] = 'zone_name';
$columns[] = array('key'=>'zone_name', 'label'=>__('Zone', 'ai-eshop-optimizer'), 'fmt'=>'string');
}
$columns[] = array('key'=>'total_orders', 'label'=>__('Orders', 'ai-eshop-optimizer'), 'fmt'=>'integer');
$columns[] = array('key'=>'free_orders', 'label'=>__('Free orders', 'ai-eshop-optimizer'), 'fmt'=>'integer');
$columns[] = array('key'=>'shipping_revenue', 'label'=>__('Customer revenue', 'ai-eshop-optimizer'), 'fmt'=>'currency');
$columns[] = array('key'=>'free_cost', 'label'=>__('Free shipping cost', 'ai-eshop-optimizer'),'fmt'=>'currency');
$columns[] = array('key'=>'free_pending', 'label'=>__('Pending cost', 'ai-eshop-optimizer'), 'fmt'=>'integer');
$columns[] = array('key'=>'gross_shipping_value', 'label'=>__('Shipping revenue + absorbed value', 'ai-eshop-optimizer'),'fmt'=>'currency_strong');
$columns[] = array('key'=>'pct_free', 'label'=>__('Free %', 'ai-eshop-optimizer'), 'fmt'=>'percent');
$select_csv = empty($select_dims) ? "''" : implode(', ', $select_dims);
$group_csv = empty($group_dims) ? "''" : implode(', ', $group_dims);
$outer_where = array();
$outer_params = array();
if ($f_method !== '') { $outer_where[] = 'method_label = %s'; $outer_params[] = $f_method; }
if ($f_zone !== '') { $outer_where[] = 'zone_name = %s'; $outer_params[] = $f_zone; }
$outer_where_sql = $outer_where ? ('WHERE ' . implode(' AND ', $outer_where)) : '';
$order_sql = 'ORDER BY ';
$order_parts = array();
if (in_array('month_key', $group_dims, true)) { $order_parts[] = 'month_key DESC'; }
if (in_array('method_label', $group_dims, true)) { $order_parts[] = 'method_label ASC'; }
if (in_array('zone_name', $group_dims, true)) { $order_parts[] = 'zone_name ASC'; }
$order_sql .= empty($order_parts) ? 'gross_shipping_value DESC' : implode(', ', $order_parts);
$final_sql = "SELECT {$select_csv},
SUM(paid_orders) AS paid_orders,
SUM(free_orders) AS free_orders,
SUM(paid_orders + free_orders) AS total_orders,
SUM(shipping_revenue) AS shipping_revenue,
SUM(free_cost) AS free_cost,
SUM(free_pending) AS free_pending,
SUM(shipping_revenue + free_cost) AS gross_shipping_value
FROM (
{$paid_sql}
UNION ALL
{$free_sql}
) sub
{$outer_where_sql}
" . (empty($group_dims) ? '' : "GROUP BY {$group_csv}") . "
{$order_sql}";
$prepared = $outer_params ? $wpdb->prepare($final_sql, $outer_params) : $final_sql;
$rows_raw = $wpdb->get_results($prepared, ARRAY_A);
if (!is_array($rows_raw)) { $rows_raw = array(); }
$rows = array();
$totals = array(
'paid_orders' => 0,
'free_orders' => 0,
'total_orders' => 0,
'shipping_revenue' => 0.0,
'free_cost' => 0.0,
'free_pending' => 0,
'gross_shipping_value' => 0.0,
);
foreach ($rows_raw as $r) {
$invoice = (float) $r['gross_shipping_value'];
$free = (float) $r['free_cost'];
$row = array(
'paid_orders' => (int) $r['paid_orders'],
'free_orders' => (int) $r['free_orders'],
'total_orders' => (int) $r['total_orders'],
'shipping_revenue' => round((float) $r['shipping_revenue'], 2),
'free_cost' => round($free, 2),
'free_pending' => (int) $r['free_pending'],
'gross_shipping_value' => round($invoice, 2),
'pct_free' => $invoice > 0 ? round($free / $invoice, 4) : 0,
);
if (isset($r['month_key'])) { $row['month_key'] = $r['month_key']; }
if (isset($r['method_label'])) { $row['method_label'] = $r['method_label']; }
if (isset($r['zone_name'])) { $row['zone_name'] = $r['zone_name']; }
$rows[] = $row;
$totals['paid_orders'] += (int) $r['paid_orders'];
$totals['free_orders'] += (int) $r['free_orders'];
$totals['total_orders'] += (int) $r['total_orders'];
$totals['shipping_revenue'] += (float) $r['shipping_revenue'];
$totals['free_cost'] += $free;
$totals['free_pending'] += (int) $r['free_pending'];
$totals['gross_shipping_value'] += $invoice;
}
foreach (array('shipping_revenue','free_cost','gross_shipping_value') as $k) {
$totals[$k] = round($totals[$k], 2);
}
$totals['pct_free'] = $totals['gross_shipping_value'] > 0 ? round($totals['free_cost'] / $totals['gross_shipping_value'], 4) : 0;
$methods = $wpdb->get_col("SELECT DISTINCT method_label FROM (
({$paid_sql})
UNION ALL
({$free_sql})
) m WHERE method_label <> '' ORDER BY method_label ASC");
if (!is_array($methods)) { $methods = array(); }
$zones = $wpdb->get_col("SELECT DISTINCT zone_name FROM (
({$paid_sql})
UNION ALL
({$free_sql})
) z WHERE zone_name <> '' ORDER BY zone_name ASC");
if (!is_array($zones)) { $zones = array(); }
return array(
'rows' => $rows,
'columns' => $columns,
'totals' => $totals,
'methods' => $methods,
'zones' => $zones,
'filters' => array('months'=>$months,'group_by'=>$group,'method'=>$f_method,'zone'=>$f_zone),
);
}
}