array(), 'queues' => array( 'payment' => 0, 'fulfil' => 0, 'returns_count' => 0, ), 'generated_at' => current_time( 'Y-m-d H:i' ), ); if ( get_option( 'storesuit_order_index_ready' ) === '1' ) { $out['stages'] = DashboardTotals::part( $errors, 'flow', static function () { global $wpdb; $out = array(); foreach ( DashboardTotals::rows( $wpdb->get_results( $wpdb->prepare( "SELECT flow_stage,COUNT(*) n FROM %i WHERE flow_stage IN ('new','preparing','ready') AND status NOT IN (" . implode( ',', array_fill( 0, count( SaleStatus::QUOTES ), '%s' ) ) . ') GROUP BY flow_stage', array_merge( array(OrderIndex::table()), SaleStatus::QUOTES ) ), ARRAY_A ) ) as $row ) { $out[$row['flow_stage']] = (int) $row['n']; } return $out; }, array() ); // Numbers, not strings: a zero must not read as true on Home (8.30 dashboard review M3). $out['queues'] = DashboardTotals::part( $errors, 'flow', static fn() => array_map( 'intval', DashboardTotals::row( $wpdb->get_results( $wpdb->prepare( 'SELECT COALESCE(SUM(needs_payment),0) payment,COALESCE(SUM(needs_fulfil),0) fulfil,COALESCE(SUM(needs_return),0) returns_count FROM %i WHERE status NOT IN (' . implode( ',', array_fill( 0, count( SaleStatus::QUOTES ), '%s' ) ) . ')', array_merge( array(OrderIndex::table()), SaleStatus::QUOTES ) ), ARRAY_A ) ) ), $out['queues'] ); } else { $errors['flow'] = I18n::t( 'The order index is being prepared. Refresh in a moment.' ); } if ( current_user_can( 'manage_woocommerce' ) ) { $catalog = DashboardTotals::part( $errors, 'stock', static function () { $data = get_transient( self::CACHE ); if ( !is_array( $data ) ) { $data = self::catalog(); set_transient( self::CACHE, $data, 5 * MINUTE_IN_SECONDS ); } return $data; }, array( 'stock' => null, 'campaigns' => null, ) ); $out['stock'] = $catalog['stock']; $out['campaigns'] = $catalog['campaigns']; $out['alerts'] = DashboardTotals::part( $errors, 'stock', static fn() => array( 'waiting' => (int) DashboardTotals::row( $wpdb->get_results( $wpdb->prepare( "SELECT COUNT(*) n FROM %i WHERE status='waiting'", StockAlerts::table() ), ARRAY_A ) )['n'], ), array() ); $out['carts'] = DashboardTotals::part( $errors, 'marketing', static fn() => self::carts(), array() ); } if ( current_user_can( 'storesuit_finance' ) ) { // Gift card credit and the till's cash register: paid build (the keys stay, empty). $out['cards'] = array(); $out['register'] = null; } $out['review'] = DashboardTotals::part( $errors, 'review', static fn() => ReviewPrompt::read(), null ); $out['errors'] = $errors; return $out; } /** Product counts and campaign states: the heavier part, kept for a few minutes. */ private static function catalog() : array { $threshold = max( 0, (int) get_option( 'woocommerce_notify_low_stock_amount', 2 ) ); // Product-family counts, exactly as the linked StoreRuler product list shows them. Each one throws on a failed read, // so a failed count is a stock-card error and nothing is cached: never a false zero for five minutes (merge review // of visibility v3, Important 1). Products::health()'s store-wide counts are not read here: no screen shows them. $low = Products::family_count( 'low_stock' ); $out = Products::family_count( 'out_of_stock' ); $missing_cost = 0; // Products without a purchase cost: paid build (Q5). $campaigns = null; return array( 'stock' => array( 'out' => $out, 'low' => $low, 'threshold' => $threshold, 'missing_cost' => $missing_cost, ), 'campaigns' => $campaigns, ); } /** * Abandoned carts are the reminder list the card opens; "active now" is the web-cart count. One dataset each, counts only * (0.62 review: this read the web-cart signals and about 90 products for two numbers). */ private static function carts() : array { $reminders = array( 'enabled' => false, 'waiting' => 0, ); $active = CartSignals::active_now(); // tracking: whether web carts are summarised at all, so a store that turned it off reads "Off" rather than an empty count. return array( 'enabled' => (bool) $reminders['enabled'], 'waiting' => (int) $reminders['waiting'], 'active' => $active, 'tracking' => CartSignals::enabled(), ); } }