feat(payment): rebuild order details and asset records
This commit is contained in:
@@ -193,8 +193,31 @@ trait OrderRepositoryAdmin {
|
||||
return ['items' => [], 'total' => 0];
|
||||
}
|
||||
|
||||
$where = ['product_type = %s', 'product_id > 0'];
|
||||
$params = [$product_type];
|
||||
$content_types = [
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE,
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_READING,
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_DOWNLOAD,
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_IMAGE,
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_VIDEO,
|
||||
];
|
||||
$legacy_pay_types = [
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_READING => \AKSRT\Core\DomainModel::PAY_TYPE_READING,
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_DOWNLOAD => \AKSRT\Core\DomainModel::PAY_TYPE_DOWNLOAD,
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_IMAGE => \AKSRT\Core\DomainModel::PAY_TYPE_GALLERY,
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_VIDEO => \AKSRT\Core\DomainModel::PAY_TYPE_VIDEO,
|
||||
];
|
||||
$where = ['product_id > 0'];
|
||||
$params = [];
|
||||
if ($product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE) {
|
||||
$where[] = 'product_type IN (' . implode(', ', array_fill(0, count($content_types), '%s')) . ')';
|
||||
array_push($params, ...$content_types);
|
||||
} elseif (isset($legacy_pay_types[$product_type])) {
|
||||
$where[] = "(product_type = %s OR (product_type = %s AND note LIKE %s))";
|
||||
array_push($params, $product_type, \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE, '%"pay_type":"' . $wpdb->esc_like($legacy_pay_types[$product_type]) . '"%');
|
||||
} else {
|
||||
$where[] = 'product_type = %s';
|
||||
$params[] = $product_type;
|
||||
}
|
||||
|
||||
$status_placeholders = implode(', ', array_fill(0, count($status_in), '%s'));
|
||||
$where[] = "status IN ({$status_placeholders})";
|
||||
@@ -236,14 +259,14 @@ trait OrderRepositoryAdmin {
|
||||
$offset = max(0, (int) ($args['offset'] ?? 0));
|
||||
|
||||
$list_sql = "SELECT product_id,
|
||||
product_type,
|
||||
MIN(product_type) AS product_type,
|
||||
currency,
|
||||
COUNT(*) AS sales_count,
|
||||
AVG(amount) AS avg_amount,
|
||||
SUM(amount) AS revenue
|
||||
FROM {$table}
|
||||
{$where_sql}
|
||||
GROUP BY product_id, product_type, currency
|
||||
GROUP BY product_id, currency
|
||||
ORDER BY {$orderby_col} {$order_dir}
|
||||
LIMIT %d OFFSET %d";
|
||||
$list_params = $params;
|
||||
@@ -252,6 +275,7 @@ trait OrderRepositoryAdmin {
|
||||
|
||||
$rows = $wpdb->get_results($wpdb->prepare($list_sql, $list_params), ARRAY_A);
|
||||
$items = [];
|
||||
$aggregate = [];
|
||||
foreach ((array) $rows as $row) {
|
||||
$sales_count = max(1, (int) ($row['sales_count'] ?? 0));
|
||||
$is_points = (string) ($row['currency'] ?? '') === 'points';
|
||||
@@ -269,12 +293,37 @@ trait OrderRepositoryAdmin {
|
||||
intdiv($revenue_minor + intdiv($sales_count, 2), $sales_count)
|
||||
);
|
||||
}
|
||||
$items[] = [
|
||||
$pid = (int) ($row['product_id'] ?? 0);
|
||||
if (!isset($aggregate[$pid])) {
|
||||
$aggregate[$pid] = [
|
||||
'product_id' => (int) ($row['product_id'] ?? 0),
|
||||
'product_type' => (string) ($row['product_type'] ?? $product_type),
|
||||
'sales_count' => (int) ($row['sales_count'] ?? 0),
|
||||
'avg_amount' => $avg_amount,
|
||||
'revenue' => $revenue,
|
||||
'product_type' => $product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE ? $product_type : (string) ($row['product_type'] ?? $product_type),
|
||||
'sales_count' => 0,
|
||||
'avg_amount' => 0,
|
||||
'revenue_minor' => 0,
|
||||
'currency' => (string) ($row['currency'] ?? 'CNY'),
|
||||
];
|
||||
}
|
||||
$aggregate[$pid]['sales_count'] += (int) ($row['sales_count'] ?? 0);
|
||||
if ((string) ($row['currency'] ?? '') === 'points') {
|
||||
$aggregate[$pid]['revenue_minor'] += (int) $revenue;
|
||||
} else {
|
||||
$aggregate[$pid]['revenue_minor'] += \AKSRT\Ledger\AssetAmount::balance_minor((string) $revenue);
|
||||
}
|
||||
}
|
||||
foreach ($aggregate as $item) {
|
||||
$count = max(1, (int) $item['sales_count']);
|
||||
$is_points = $item['currency'] === 'points';
|
||||
$revenue = $is_points
|
||||
? (int) $item['revenue_minor']
|
||||
: \AKSRT\Ledger\AssetAmount::decimal_from_minor((int) $item['revenue_minor']);
|
||||
$average_minor = intdiv((int) $item['revenue_minor'] + intdiv($count, 2), $count);
|
||||
$items[] = [
|
||||
'product_id' => (int) $item['product_id'],
|
||||
'product_type' => (string) $item['product_type'],
|
||||
'sales_count' => (int) $item['sales_count'],
|
||||
'avg_amount' => $is_points ? $average_minor : \AKSRT\Ledger\AssetAmount::decimal_from_minor($average_minor),
|
||||
'revenue' => $revenue,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -344,17 +393,39 @@ trait OrderRepositoryAdmin {
|
||||
$status = sanitize_key((string) ($args['status'] ?? ''));
|
||||
$valid_statuses = array_keys(\AKSRT\Core\DomainModel::order_status_labels());
|
||||
if ($status !== '' && $status !== 'all' && in_array($status, $valid_statuses, true)) {
|
||||
$where[] = 'status = %s';
|
||||
$params[] = $status;
|
||||
if ($status === 'paid') {
|
||||
$where[] = "status IN (%s, 'refunding')";
|
||||
$params[] = $status;
|
||||
} else {
|
||||
$where[] = 'status = %s';
|
||||
$params[] = $status;
|
||||
}
|
||||
} else {
|
||||
$where[] = 'status IN (' . implode(', ', array_fill(0, count($valid_statuses), '%s')) . ')';
|
||||
array_push($params, ...$valid_statuses);
|
||||
$where[] = "status IN (" . implode(', ', array_fill(0, count($valid_statuses), '%s')) . ", 'refunding')";
|
||||
array_push($params, ...$valid_statuses);
|
||||
}
|
||||
|
||||
$product_type = sanitize_key((string) ($args['product_type'] ?? ''));
|
||||
if ($product_type !== '') {
|
||||
$where[] = 'product_type = %s';
|
||||
$params[] = $product_type;
|
||||
if ($product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE) {
|
||||
$types = \AKSRT\Core\DomainModel::resource_order_product_types();
|
||||
$where[] = 'product_type IN (' . implode(', ', array_fill(0, count($types), '%s')) . ')';
|
||||
array_push($params, ...$types);
|
||||
} else {
|
||||
$legacy_pay_types = [
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_READING => \AKSRT\Core\DomainModel::PAY_TYPE_READING,
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_DOWNLOAD => \AKSRT\Core\DomainModel::PAY_TYPE_DOWNLOAD,
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_IMAGE => \AKSRT\Core\DomainModel::PAY_TYPE_GALLERY,
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_VIDEO => \AKSRT\Core\DomainModel::PAY_TYPE_VIDEO,
|
||||
];
|
||||
if (isset($legacy_pay_types[$product_type])) {
|
||||
$where[] = "(product_type = %s OR (product_type = %s AND note LIKE %s))";
|
||||
array_push($params, $product_type, \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE, '%"pay_type":"' . $wpdb->esc_like($legacy_pay_types[$product_type]) . '"%');
|
||||
} else {
|
||||
$where[] = 'product_type = %s';
|
||||
$params[] = $product_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$product_id = (int) ($args['product_id'] ?? 0);
|
||||
@@ -383,24 +454,35 @@ trait OrderRepositoryAdmin {
|
||||
}
|
||||
|
||||
if ($search !== '') {
|
||||
$flow_table = \AKSRT\Core\DBMigrator::table('financial_flow');
|
||||
$flow_match = "EXISTS (SELECT 1 FROM {$flow_table} ff WHERE ff.order_number = " . $this->table() . '.order_number AND ff.flow_number LIKE %s)';
|
||||
// Numeric search may mean user_id or product_id; also match order or flow number / type.
|
||||
if (ctype_digit($search)) {
|
||||
$where[] = "(order_number LIKE %s OR product_type LIKE %s OR user_id = %d OR product_id = %d OR {$flow_match})";
|
||||
$like = '%' . $wpdb->esc_like($search) . '%';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
$params[] = (int) $search;
|
||||
$params[] = (int) $search;
|
||||
$params[] = $like;
|
||||
} else {
|
||||
$where[] = "(order_number LIKE %s OR product_type LIKE %s OR payment_method LIKE %s OR {$flow_match})";
|
||||
$like = '%' . $wpdb->esc_like($search) . '%';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
$user_table = $wpdb->users;
|
||||
$user_match = "EXISTS (SELECT 1 FROM {$user_table} u WHERE u.ID = " . $this->table() . '.user_id AND (u.user_login LIKE %s OR u.user_email LIKE %s OR u.display_name LIKE %s))';
|
||||
$post_table = $wpdb->posts;
|
||||
$product_match = "EXISTS (SELECT 1 FROM {$post_table} p WHERE p.ID = " . $this->table() . '.product_id AND p.post_title LIKE %s)';
|
||||
$meta_match = "note LIKE %s";
|
||||
// Search matches the public order number, account, product title, and stored product label.
|
||||
if (ctype_digit($search)) {
|
||||
$where[] = "(order_number LIKE %s OR product_type LIKE %s OR user_id = %d OR product_id = %d OR {$user_match} OR {$product_match} OR {$meta_match})";
|
||||
$like = '%' . $wpdb->esc_like($search) . '%';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
$params[] = (int) $search;
|
||||
$params[] = (int) $search;
|
||||
array_push($params, $like, $like, $like, $like, $like);
|
||||
} else {
|
||||
$where[] = "(order_number LIKE %s OR product_type LIKE %s OR payment_method LIKE %s OR {$user_match} OR {$product_match} OR {$meta_match})";
|
||||
$like = '%' . $wpdb->esc_like($search) . '%';
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
$params[] = $like;
|
||||
array_push($params, $like, $like, $like, $like, $like);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['date_from' => '>=', 'date_to' => '<='] as $key => $operator) {
|
||||
$date = sanitize_text_field((string) ($args[$key] ?? ''));
|
||||
if ($date !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
|
||||
$where[] = 'created_at ' . $operator . ' %s';
|
||||
$params[] = $operator === '>=' ? $date . ' 00:00:00' : $date . ' 23:59:59';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user