feat(admin): native paid-post sales report page
Add aksrt_pay_products under payment center for content-pay posts (resource orders), with order aggregation and deep-link filters.
This commit is contained in:
1 parent
42acb1a630
commit
74bde18880
6 files changed
+598
-34
No files matched your search
@@ -408,21 +408,7 @@ class AKSRT_Order_Repository {
|
||||
public function get_orders_admin(array $args = [], string $search = ''): array {
|
||||
global $wpdb;
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
$status = sanitize_key((string) ($args['status'] ?? ''));
|
||||
if ($status !== '' && $status !== 'all') {
|
||||
$where[] = 'status = %s';
|
||||
$params[] = $status;
|
||||
}
|
||||
|
||||
if ($search !== '') {
|
||||
$where[] = '(order_number LIKE %s OR product_type LIKE %s)';
|
||||
$like = '%' . $wpdb->esc_like($search) . '%';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
}
|
||||
[$where, $params] = $this->build_admin_order_filters($args, $search);
|
||||
|
||||
$limit = max(1, min(100, (int) ($args['limit'] ?? 20)));
|
||||
$offset = max(0, (int) ($args['offset'] ?? 0));
|
||||
@@ -442,21 +428,7 @@ class AKSRT_Order_Repository {
|
||||
public function get_orders_count(array $args = [], string $search = ''): int {
|
||||
global $wpdb;
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
$status = sanitize_key((string) ($args['status'] ?? ''));
|
||||
if ($status !== '' && $status !== 'all') {
|
||||
$where[] = 'status = %s';
|
||||
$params[] = $status;
|
||||
}
|
||||
|
||||
if ($search !== '') {
|
||||
$where[] = '(order_number LIKE %s OR product_type LIKE %s)';
|
||||
$like = '%' . $wpdb->esc_like($search) . '%';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
}
|
||||
[$where, $params] = $this->build_admin_order_filters($args, $search);
|
||||
|
||||
$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
$sql = "SELECT COUNT(*) FROM {$this->table()} {$where_sql}";
|
||||
@@ -467,6 +439,192 @@ class AKSRT_Order_Repository {
|
||||
return (int) $wpdb->get_var($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregate paid content (resource) sales by product_id (post ID).
|
||||
*
|
||||
* @param array{
|
||||
* product_type?: string,
|
||||
* product_ids?: int[],
|
||||
* status_in?: string[],
|
||||
* limit?: int,
|
||||
* offset?: int,
|
||||
* orderby?: string,
|
||||
* order?: string
|
||||
* } $args
|
||||
* @return array{items: list<array{product_id:int,product_type:string,sales_count:int,avg_amount:float,revenue:float}>, total: int}
|
||||
*/
|
||||
public function get_content_sales_summary(array $args = []): array {
|
||||
global $wpdb;
|
||||
|
||||
$product_type = sanitize_key((string) ($args['product_type'] ?? AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE));
|
||||
if ($product_type === '') {
|
||||
$product_type = AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE;
|
||||
}
|
||||
|
||||
$status_in = $args['status_in'] ?? AKSRT_Domain_Model::paid_order_statuses();
|
||||
if (!is_array($status_in) || empty($status_in)) {
|
||||
$status_in = AKSRT_Domain_Model::paid_order_statuses();
|
||||
}
|
||||
$status_in = array_values(array_filter(array_map('sanitize_key', $status_in)));
|
||||
if (empty($status_in)) {
|
||||
return ['items' => [], 'total' => 0];
|
||||
}
|
||||
|
||||
$where = ['product_type = %s', 'product_id > 0'];
|
||||
$params = [$product_type];
|
||||
|
||||
$status_placeholders = implode(', ', array_fill(0, count($status_in), '%s'));
|
||||
$where[] = "status IN ({$status_placeholders})";
|
||||
foreach ($status_in as $st) {
|
||||
$params[] = $st;
|
||||
}
|
||||
|
||||
$product_ids = $args['product_ids'] ?? [];
|
||||
if (is_array($product_ids) && !empty($product_ids)) {
|
||||
$ids = array_values(array_unique(array_filter(array_map('intval', $product_ids), static function ($id) {
|
||||
return $id > 0;
|
||||
})));
|
||||
if (!empty($ids)) {
|
||||
$id_ph = implode(', ', array_fill(0, count($ids), '%d'));
|
||||
$where[] = "product_id IN ({$id_ph})";
|
||||
foreach ($ids as $id) {
|
||||
$params[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$where_sql = 'WHERE ' . implode(' AND ', $where);
|
||||
$table = $this->table();
|
||||
|
||||
$total_sql = "SELECT COUNT(DISTINCT product_id) FROM {$table} {$where_sql}";
|
||||
$total = (int) $wpdb->get_var($wpdb->prepare($total_sql, $params));
|
||||
|
||||
$orderby_map = [
|
||||
'sales_count' => 'sales_count',
|
||||
'revenue' => 'revenue',
|
||||
'avg_amount' => 'avg_amount',
|
||||
'product_id' => 'product_id',
|
||||
];
|
||||
$orderby_key = sanitize_key((string) ($args['orderby'] ?? 'sales_count'));
|
||||
$orderby_col = $orderby_map[$orderby_key] ?? 'sales_count';
|
||||
$order_dir = strtoupper((string) ($args['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
|
||||
|
||||
$limit = max(1, min(100, (int) ($args['limit'] ?? 20)));
|
||||
$offset = max(0, (int) ($args['offset'] ?? 0));
|
||||
|
||||
$list_sql = "SELECT product_id,
|
||||
product_type,
|
||||
COUNT(*) AS sales_count,
|
||||
AVG(amount) AS avg_amount,
|
||||
SUM(amount) AS revenue
|
||||
FROM {$table}
|
||||
{$where_sql}
|
||||
GROUP BY product_id, product_type
|
||||
ORDER BY {$orderby_col} {$order_dir}
|
||||
LIMIT %d OFFSET %d";
|
||||
$list_params = $params;
|
||||
$list_params[] = $limit;
|
||||
$list_params[] = $offset;
|
||||
|
||||
$rows = $wpdb->get_results($wpdb->prepare($list_sql, $list_params), ARRAY_A);
|
||||
$items = [];
|
||||
foreach ((array) $rows as $row) {
|
||||
$items[] = [
|
||||
'product_id' => (int) ($row['product_id'] ?? 0),
|
||||
'product_type' => (string) ($row['product_type'] ?? $product_type),
|
||||
'sales_count' => (int) ($row['sales_count'] ?? 0),
|
||||
'avg_amount' => round((float) ($row['avg_amount'] ?? 0), 2),
|
||||
'revenue' => round((float) ($row['revenue'] ?? 0), 2),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Paid order totals for a set of product IDs (resource content).
|
||||
*
|
||||
* @param int[] $product_ids
|
||||
* @return array<int, array{sales_count:int,avg_amount:float,revenue:float}>
|
||||
*/
|
||||
public function get_content_sales_map(array $product_ids, string $product_type = ''): array {
|
||||
$ids = array_values(array_unique(array_filter(array_map('intval', $product_ids), static function ($id) {
|
||||
return $id > 0;
|
||||
})));
|
||||
if (empty($ids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($product_type === '') {
|
||||
$product_type = AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE;
|
||||
}
|
||||
|
||||
$summary = $this->get_content_sales_summary([
|
||||
'product_type' => $product_type,
|
||||
'product_ids' => $ids,
|
||||
'limit' => max(count($ids), 1),
|
||||
'offset' => 0,
|
||||
'orderby' => 'product_id',
|
||||
'order' => 'ASC',
|
||||
]);
|
||||
|
||||
$map = [];
|
||||
foreach ($summary['items'] as $item) {
|
||||
$pid = (int) ($item['product_id'] ?? 0);
|
||||
if ($pid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$map[$pid] = [
|
||||
'sales_count' => (int) ($item['sales_count'] ?? 0),
|
||||
'avg_amount' => (float) ($item['avg_amount'] ?? 0),
|
||||
'revenue' => (float) ($item['revenue'] ?? 0),
|
||||
];
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared WHERE builder for admin order list/count.
|
||||
*
|
||||
* @return array{0: string[], 1: array<int, mixed>}
|
||||
*/
|
||||
private function build_admin_order_filters(array $args, string $search = ''): array {
|
||||
global $wpdb;
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
$status = sanitize_key((string) ($args['status'] ?? ''));
|
||||
if ($status !== '' && $status !== 'all') {
|
||||
$where[] = 'status = %s';
|
||||
$params[] = $status;
|
||||
}
|
||||
|
||||
$product_type = sanitize_key((string) ($args['product_type'] ?? ''));
|
||||
if ($product_type !== '') {
|
||||
$where[] = 'product_type = %s';
|
||||
$params[] = $product_type;
|
||||
}
|
||||
|
||||
$product_id = (int) ($args['product_id'] ?? 0);
|
||||
if ($product_id > 0) {
|
||||
$where[] = 'product_id = %d';
|
||||
$params[] = $product_id;
|
||||
}
|
||||
|
||||
if ($search !== '') {
|
||||
$where[] = '(order_number LIKE %s OR product_type LIKE %s)';
|
||||
$like = '%' . $wpdb->esc_like($search) . '%';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
}
|
||||
|
||||
return [$where, $params];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单统计
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user