You are here

commerce_reports_stock.module in Commerce Reporting 7.4

Same filename and directory in other branches
  1. 7.3 modules/stock/commerce_reports_stock.module

This module provides advanced stock reporting for Drupal Commerce.

File

modules/stock/commerce_reports_stock.module
View source
<?php

/**
 * @file
 * This module provides advanced stock reporting for Drupal Commerce.
 */

/**
 * Implements hook_views_api().
 */
function commerce_reports_stock_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'commerce_reports_stock') . '/includes/views',
  );
}

/**
 * Implements hook_views_pre_execute().
 */
function commerce_reports_stock_views_pre_execute(&$view) {
  if ($view->name == 'commerce_reports_stock') {

    // Prime the stock reports data set into static cache.
    _commerce_reports_stock_calculate_dataset();
  }
}

/**
 * Calculate all the stock reports data.
 *
 * Returns data for all products, or specific item.
 *
 * @todo : This is dirty and heavy. Can we trim this up / streamline?
 *          Our view is technically querying all products twice :/
 *
 * @param null|string $sku
 *    Optional SKU to return data for.
 * @param null|string $key
 *    Optional data key to return.
 * @param bool $reset
 *    Boolean to reset cache or not.
 *
 * @return mixed
 *    Returns an array of data, or string if $key was set.
 */
function _commerce_reports_stock_calculate_dataset($sku = NULL, $key = NULL, $reset = FALSE) {
  $data =& drupal_static(__FUNCTION__);

  // If there is no static cache for dataset yet or a reset was specified...
  if (!isset($data) || $reset) {
    $products = _commerce_reports_stock_get_stock_enabled_products();
    $start = variable_get('commerce_reports_stock_historyperiod', '3 months ago');
    $start = strtotime($start);
    $weekly_sales = _commerce_reports_stock_api_sales('W', $start);
    $monthly_sales = _commerce_reports_stock_api_sales('M', $start);
    $data = array();
    $lifetimes = array();
    $in_stock = array();

    // Go through each product and make calculations.
    foreach ($products as $product) {

      /** @var EntityDrupalWrapper $product_wrapper */
      $product_wrapper = entity_metadata_wrapper('commerce_product', $product);
      $sku = (string) $product_wrapper->sku
        ->value();

      // Provide a default value.
      $stock = 0;

      // @todo: This assumes Commerce Simple Stock was utilized.
      if ($product_wrapper
        ->__isset('commerce_stock')) {
        $stock = (int) $product_wrapper->commerce_stock
          ->value();
      }

      // Compute the number of weeks since start.
      $now = new DateTime();

      // Check if that product is more recent than the history period.
      if ($start < $product_wrapper->created
        ->value()) {
        $start_datetime = new DateTime();
        $start_datetime
          ->setTimestamp($product_wrapper->created
          ->value());
      }
      else {
        $start_datetime = new DateTime(variable_get('commerce_reports_stock_historyperiod', '3 months ago'));
      }
      $number_of_weeks = max($now
        ->diff($start_datetime)->days / 7, 1);
      $weekly_burn = 0;
      if (isset($weekly_sales[$sku])) {
        $weekly_burn = _commerce_reports_stock_calculate_average_sales($weekly_sales[$sku], $number_of_weeks);
      }

      // Compute the number of month since start.
      $number_of_month = max($now
        ->diff($start_datetime)->m, 1);
      $monthly_burn = 0;
      if (isset($monthly_sales[$sku])) {
        $monthly_burn = _commerce_reports_stock_calculate_average_sales($monthly_sales[$sku], $number_of_month);
      }
      $lifetimes[$sku] = _commerce_reports_stock_calculate_lifetime($stock, $weekly_burn);
      $in_stock[$sku] = (bool) $stock;
      $data[$sku] = array(
        'sku' => $sku,
        'stock' => $stock,
        'weeklysales' => sprintf('%0.1f', $weekly_burn),
        'monthlysales' => sprintf('%0.1f', $monthly_burn),
        'lifetime' => $lifetimes[$sku],
      );
    }
    array_multisort($in_stock, SORT_NUMERIC, $lifetimes, SORT_NUMERIC, $data);
  }

  // Check to see if we should return specific data, or whole array.
  if ($key !== NULL && $sku !== NULL) {
    if (isset($data[$sku]) && isset($data[$sku][$key])) {
      return $data[$sku][$key];
    }
    else {
      return $key == 'lifetime' ? 1000 : 0;
    }

    // If we have a key return that, else the whole sku entry.
  }
  elseif ($sku !== NULL) {
    if (isset($data[$sku])) {
      return $data[$sku];
    }
    else {
      return array(
        'sku' => $sku,
        'stock' => $stock,
        'weeklysales' => 0,
        'monthlysales' => 0,
        'lifetime' => 1000,
      );
    }
  }
  else {
    return $data;
  }
}

