Automattic\WooCommerce\Admin\API\Reports\Products

DataStore::sync_order_productspublic staticWC 3.5.0

Create or update an entry in the wc_admin_order_product_lookup table for an order.

Метод класса: DataStore{}

Хуки из метода

Возвращает

int|true|false. Returns -1 if order won't be processed, or a boolean indicating processing success.

Использование

$result = DataStore::sync_order_products( $order_id );
$order_id(int) (обязательный)
Order ID.

Список изменений

С версии 3.5.0 Введена.

Код DataStore::sync_order_products() WC 10.5.2

public static function sync_order_products( $order_id ) {
	global $wpdb;

	$order = wc_get_order( $order_id );
	if ( ! $order ) {
		return -1;
	}

	$table_name     = self::get_db_table_name();
	$existing_items = $wpdb->get_col(
		$wpdb->prepare(
			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			"SELECT order_item_id FROM {$table_name} WHERE order_id = %d",
			$order_id
		)
	);
	$existing_items = array_flip( $existing_items );
	$order_items    = $order->get_items();
	$num_updated    = 0;
	$decimals       = wc_get_price_decimals();
	$round_tax      = 'no' === get_option( 'woocommerce_tax_round_at_subtotal' );

	$is_full_refund_without_line_items = false;
	$partial_refund_product_revenue    = array();
	$refund_type                       = $order->get_meta( '_refund_type' );
	$uses_new_full_refund_data         = OrderUtil::uses_new_full_refund_data();

	$parent_order = null;

	// When changing the order status to "Refunded", the refund order's type will be full refund, and the order items will be empty.
	// We need to get the parent order items, and exclude the items that are already being partially refunded.
	if (
		'shop_order_refund' === $order->get_type() &&
		'full' === $refund_type &&
		empty( $order_items ) &&
		$uses_new_full_refund_data
	) {
		$is_full_refund_without_line_items = true;

		$parent_order_id = $order->get_parent_id();
		$parent_order    = wc_get_order( $parent_order_id );
		$order_items     = $parent_order->get_items();

		// Get the partially refunded product and variation IDs along with their sum of product_net_revenue from the parent order.
		$partial_refund_products = $wpdb->get_results(
			$wpdb->prepare(
				"
					SELECT
						product_lookup.product_id,
						product_lookup.variation_id,
						SUM( product_lookup.product_net_revenue ) AS product_net_revenue
					FROM %i AS product_lookup
					INNER JOIN {$wpdb->prefix}wc_order_stats AS order_stats
						ON order_stats.order_id = product_lookup.order_id
					WHERE 1 = 1
						AND order_stats.parent_id = %d
						AND product_lookup.product_net_revenue < 0
					GROUP BY product_lookup.product_id, product_lookup.variation_id
				",
				$table_name,
				$parent_order_id
			)
		);

		/**
		 * Create a lookup table for partially refunded products.
		 * E.g. [
		 *   '1' => -20,
		 *   '2' => -40,
		 *   '51' => -10,
		 *   '52' => -30,
		 * ]
		 */
		foreach ( $partial_refund_products as $product ) {
			$id                                    = $product->variation_id ? $product->variation_id : $product->product_id;
			$partial_refund_product_revenue[ $id ] = (float) $product->product_net_revenue;
		}
	}

	foreach ( $order_items as $order_item ) {
		$order_item_id = $order_item->get_id();
		unset( $existing_items[ $order_item_id ] );
		$product_qty         = $order_item->get_quantity( 'edit' );
		$product_id          = $order_item->get_product_id( 'edit' );
		$variation_id        = $order_item->get_variation_id( 'edit' );
		$shipping_amount     = $order->get_item_shipping_amount( $order_item );
		$shipping_tax_amount = $order->get_item_shipping_tax_amount( $order_item );
		$coupon_amount       = $order->get_item_coupon_amount( $order_item );
		$tax_amount          = $order->get_item_cart_tax_amount( $order_item );
		$net_revenue         = round( $order_item->get_total( 'edit' ), $decimals );

		// If the order is a full refund and there is no order items. The order item here is the parent order item.
		if ( $is_full_refund_without_line_items ) {
			$id             = $variation_id ? $variation_id : $product_id;
			$partial_refund = $partial_refund_product_revenue[ $id ] ?? 0;
			// If a single line item was refunded 60% then fully refunded after, we need store the difference in the product lookup table.
			// E.g. A product costs $100, it was previously partially refunded $60, then fully refunded $40.
			// So it will be -abs( 100 + (-60) ) = -40.
			$net_revenue = -abs( $net_revenue + $partial_refund );

			// Skip items that have already been fully refunded (single or multiple partial refunds).
			if ( 0.0 === $net_revenue ) {
				continue;
			}

			$product_qty = -abs( $product_qty );

			// Set coupon amount to 0 for full refunds without line items.
			$coupon_amount = 0;

			if ( $parent_order ) {
				$remaining_refund_items = $parent_order->get_remaining_refund_items();

				// Calculate the shipping amount to refund from the parent order.
				$total_shipping_refunded  = $parent_order->get_total_shipping_refunded();
				$shipping_total           = (float) $parent_order->get_shipping_total();
				$total_shipping_to_refund = $shipping_total - $total_shipping_refunded;

				if ( $total_shipping_to_refund > 0 ) {
					$shipping_amount = -abs( $parent_order->get_item_shipping_amount( $order_item, $remaining_refund_items, $total_shipping_to_refund ) );
				}

				// Calculate the shipping tax amount to refund from the parent order.
				$shipping_tax                 = (float) $parent_order->get_shipping_tax();
				$total_shipping_tax_refunded  = $parent_order->get_total_shipping_tax_refunded();
				$total_shipping_tax_to_refund = $shipping_tax - $total_shipping_tax_refunded;

				if ( $total_shipping_tax_to_refund > 0 ) {
					$shipping_tax_amount = -abs( $parent_order->get_item_shipping_tax_amount( $order_item, $remaining_refund_items, $total_shipping_tax_to_refund ) );
				}

				// Calculate cart tax amount of the item from the parent order.
				$tax_amount = -abs( $parent_order->get_item_cart_tax_amount( $order_item ) );
			}
		}

		$is_refund = $net_revenue < 0;

		// Skip line items without changes to product quantity.
		if ( ! $product_qty && ! $is_refund ) {
			++$num_updated;
			continue;
		}

		if ( $round_tax ) {
			$tax_amount = round( $tax_amount, $decimals );
		}

		$result = $wpdb->replace(
			self::get_db_table_name(),
			array(
				'order_item_id'         => $order_item_id,
				'order_id'              => $order->get_id(),
				'product_id'            => $product_id,
				'variation_id'          => $variation_id,
				'customer_id'           => $order->get_report_customer_id(),
				'product_qty'           => $product_qty,
				'product_net_revenue'   => $net_revenue,
				'date_created'          => $order->get_date_created( 'edit' )->date( TimeInterval::$sql_datetime_format ),
				'coupon_amount'         => $coupon_amount,
				'tax_amount'            => $tax_amount,
				'shipping_amount'       => $shipping_amount,
				'shipping_tax_amount'   => $shipping_tax_amount,
				// @todo Can this be incorrect if modified by filters?
				'product_gross_revenue' => $net_revenue + $tax_amount + $shipping_amount + $shipping_tax_amount,
			),
			array(
				'%d', // order_item_id.
				'%d', // order_id.
				'%d', // product_id.
				'%d', // variation_id.
				'%d', // customer_id.
				'%d', // product_qty.
				'%f', // product_net_revenue.
				'%s', // date_created.
				'%f', // coupon_amount.
				'%f', // tax_amount.
				'%f', // shipping_amount.
				'%f', // shipping_tax_amount.
				'%f', // product_gross_revenue.
			)
		); // WPCS: cache ok, DB call ok, unprepared SQL ok.

		/**
		 * Fires when product's reports are updated.
		 *
		 * @param int $order_item_id Order Item ID.
		 * @param int $order_id      Order ID.
		 */
		do_action( 'woocommerce_analytics_update_product', $order_item_id, $order->get_id() );

		// Sum the rows affected. Using REPLACE can affect 2 rows if the row already exists.
		$num_updated += 2 === intval( $result ) ? 1 : intval( $result );
	}

	if ( ! empty( $existing_items ) ) {
		$existing_items = array_flip( $existing_items );
		$format         = array_fill( 0, count( $existing_items ), '%d' );
		$format         = implode( ',', $format );
		array_unshift( $existing_items, $order_id );
		$wpdb->query(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				"DELETE FROM {$table_name} WHERE order_id = %d AND order_item_id in ({$format})",
				$existing_items
			)
		);
	}

	return ( count( $order_items ) === $num_updated );
}