no_stock = intval( get_option( 'woocommerce_notify_no_stock_amount' ) ); // TODO: Allow to specify the day of query in constructor atts $this->day = Helpers::date_format( current_time('timestamp'), TRUE ); $this->taxonomies[] = array( 'taxonomy' => 'product_type', 'field' => 'slug', 'terms' => Globals::get_product_types() ); // NAMING CONVENTION: The column names starting by underscore (_) are based on meta keys (the name must match the meta key name), // the column names starting with "calc_" are calculated fields and the rest are WP's standard fields // *** Following this convention is necessary for column sorting functionality *** $args['table_columns'] = array( 'thumb' => '' . __( 'Thumb', ATUM_TEXT_DOMAIN ) . '', 'title' => __( 'Product Name', ATUM_TEXT_DOMAIN ), '_supplier' => __( 'Supplier', ATUM_TEXT_DOMAIN ), '_sku' => __( 'SKU', ATUM_TEXT_DOMAIN ), '_supplier_sku' => __( 'Supplier SKU', ATUM_TEXT_DOMAIN ), 'ID' => __( 'ID', ATUM_TEXT_DOMAIN ), 'calc_type' => '' . __( 'Product Type', ATUM_TEXT_DOMAIN ) . '', 'calc_location' => '' . __( 'Location', ATUM_TEXT_DOMAIN ) . '', '_regular_price' => __( 'Regular Price', ATUM_TEXT_DOMAIN ), '_sale_price' => __( 'Sale Price', ATUM_TEXT_DOMAIN ), '_purchase_price' => __( 'Purchase Price', ATUM_TEXT_DOMAIN ), '_stock' => __( 'Current Stock', ATUM_TEXT_DOMAIN ), 'calc_inbound' => __( 'Inbound Stock', ATUM_TEXT_DOMAIN ), 'calc_hold' => __( 'Stock on Hold', ATUM_TEXT_DOMAIN ), 'calc_reserved' => __( 'Reserved Stock', ATUM_TEXT_DOMAIN ), 'calc_back_orders' => __( 'Back Orders', ATUM_TEXT_DOMAIN ), 'calc_sold_today' => __( 'Sold Today', ATUM_TEXT_DOMAIN ), 'calc_returns' => __( 'Customer Returns', ATUM_TEXT_DOMAIN ), 'calc_damages' => __( 'Warehouse Damages', ATUM_TEXT_DOMAIN ), 'calc_lost_in_post' => __( 'Lost in Post', ATUM_TEXT_DOMAIN ), 'calc_sales14' => __( 'Sales Last 14 Days', ATUM_TEXT_DOMAIN ), 'calc_sales7' => __( 'Sales Last 7 Days', ATUM_TEXT_DOMAIN ), 'calc_will_last' => __( 'Stock will Last (Days)', ATUM_TEXT_DOMAIN ), 'calc_stock_out_days' => __( 'Out of Stock for (Days)', ATUM_TEXT_DOMAIN ), 'calc_lost_sales' => __( 'Lost Sales', ATUM_TEXT_DOMAIN ), 'calc_stock_indicator' => '' . __( 'Stock Indicator', ATUM_TEXT_DOMAIN ) . '', ); // Hide the purchase price column if the current user has not the capability if ( ! AtumCapabilities::current_user_can('view_purchase_price') ) { unset( $args['table_columns']['_purchase_price'] ); } // Hide the supplier's columns if the current user has not the capability if ( ! ModuleManager::is_module_active('purchase_orders') || ! AtumCapabilities::current_user_can('read_supplier') ) { unset( $args['table_columns']['_supplier'] ); unset( $args['table_columns']['_supplier_sku'] ); } if ( ! ModuleManager::is_module_active('purchase_orders') ) { unset( $args['table_columns']['_purchase_price'] ); unset( $args['table_columns']['calc_inbound'] ); } $args['table_columns'] = (array) apply_filters( 'atum/stock_central_list/table_columns', $args['table_columns'] ); // TODO: Add group table functionality if some columns are invisible $args['group_members'] = (array) apply_filters( 'atum/stock_central_list/column_group_members', array( 'product-details' => array( 'title' => __( 'Product Details', ATUM_TEXT_DOMAIN ), 'members' => array( 'thumb', 'title', '_supplier', '_sku', '_supplier_sku', 'ID', 'calc_type', 'calc_location', '_regular_price', '_sale_price', '_purchase_price' ) ), 'stock-counters' => array( 'title' => __( 'Stock Counters', ATUM_TEXT_DOMAIN ), 'members' => array( '_stock', 'calc_inbound', 'calc_hold', 'calc_reserved', 'calc_back_orders', 'calc_sold_today' ) ), 'stock-negatives' => array( 'title' => __( 'Stock Negatives', ATUM_TEXT_DOMAIN ), 'members' => array( 'calc_returns', 'calc_damages', 'calc_lost_in_post' ) ), 'stock-selling-manager' => array( 'title' => __( 'Stock Selling Manager', ATUM_TEXT_DOMAIN ), 'members' => array( 'calc_sales14', 'calc_sales7', 'calc_will_last', 'calc_stock_out_days', 'calc_lost_sales', 'calc_stock_indicator' ) ) ) ); // Initialize totalizers $this->totalizers = apply_filters( 'atum/list_table/totalizers', array( '_stock' => 0, 'calc_inbound' => 0, 'calc_hold' => 0, 'calc_reserved' => 0, 'calc_back_orders' => 0, 'calc_sold_today' => 0, 'calc_returns' => 0, 'calc_damages' => 0, 'calc_lost_in_post' => 0, 'calc_sales14' => 0, 'calc_sales7' => 0, 'calc_lost_sales' => 0, )); parent::__construct( $args ); // Filtering with extra filters if ( ! empty( $_REQUEST['extra_filter'] ) ) { add_action( 'pre_get_posts', array($this, 'do_extra_filter') ); } // Add the "Apply Bulk Action" button to the title section add_action( 'atum/list_table/page_title_buttons', array( $this, 'add_apply_bulk_action_button' ) ); } /** * @inheritdoc */ protected function table_nav_filters() { parent::table_nav_filters(); // Extra filters $extra_filters = (array) apply_filters( 'atum/stock_central_list/extra_filters', array( 'inbound_stock' => __( 'Inbound Stock', ATUM_TEXT_DOMAIN ), 'stock_on_hold' => __( 'Stock on Hold', ATUM_TEXT_DOMAIN ), 'reserved_stock' => __( 'Reserved Stock', ATUM_TEXT_DOMAIN ), 'back_orders' => __( 'Back Orders', ATUM_TEXT_DOMAIN ), 'sold_today' => __( 'Sold Today', ATUM_TEXT_DOMAIN ), 'customer_returns' => __( 'Customer Returns', ATUM_TEXT_DOMAIN ), 'warehouse_damages' => __( 'Warehouse Damages', ATUM_TEXT_DOMAIN ), 'lost_in_post' => __( 'Lost in Post', ATUM_TEXT_DOMAIN ) )); ?> get_current_product_id(); if ( $this->allow_calcs ) { $regular_price_value = $this->product->get_regular_price(); $regular_price_value = is_numeric( $regular_price_value ) ? Helpers::format_price( $regular_price_value, [ 'trim_zeros' => TRUE, 'currency' => $this->default_currency ] ) : $regular_price; $args = apply_filters( 'atum/stock_central_list/args_regular_price', array( 'post_id' => $product_id, 'meta_key' => 'regular_price', 'value' => $regular_price_value, 'symbol' => get_woocommerce_currency_symbol(), 'currency' => $this->default_currency, 'tooltip' => __( 'Click to edit the regular price', ATUM_TEXT_DOMAIN ) ) ); $regular_price = $this->get_editable_column( $args ); } return apply_filters( 'atum/stock_central_list/column_regular_price', $regular_price, $item, $this->product ); } /** * Column for sale price * * @since 1.2.0 * * @param \WP_Post $item The WooCommerce product post to use in calculations * * @return float */ protected function column__sale_price( $item ) { $sale_price = self::EMPTY_COL; $product_id = $this->get_current_product_id(); if ( $this->allow_calcs ) { $sale_price_value = $this->product->get_sale_price(); $sale_price_value = ( is_numeric( $sale_price_value ) ) ? Helpers::format_price( $sale_price_value, [ 'trim_zeros' => TRUE, 'currency' => $this->default_currency ] ) : $sale_price; $sale_price_dates_from = ( $date = get_post_meta( $product_id, '_sale_price_dates_from', TRUE ) ) ? date_i18n( 'Y-m-d', $date ) : ''; $sale_price_dates_to = ( $date = get_post_meta( $product_id, '_sale_price_dates_to', TRUE ) ) ? date_i18n( 'Y-m-d', $date ) : ''; $args = apply_filters( 'atum/stock_central_list/args_sale_price', array( 'post_id' => $product_id, 'meta_key' => 'sale_price', 'value' => $sale_price_value, 'symbol' => get_woocommerce_currency_symbol(), 'currency' => $this->default_currency, 'tooltip' => __( 'Click to edit the sale price', ATUM_TEXT_DOMAIN ), 'extra_meta' => array( array( 'name' => '_sale_price_dates_from', 'type' => 'text', 'placeholder' => _x( 'Sale date from...', 'placeholder', ATUM_TEXT_DOMAIN ) . ' YYYY-MM-DD', 'value' => $sale_price_dates_from, 'maxlength' => 10, 'pattern' => '[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])', 'class' => 'datepicker from' ), array( 'name' => '_sale_price_dates_to', 'type' => 'text', 'placeholder' => _x( 'Sale date to...', 'placeholder', ATUM_TEXT_DOMAIN ) . ' YYYY-MM-DD', 'value' => $sale_price_dates_to, 'maxlength' => 10, 'pattern' => '[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])', 'class' => 'datepicker to' ) ) ) ); $sale_price = $this->get_editable_column( $args ); } return apply_filters( 'atum/stock_central_list/column_sale_price', $sale_price, $item, $this->product ); } /** * Column for stock on hold: show amount of items with pending payment. * * @since 0.0.1 * * @param \WP_Post $item The WooCommerce product post to use in calculations * * @return int */ protected function column_calc_hold( $item ) { $stock_on_hold = self::EMPTY_COL; if ($this->allow_calcs) { global $wpdb; $product_id_key = Helpers::is_child_type( $this->product->get_type() ) ? '_variation_id' : '_product_id'; $sql = $wpdb->prepare(" SELECT SUM(omq.`meta_value`) AS qty FROM `{$wpdb->prefix}woocommerce_order_items` oi LEFT JOIN `$wpdb->order_itemmeta` omq ON omq.`order_item_id` = oi.`order_item_id` LEFT JOIN `$wpdb->order_itemmeta` omp ON omp.`order_item_id` = oi.`order_item_id` WHERE `order_id` IN ( SELECT `ID` FROM $wpdb->posts WHERE `post_type` = 'shop_order' AND `post_status` IN ('wc-pending', 'wc-on-hold') ) AND omq.`meta_key` = '_qty' AND `order_item_type` = 'line_item' AND omp.`meta_key` = %s AND omp.`meta_value` = %d GROUP BY omp.`meta_value`", $product_id_key, $this->product->get_id() ); $stock_on_hold = wc_stock_amount( $wpdb->get_var($sql) ); $this->increase_total('calc_hold', $stock_on_hold); } return apply_filters( 'atum/stock_central_list/column_stock_hold', $stock_on_hold, $item, $this->product ); } /** * Column for reserved stock: sums the items within "Reserved Stock" logs * * @since 1.2.4 * * @param \WP_Post $item The WooCommerce product post to use in calculations * * @return int */ protected function column_calc_reserved( $item ) { $reserved_stock = !$this->allow_calcs ? self::EMPTY_COL : $this->get_log_item_qty( 'reserved-stock', $this->product->get_id() ); $this->increase_total('calc_reserved', $reserved_stock); return apply_filters( 'atum/stock_central_list/column_reserved_stock', $reserved_stock, $item, $this->product ); } /** * Column for back orders amount: show amount if items pending to serve and without existences * * @since 0.0.1 * * @param \WP_Post $item The WooCommerce product post to use in calculations * * @return int|string */ protected function column_calc_back_orders( $item ) { $back_orders = self::EMPTY_COL; if ($this->allow_calcs) { $back_orders = '--'; if ( $this->product->backorders_allowed() ) { $stock_quantity = $this->product->get_stock_quantity(); $back_orders = 0; if ( $stock_quantity < $this->no_stock ) { $back_orders = $this->no_stock - $stock_quantity; } } $this->increase_total('calc_back_orders', $back_orders); } return apply_filters( 'atum/stock_central_list/column_back_orders', $back_orders, $item, $this->product ); } /** * Column for items sold today * * @since 0.0.1 * * @param \WP_Post $item The WooCommerce product post to use in calculations * * @return int */ protected function column_calc_sold_today( $item ) { if (! $this->allow_calcs) { $sold_today = self::EMPTY_COL; } else { $sold_today = empty( $this->calc_columns[ $this->product->get_id() ]['sold_today'] ) ? 0 : $this->calc_columns[ $this->product->get_id() ]['sold_today']; } $this->increase_total('calc_sold_today', $sold_today); return apply_filters( 'atum/stock_central_list/column_sold_today', $sold_today, $item, $this->product ); } /** * Column for customer returns: sums the items within "Reserved Stock" logs * * @since 1.2.4 * * @param \WP_Post $item The WooCommerce product post to use in calculations * * @return int */ protected function column_calc_returns( $item ) { $consumer_returns = ! $this->allow_calcs ? self::EMPTY_COL : $this->get_log_item_qty( 'customer-returns', $this->product->get_id() ); $this->increase_total('calc_returns', $consumer_returns); return apply_filters( 'atum/stock_central_list/column_cutomer_returns', $consumer_returns, $item, $this->product ); } /** * Column for warehouse damages: sums the items within "Warehouse Damage" logs * * @since 1.2.4 * * @param \WP_Post $item The WooCommerce product post to use in calculations * * @return int */ protected function column_calc_damages( $item ) { $warehouse_damages = ! $this->allow_calcs ? self::EMPTY_COL : $this->get_log_item_qty( 'warehouse-damage', $this->product->get_id() ); $this->increase_total('calc_damages', $warehouse_damages); return apply_filters( 'atum/stock_central_list/column_warehouse_damage', $warehouse_damages, $item, $this->product ); } /** * Column for lost in post: sums the items within "Lost in Post" logs * * @since 1.2.4 * * @param \WP_Post $item The WooCommerce product post to use in calculations * * @return int */ protected function column_calc_lost_in_post( $item ) { $lost_in_post = ! $this->allow_calcs ? self::EMPTY_COL : $this->get_log_item_qty( 'lost-in-post', $this->product->get_id() ); $this->increase_total('calc_lost_in_post', $lost_in_post); return apply_filters( 'atum/stock_central_list/column_lost_in_post', $lost_in_post, $item, $this->product ); } /** * Column for items sold during the last week * * @since 0.1.2 * * @param \WP_Post $item The WooCommerce product post to use in calculations * * @return int */ protected function column_calc_sales7( $item ) { if (! $this->allow_calcs) { $sales7 = self::EMPTY_COL; } else { $sales7 = empty( $this->calc_columns[ $this->product->get_id() ]['sold_7'] ) ? 0 : $this->calc_columns[ $this->product->get_id() ]['sold_7']; $this->increase_total('calc_sales7', $sales7); } return apply_filters( 'atum/stock_central_list/column_sold_last_7_days', $sales7, $item, $this->product ); } /** * Column for items sold during the last 2 weeks * * @since 0.1.2 * * @param \WP_Post $item The WooCommerce product post to use in calculations * * @return int */ protected function column_calc_sales14( $item ) { if (! $this->allow_calcs) { $sales14 = self::EMPTY_COL; } else { $sales14 = empty( $this->calc_columns[ $this->product->get_id() ]['sold_14'] ) ? 0 : $this->calc_columns[ $this->product->get_id() ]['sold_14']; $this->increase_total('calc_sales14', $sales14); } return apply_filters( 'atum/stock_central_list/column_sold_last_14_days', $sales14, $item, $this->product ); } /** * Column for number of days the stock will be sufficient to fulfill orders * Formula: Current Stock Value / (Sales Last 7 Days / 7) * * @since 0.1.3 * * @param \WP_Post $item The WooCommerce product post to use in calculations * * @return int|string */ protected function column_calc_will_last( $item ) { // NOTE: FOR NOW IS FIXED TO 7 DAYS AVERAGE $will_last = self::EMPTY_COL; if ($this->allow_calcs) { $sales = $this->column_calc_sales7( $item ); $stock = $this->product->get_stock_quantity(); if ( $stock > 0 && $sales > 0 ) { $will_last = ceil( $stock / ( $sales / 7 ) ); } elseif ( $stock > 0 ) { $will_last = '>30'; } } return apply_filters( 'atum/stock_central_list/column_stock_will_last_days', $will_last, $item, $this->product ); } /** * Column for number of days the product is out of stock * * @since 0.1.4 * * @param \WP_Post $item The WooCommerce product post to use in calculations * * @return int|string */ protected function column_calc_stock_out_days( $item ) { $out_of_stock_days = ''; if ($this->allow_calcs) { $out_of_stock_days = Helpers::get_product_out_of_stock_days( $this->product->get_id() ); } $out_of_stock_days = is_numeric($out_of_stock_days) ? $out_of_stock_days : self::EMPTY_COL; return apply_filters( 'atum/stock_central_list/column_stock_out_days', $out_of_stock_days, $item, $this->product ); } /** * Column for lost sales * * @since 1.2.0 * * @param \WP_Post $item The WooCommerce product post to use in calculations * * @return int|string */ protected function column_calc_lost_sales( $item ) { $lost_sales = ''; if ($this->allow_calcs) { $lost_sales = Helpers::get_product_lost_sales( $this->product->get_id() ); } $lost_sales = is_numeric($lost_sales) ? Helpers::format_price( $lost_sales, ['trim_zeros' => TRUE] ) : self::EMPTY_COL; $this->increase_total('calc_lost_sales', $lost_sales); return apply_filters( 'atum/stock_central_list/column_lost_sales', $lost_sales, $item, $this->product ); } /** * Prepare the table data * * @since 0.0.2 */ public function prepare_items() { parent::prepare_items(); $calc_products = array_merge( $this->current_products, $this->children_products); // Calc products sold today (since midnight) $rows = Helpers::get_sold_last_days( $calc_products, 'today 00:00:00', $this->day ); if ( $rows ) { foreach ( $rows as $row ) { $this->calc_columns[ $row['PROD_ID'] ]['sold_today'] = $row['QTY']; } } // Calc products sold during the last week $rows = Helpers::get_sold_last_days( $calc_products, $this->day . ' -1 week', $this->day ); if ( $rows ) { foreach ( $rows as $row ) { $this->calc_columns[ $row['PROD_ID'] ]['sold_7'] = $row['QTY']; } } // Calc products sold during the last 2 weeks $rows = Helpers::get_sold_last_days( $calc_products, $this->day . ' -2 weeks', $this->day ); if ( $rows ) { foreach ( $rows as $row ) { $this->calc_columns[ $row['PROD_ID'] ]['sold_14'] = $row['QTY']; } } // Calc products sold the $last_days days $rows = Helpers::get_sold_last_days( $calc_products, "-$this->last_days days", $this->day ); if ( $rows ) { foreach ( $rows as $row ) { $this->calc_columns[ $row['PROD_ID'] ]['sold_last_days'] = $row['QTY']; } } } /** * Get the Inventory Log item quantity for a specific type of log * * @since 1.2.4 * * @type string $log_type Type of log * @type int $item_id Item (WC Product) ID to check * @type string $log_status Optional. Log status (completed or pending) * * @return int */ protected function get_log_item_qty( $log_type, $item_id, $log_status = 'pending' ) { $qty = 0; $log_ids = Helpers::get_logs($log_type, $log_status); if ( ! empty($log_ids) ) { global $wpdb; foreach ($log_ids as $log_id) { // Get the _qty meta for the specified product in the specified log $query = $wpdb->prepare( "SELECT SUM(meta_value) FROM {$wpdb->prefix}" . AtumOrderPostType::ORDER_ITEM_META_TABLE . " om JOIN {$wpdb->prefix}" . AtumOrderPostType::ORDER_ITEMS_TABLE . " oi ON om.order_item_id = oi.order_item_id WHERE order_id = %d AND order_item_type = %s AND meta_key = '_qty' AND om.order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}" . AtumOrderPostType::ORDER_ITEM_META_TABLE . " WHERE meta_key IN ('_product_id', '_variation_id') AND meta_value = %d )", $log_id, 'line_item', $item_id ); $qty += $wpdb->get_var($query); } } return absint( $qty ); } /** * Apply an extra filter to the current List Table query * * @since 1.2.8 * * @param \WP_Query $query */ public function do_extra_filter($query) { // Avoid calling the "pre_get_posts" again when querying orders if ( $query->query_vars['post_type'] != 'product' ) { return; } if ( ! empty($query->query_vars['post__in']) ) { return; } global $wpdb; $extra_filter = esc_attr( $_REQUEST['extra_filter'] ); $sorted = FALSE; $extra_filter_transient = Helpers::get_transient_identifier( $extra_filter, 'list_table_extra_filter' ); $filtered_products = Helpers::get_transient( $extra_filter_transient ); if ( empty($filtered_products) ) { switch ( $extra_filter ) { case 'inbound_stock': // Get all the products within pending Purchase Orders $sql = $wpdb->prepare( " SELECT product_id, SUM(qty) AS qty FROM ( SELECT MAX(CAST(omp.`meta_value` AS SIGNED)) AS product_id, omq.`meta_value` AS qty FROM `{$wpdb->prefix}" . AtumOrderPostType::ORDER_ITEMS_TABLE . "` oi LEFT JOIN `$wpdb->atum_order_itemmeta` omq ON omq.`order_item_id` = oi.`order_item_id` LEFT JOIN `$wpdb->atum_order_itemmeta` omp ON omp.`order_item_id` = oi.`order_item_id` WHERE `order_id` IN ( SELECT `ID` FROM `$wpdb->posts` WHERE `post_type` = %s AND `post_status` = 'atum_pending' ) AND omq.`meta_key` = '_qty' AND `order_item_type` = 'line_item' AND omp.`meta_key` IN ('_product_id', '_variation_id' ) GROUP BY oi.order_item_id ) AS T GROUP BY product_id ORDER by qty DESC;", PurchaseOrders::POST_TYPE ); $product_results = $wpdb->get_results( $sql, OBJECT_K ); if ( ! empty( $product_results ) ) { array_walk( $product_results, function ( &$item ) { $item = $item->qty; } ); $filtered_products = $product_results; $sorted = TRUE; } break; case 'stock_on_hold': $sql = " SELECT product_id, SUM(qty) AS qty FROM ( SELECT MAX(CAST(omp.`meta_value` AS SIGNED)) AS product_id, omq.`meta_value` AS qty FROM `{$wpdb->prefix}woocommerce_order_items` oi LEFT JOIN `$wpdb->order_itemmeta` omq ON omq.`order_item_id` = oi.`order_item_id` LEFT JOIN `$wpdb->order_itemmeta` omp ON omp.`order_item_id` = oi.`order_item_id` WHERE `order_id` IN ( SELECT `ID` FROM `$wpdb->posts` WHERE `post_type` = 'shop_order' AND `post_status` IN ('wc-pending', 'wc-on-hold') ) AND omq.`meta_key` = '_qty' AND `order_item_type` = 'line_item' AND (omp.`meta_key` IN ('_product_id', '_variation_id' )) GROUP BY oi.`order_item_id` ) AS T GROUP BY product_id ORDER BY qty DESC; "; $product_results = $wpdb->get_results( $sql, OBJECT_K ); if ( ! empty( $product_results ) ) { array_walk( $product_results, function ( &$item ) { $item = $item->qty; } ); $filtered_products = $product_results; $sorted = TRUE; } break; case 'reserved_stock': // Get all the products within 'Reserved Stock' logs $filtered_products = $this->get_log_products( 'reserved-stock', 'pending' ); break; case 'back_orders': // Avoid infinite loop of recalls remove_action( 'pre_get_posts', array( $this, 'do_extra_filter' ) ); // Get all the products that allow back orders $args = array( 'post_type' => 'product', 'posts_per_page' => - 1, 'meta_key' => '_backorders', 'meta_value' => 'yes' ); $products = get_posts( $args ); foreach ( $products as $product ) { $wc_product = wc_get_product( $product->ID ); $back_orders = 0; $stock_quantity = $wc_product->get_stock_quantity(); if ( $stock_quantity < $this->no_stock ) { $back_orders = $this->no_stock - $stock_quantity; } if ( $back_orders ) { $filtered_products[ $wc_product->get_id() ] = $back_orders; } } // Re-add the action add_action( 'pre_get_posts', array( $this, 'do_extra_filter' ) ); break; case 'sold_today': // Get the orders processed today $atts = array( 'status' => [ 'wc-processing', 'wc-completed' ], 'date_start' => 'today 00:00:00' ); $today_orders = Helpers::get_orders( $atts ); foreach ( $today_orders as $today_order ) { $products = $today_order->get_items(); foreach ( $products as $product ) { if ( isset( $filtered_products[ $product['product_id'] ] ) ) { $filtered_products[ $product['product_id'] ] += $product['qty']; } else { $filtered_products[ $product['product_id'] ] = $product['qty']; } } } break; case 'customer_returns': // Get all the products within 'Customer Returns' logs $filtered_products = $this->get_log_products( 'customer-returns', 'pending' ); break; case 'warehouse_damages': // Get all the products within 'Warehouse Damage' logs $filtered_products = $this->get_log_products( 'warehouse-damage', 'pending' ); break; case 'lost_in_post': // Get all the products within 'Lost in Post' logs $filtered_products = $this->get_log_products( 'lost-in-post', 'pending' ); break; } if ( ! empty($filtered_products) ) { // Order desc by quantity and get the ordered IDs if ( ! $sorted ) { arsort( $filtered_products ); } $filtered_products = array_keys( $filtered_products ); } // Set the transient to expire in 60 seconds Helpers::set_transient($extra_filter_transient, $filtered_products, 60); } // Filter the query posts by these IDs if ( ! empty($filtered_products) ) { $query->set( 'post__in', $filtered_products ); $query->set( 'orderby', 'post__in' ); } // Force no results ("-1" never will be a post ID) else { $query->set( 'post__in', array(-1) ); } } /** * Get all the products with total quantity within a specific type of Log * * @since 1.2.8 * * @param string $log_type * @param string $log_status * * @return array|bool */ protected function get_log_products($log_type, $log_status = '') { $log_types = array_keys( Log::get_types() ); if ( ! in_array($log_type, $log_types) ) { return FALSE; } $log_ids = Helpers::get_logs($log_type, $log_status); $products = array(); if ( ! empty($log_ids) ) { foreach ($log_ids as $log_id) { $log = new Log( $log_id ); $log_items = $log->get_items(); if ( ! empty($log_items) ) { foreach ( $log_items as $log_item ) { if ( ! is_a($log_item, '\Atum\InventoryLogs\Items\LogItemProduct') ) { continue; } $qty = $log_item->get_quantity(); $variation_id = $log_item->get_variation_id(); $product_id = $variation_id ?: $log_item->get_product_id(); if ( isset( $products[ $product_id ] ) ) { $products[ $product_id ] += $qty; } else { $products[ $product_id ] = $qty; } } } } } return $products; } }