feat(admin): pending shipment board with immediate ship actions

Add paid-but-unshipped order queue on the shipping page (zibll-style),
repository query via NOT EXISTS on order_shipping, overview pending card,
and create-shipping redirect back to the pending tab.
This commit is contained in:
kate522 committed 2026-07-15 03:05:01 +08:00
1 parent d1697c19d9
commit 4c5e4ab222
3 files changed
+180 -6

No files matched your search

@@ -348,6 +348,63 @@ class AKSRT_Order_Repository {
/**
* 管理后台订单列表 — 支持 search(order_number / product_type 模糊匹配)
*/
/**
* Paid orders that still need shipping (no shipping row yet).
*
* @return array{items: array<int,array<string,mixed>>, total: int, page: int, max_pages: int}
*/
public function get_pending_shipment_orders(array $args = []): array {
global $wpdb;
$limit = max(1, min(100, (int) ($args['limit'] ?? 20)));
$offset = max(0, (int) ($args['offset'] ?? 0));
$search = sanitize_text_field((string) ($args['search'] ?? $args['s'] ?? ''));
$order_table = $this->table();
// Must match AKSRT_Shipping::table() key.
$shipping_table = class_exists('AKSRT_DB_Migrator')
? AKSRT_DB_Migrator::table('order_shipping')
: $wpdb->prefix . 'aksrt_order_shipping';
$where = ["o.status = %s"];
$params = ['paid'];
if ($search !== '') {
$where[] = '(o.order_number LIKE %s OR o.product_type LIKE %s)';
$like = '%' . $wpdb->esc_like($search) . '%';
$params[] = $like;
$params[] = $like;
}
// NOT EXISTS avoids ONLY_FULL_GROUP_BY issues from LEFT JOIN + GROUP BY.
$where[] = "NOT EXISTS (
SELECT 1 FROM {$shipping_table} s
WHERE s.order_number = o.order_number
)";
$where_sql = 'WHERE ' . implode(' AND ', $where);
$count_sql = "SELECT COUNT(*) FROM {$order_table} o {$where_sql}";
$total = (int) $wpdb->get_var($wpdb->prepare($count_sql, $params));
$sql = "SELECT o.*
FROM {$order_table} o
{$where_sql}
ORDER BY COALESCE(o.paid_at, o.created_at) DESC, o.id DESC
LIMIT %d OFFSET %d";
$query_params = $params;
$query_params[] = $limit;
$query_params[] = $offset;
$rows = $wpdb->get_results($wpdb->prepare($sql, $query_params), ARRAY_A);
$items = array_map([$this, 'normalize_row'], (array) $rows);
return [
'items' => $items,
'total' => $total,
'page' => (int) floor($offset / $limit) + 1,
'max_pages' => (int) ceil(max(1, $total) / $limit),
];
}
public function get_orders_admin(array $args = [], string $search = ''): array {
global $wpdb;