/**
 * Calculate the stock lifetime.
 *
 * @param int $stock
 *    The current stock count.
 * @param int $weekly_burn
 *    The weekly burn.
 *
 * @return int
 *    The length of the current stock's lifetime.
 */
function _commerce_reports_stock_calculate_lifetime($stock, $weekly_burn) {

  // Why does this mean it is 1000?
  if ($weekly_burn === 0) {
    return 1000;
  }
  return (int) ($stock / ($weekly_burn / 7));
}

/**
 * Calculate the average sales.
 *
 * This is simplistic, we could do more.
 *
 * @param array $sales
 *    Array of the number of sales.
 * @param int $period
 *    The period length.
 *
 * @return float|int
 *    The average amount of sales.
 */
function _commerce_reports_stock_calculate_average_sales(array $sales, $period) {
  return empty($sales) ? 0 : array_sum($sales) / $period;
}

/**
 * Get the stock enabled products.
 *
 * @param bool $reset
 *    Boolean to reset cache.
 *
 * @return array
 *    Array of products, keyed by SKU.
 */
function _commerce_reports_stock_get_stock_enabled_products($reset = FALSE) {
  $product_list =& drupal_static(__FUNCTION__);

  // If there is no static cache for dataset yet or a reset was specified...
  if (!isset($product_list) || $reset) {
    $product_list = array();

    // Grab all products that have stock enabled.
    $product_query = new EntityFieldQuery();
    $product_query
      ->entityCondition('entity_type', 'commerce_product')
      ->propertyCondition('status', 1, '=')
      ->fieldCondition('commerce_stock', 'value', 'NULL', '!=');
    $products_result = $product_query
      ->execute();
    if (!empty($products_result)) {
      $products_result = reset($products_result);
      $products = entity_load('commerce_product', array_keys($products_result));

      // Key the list by SKU.
      // @todo: Is there a way to do this already in core Commerce?
      foreach ($products as $pid => $product) {
        $product_list[$product->sku] = $product;
      }
    }
  }
  return $product_list;
}

/**
 * Group sales by year, month, day or week.
 *
 * @param string $interval
 *    Interval to group sales by. Valid options: D, W, M, Y.
 * @param int $start
 *    Timestamp to check creation after.
 *
 * @return array
 *    An array of products.
 */
function _commerce_reports_stock_api_sales($interval = 'D', $start = 0) {
  $formats = array(
    'D' => '%Y-%m-%d',
    'W' => '%Y-%u',
    'M' => '%Y-%m',
    'Y' => '%Y',
  );
  $format = $formats[$interval];
  $query = sprintf("\n       SELECT DATE_FORMAT(FROM_UNIXTIME(o.created), '%s') AS date,\n              line_item_label AS sku,\n              SUM(quantity) AS sales\n         FROM commerce_line_item li\n    LEFT JOIN commerce_order o\n           ON li.order_id = o.order_id\n        WHERE o.status = 'completed'\n          AND li.type = 'product'\n          AND o.created >= :created\n     GROUP BY li.line_item_label, date", $format);
  $res = db_query($query, array(
    ':created' => $start,
  ));
  $data = array();
  foreach ($res as $row) {
    $data[$row->sku][$row->date] = (int) $row->sales;
  }
  return $data;
}

Functions

Namesort descending Description
commerce_reports_stock_views_api Implements hook_views_api().
commerce_reports_stock_views_pre_execute Implements hook_views_pre_execute().
_commerce_reports_stock_api_sales Group sales by year, month, day or week.
_commerce_reports_stock_calculate_average_sales Calculate the average sales.
_commerce_reports_stock_calculate_dataset Calculate all the stock reports data.
_commerce_reports_stock_calculate_lifetime Calculate the stock lifetime.
_commerce_reports_stock_get_stock_enabled_products Get the stock enabled products.