'admin/store/reports/stock',
'title' => t('Stock reports'),
'description' => t('View reports for product stock.'),
'callback' => 'uc_stock_report',
'access' => user_access('view reports'),
'type' => MENU_NORMAL_ITEM,
);
$items[] = array(
'path' => 'admin/store/settings/stock',
'title' => t('Stock settings'),
'description' => t('View the stock settings.'),
'callback' => 'drupal_get_form',
'callback arguments' => array('uc_stock_settings_form'),
'access' => user_access('administer products'),
'type' => MENU_NORMAL_ITEM,
);
}
else {
$items[] = array(
'path' => 'node/'. arg(1) .'/edit/stock',
'title' => t('Stock'),
'callback' => 'drupal_get_form',
'callback arguments' => array('uc_stock_edit_form', arg(1)),
'access' => user_access('administer products'),
'weight' => 10,
'type' => MENU_LOCAL_TASK,
);
}
return $items;
}
/*******************************************************************************
* Ubercart Hooks *
******************************************************************************/
/**
* Implementation of hook_token_list().
*/
function uc_stock_token_list($type = 'all') {
if ($type == 'stock' || $type == 'ubercart' || $type == 'all') {
$tokens['stock']['stock-level'] = t('The current stock level');
$tokens['stock']['stock-model'] = t('The model or SKU of the stock level');
$tokens['stock']['stock-threshold'] = t('The threshold or warning limit of the stock level');
}
return $tokens;
}
/**
* Implementation of hook_token_values().
*/
function uc_stock_token_values($type, $object = NULL) {
switch ($type) {
case 'stock':
$values['stock-level'] = $object->stock;
$values['stock-model'] = $object->sku;
$values['stock-threshold'] = $object->threshold;
break;
}
return $values;
}
/**
* Implementation of hook_uc_message().
*/
function uc_stock_uc_message() {
$messages['uc_stock_threshold_notification_subject'] = t('[store-name]: Stock threshold limit reached');
$messages['uc_stock_threshold_notification_message'] = t("This message has been sent to let you know that the stock level for the model [stock-model] has reached [stock-level]. There may not be enough units in stock to fulfill order #[order-link].");
return $messages;
}
/*******************************************************************************
* Callback Functions, Forms, and Tables *
******************************************************************************/
// Form builder for product stock edit form.
function uc_stock_edit_form($nid) {
$node = node_load($nid);
drupal_set_title(check_plain($node->title));
$form = array();
$skus = uc_stock_skus($nid);
if (!$skus) {
drupal_not_found();
}
$form['stock'] = array('#tree' => TRUE);
foreach ((array) $skus as $id => $sku) {
$stock = db_fetch_array(db_query("SELECT * FROM {uc_product_stock} WHERE sku = '%s'", $sku));
$form['stock'][$id]['sku'] = array(
'#type' => 'value',
'#value' => $sku,
);
// Checkbox to mark this as active.
$form['stock'][$id]['active'] = array(
'#type' => 'checkbox',
'#default_value' => !empty($stock['active']) ? $stock['active'] : 0,
);
// Sanitized version of the SKU for display.
$form['stock'][$id]['display_sku'] = array(
'#value' => check_plain($sku),
);
// Textfield for entering the stock level.
$form['stock'][$id]['stock'] = array(
'#type' => 'textfield',
'#default_value' => !empty($stock['stock']) ? $stock['stock'] : 0,
'#maxlength' => 9,
'#size' => 9,
);
// Textfield for entering the threshold level.
$form['stock'][$id]['threshold'] = array(
'#type' => 'textfield',
'#default_value' => !empty($stock['threshold']) ? $stock['threshold'] : 0,
'#maxlength' => 9,
'#size' => 9,
);
}
$form['nid'] = array(
'#type' => 'value',
'#value' => $nid,
);
$form['save'] = array(
'#type' => 'submit',
'#value' => t('Save changes'),
);
return $form;
}
function theme_uc_stock_edit_form($form) {
$header = array(
array('data' => t('Active')),
array('data' => t('SKU')),
array('data' => t('Stock')),
array('data' => t('Threshold')),
);
foreach (element_children($form['stock']) as $id) {
$rows[] = array(
array('data' => drupal_render($form['stock'][$id]['active'])),
array('data' => drupal_render($form['stock'][$id]['display_sku'])),
array('data' => drupal_render($form['stock'][$id]['stock'])),
array('data' => drupal_render($form['stock'][$id]['threshold'])),
);
}
return theme('table', $header, $rows) . drupal_render($form);
}
function uc_stock_edit_form_submit($form_id, $form_values) {
foreach (element_children($form_values['stock']) as $id) {
db_query("UPDATE {uc_product_stock} SET active = %d, stock = %d, threshold = %d WHERE sku = '%s'",
$form_values['stock'][$id]['active'] ? 1 : 0, intval($form_values['stock'][$id]['stock']),
intval($form_values['stock'][$id]['threshold']), $form_values['stock'][$id]['sku']);
if (!db_affected_rows()) {
db_query("INSERT INTO {uc_product_stock} (sku, nid, active, stock, threshold) VALUES ('%s', %d, %d, %d, %d)",
$form_values['stock'][$id]['sku'], $form_values['nid'], $form_values['stock'][$id]['active'] ? 1 : 0,
intval($form_values['stock'][$id]['stock']), intval($form_values['stock'][$id]['threshold']));
}
}
drupal_set_message(t('Stock settings saved.'));
}
// Displays a stock report for products with stock tracking enabled.
function uc_stock_report() {
drupal_add_css(drupal_get_path('module', 'uc_stock') .'/uc_stock.css');
$page_size = (!is_null($_GET['nopage'])) ? UC_REPORTS_MAX_RECORDS : variable_get('uc_reports_table_size', 30);
$csv_rows = array();
$rows = array();
$header = array(
array('data' => t('SKU'), 'field' => 'sku', 'sort' => 'asc'),
array('data' => t('Product'), 'field' => 'title'),
array('data' => t('Stock'), 'field' => 'stock'),
array('data' => t('Threshold'), 'field' => 'threshold'),
array('data' => t('Operations')),
);
$csv_rows[] = array(t('SKU'), t('Product'), t('Stock'), t('Threshold'));
$sql = "SELECT s.nid, sku, title, stock, threshold FROM {uc_product_stock} as s LEFT JOIN {node} as n ON s.nid = n.nid WHERE active = 1 AND title <> ''";
if (arg(4) == 'threshold') {
$sql .= ' AND threshold >= stock';
}
$result = pager_query($sql . tablesort_sql($header), $page_size, 0, NULL);
while ($stock = db_fetch_object($result)) {
$op = array();
if (user_access('administer products')) {
$op[] = l(t('edit'), 'node/'. $stock->nid .'/edit/stock', array(), 'destination=admin/store/reports/stock');
}
// Add the data to a table row for display.
$rows[] = array(
'data' => array(
array('data' => $stock->sku),
array('data' => l($stock->title, 'node/'. $stock->nid)),
array('data' => $stock->stock),
array('data' => $stock->threshold),
array('data' => implode(' ', $op)),
),
'class' => ($stock->threshold >= $stock->stock) ? 'uc-stock-below-threshold' : 'uc-stock-above-threshold',
);
// Add the data to the CSV contents for export.
$csv_rows[] = array($stock->sku, $stock->title, $stock->stock, $stock->threshold);
}
$csv_data = uc_reports_store_csv('uc_stock', $csv_rows);
$output = drupal_get_form('uc_stock_report_form')
. theme('table', $header, $rows, array('width' => '100%', 'class' => 'uc-stock-table'))
. theme_pager(NULL, $page_size);
$output .= '
'. l(t('Export to CSV file'), 'admin/store/reports/getcsv/'. $csv_data['report'] .'/'. $csv_data['user']) .' '. ((!is_null($_GET['nopage'])) ? l(t('Show paged records'), 'admin/store/reports/stock') : l(t('Show all records'), 'admin/store/reports/stock', array(), 'nopage=1')) .'
';
return $output;
}
// Form builder for stock report threshold filter.
function uc_stock_report_form() {
$form['threshold'] = array(
'#type' => 'checkbox',
'#title' => t('Only show SKUs that are below their threshold.'),
'#default_value' => arg(4) == 'threshold' ? TRUE : FALSE,
'#attributes' => array('onchange' => 'this.form.submit();'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
'#attributes' => array('style' => "display:none;"),
);
return $form;
}
function uc_stock_report_form_submit($form_id, $form_values) {
if ($form_values['threshold']) {
drupal_goto('admin/store/reports/stock/threshold');
}
else {
drupal_goto('admin/store/reports/stock');
}
}
// Form builder for stock settings form.
function uc_stock_settings_form() {
$form['uc_stock_threshold_notification'] = array(
'#type' => 'checkbox',
'#title' => t('Send email notification when stock level reaches its threshold value'),
'#default_value' => variable_get('uc_stock_threshold_notification', FALSE),
);
$form['uc_stock_threshold_notification_recipients'] = array(
'#type' => 'textfield',
'#title' => t('Notification recipients'),
'#default_value' => variable_get('uc_stock_threshold_notification_recipients', variable_get('uc_store_email', ini_get('sendmail_from'))),
'#description' => t('The list of comma seperated email addresses that will receive the notification.'),
);
$form['uc_stock_threshold_notification_subject'] = array(
'#type' => 'textfield',
'#title' => t('Message subject'),
'#default_value' => variable_get('uc_stock_threshold_notification_subject', uc_get_message('uc_stock_threshold_notification_subject')),
);
$form['uc_stock_threshold_notification_message'] = array(
'#type' => 'textarea',
'#title' => t('Message text'),
'#default_value' => variable_get('uc_stock_threshold_notification_message', uc_get_message('uc_stock_threshold_notification_message')),
'#description' => t('The message the user receives when the stock level reaches its threshold value (uses global, order, and stock tokens).', array('!token-help-page' => url('admin/store/help/tokens'))),
'#rows' => 10,
);
$form['uc_stock_threshold_notification_format'] = filter_form(variable_get('uc_stock_threshold_notification_format', FILTER_FORMAT_DEFAULT), NULL, array('uc_stock_threshold_notification_format'));
return system_settings_form($form);
}
/*******************************************************************************
* Module and Helper Functions *
******************************************************************************/
/**
* Adjust the product stock level by a set amount.
*
* @param $sku
* The product SKU of the stock level to adjust.
* @param $qty
* The amount to add to or subtract from the stock level.
*/
function uc_stock_adjust($sku, $qty) {
db_query("UPDATE {uc_product_stock} SET stock = stock + %d WHERE sku = '%s'", $qty, $sku);
}
/**
* Get the stock level of a particular product SKU
* @param $sku
* The Ubercart product SKU of the stock level to return.
* @return:
* The associated stock level with the particular SKU or FALSE if not active.
*/
function uc_stock_level($sku) {
return db_result(db_query("SELECT stock FROM {uc_product_stock} WHERE sku = '%s' AND active = 1", $sku));
}
/**
* Gets the SKUs associated with a particular product.
*
* @param $nid
* The node id of the ubercart product
* @return:
* An array containing all the SKUs that are associated with the product or
* FALSE if the node ID isn't an Ubercart product.
*/
function uc_stock_skus($nid) {
$node = node_load($nid);
if (is_null($node->model)) {
return FALSE;
}
$skus = array($node->model);
if (module_exists('uc_attribute')) {
$models = db_query("SELECT model FROM {uc_product_adjustments} WHERE nid = %d", $node->nid);
while ($model = db_fetch_object($models)) {
if (!in_array($model->model, $skus)) {
$skus[] = $model->model;
}
}
}
return $skus;
}
/**
* Emails administrator regarding any stock level thresholds hit.
*
* @param $order
* The order object that tripped the threshold limit.
* @param $product
* The product object that is associated with the SKU.
* @param $stock
* The stock level object that contains the stock level and SKU.
* @return
* The result of drupal_mail().
*/
function _uc_stock_send_mail($order, $stock) {
$token_filters = array('global' => NULL, 'order' => $order, 'stock' => $stock);
$to = variable_get('uc_stock_threshold_notification_recipients', variable_get('uc_store_email', ini_get('sendmail_from')));
$from = uc_store_email_from();
$subject = variable_get('uc_stock_threshold_notification_subject', uc_get_message('uc_stock_threshold_notification_subject'));
$subject = token_replace_multiple($subject, $token_filters);
$body = variable_get('uc_stock_threshold_notification_message', uc_get_message('uc_stock_threshold_notification_message'));
$body = token_replace_multiple($body, $token_filters);
return drupal_mail('uc_stock', $to, $subject, $body, $from);
}
/**
* Implementation of hook_views_tables().
*/
function uc_stock_views_tables() {
$tables['uc_product_stock'] = array(
'name' => 'uc_product_stock',
'join' => array(
'left' => array(
'table' => 'node',
'field' => 'nid'
),
'right' => array(
'field' => 'nid',
),
),
'fields' => array(
'sku' => array(
'name' => t('Stock: SKU'),
'help' => t('The model or SKU of the stock level'),
'sortable' => TRUE,
),
'active' => array(
'name' => t('Stock: Active'),
'help' => t('Whether or not the stock level is currently being tracked'),
'handler' => 'uc_stock_views_handler_active',
'sortable' => TRUE,
),
'stock' => array(
'name' => t('Stock: Stock Level'),
'help' => t('The current stock level'),
'sortable' => TRUE,
),
'threshold' => array(
'name' => t('Stock: Threshold'),
'help' => t('The threshold or warning limit of the stock level'),
'sortable' => TRUE,
),
),
'sorts' => array(
'sku' => array(
'name' => t('Stock: SKU'),
),
'stock' => array(
'name' => t('Stock: Stock Level'),
'help' => t('The current stock level'),
),
'threshold' => array(
'name' => t('Stock: Threshold'),
'help' => t('The threshold or warning limit of the stock level'),
),
),
'filters' => array(
'sku' => array(
'name' => 'Stock: SKU',
'operator' => 'views_handler_operator_like',
'handler' => 'views_handler_filter_like',
'help' => t('Filter the node based on stock SKU.'),
),
'stock' => array(
'name' => 'Stock: Stock Level',
'operator' => 'views_handler_operator_gtlt',
'help' => t('Filter the node based on stock level.'),
),
'threshold' => array(
'name' => 'Stock: Threshold',
'operator' => 'views_handler_operator_gtlt',
'help' => t('Filter the node based on threshold level.'),
),
'is_active' => array(
'name' => 'Stock: Is Active',
'operator' => array('=' => 'Equals'),
'list' => 'views_handler_operator_yesno',
'list-type' => 'select',
'handler' => 'uc_stock_views_handler_filter_is_active',
'help' => t('Filter the data based on whether or not stock tracking is active for the SKU.'),
),
'below_threshold' => array(
'name' => 'Stock: Is Below Threshold',
'operator' => array('=' => 'Equals'),
'list' => 'views_handler_operator_yesno',
'list-type' => 'select',
'handler' => 'uc_stock_views_handler_filter_below_threshold',
'help' => t('Filter the node based on whether it stock level is below the threshold for the SKU.'),
),
),
);
return $tables;
}
function uc_stock_views_handler_active($fieldinfo, $fielddata, $value, $data) {
return $value ? t('Active') : t('Inactive');
}
function uc_stock_views_handler_filter_below_threshold($op, $filter, $filterinfo, &$query) {
if ($op == 'handler') {
if ($filter['value'] == 0) {
$operator = '>=';
}
else {
$operator = '<';
}
$query->add_where('uc_product_stock.stock '. $operator .' uc_product_stock.threshold');
}
}
function uc_stock_views_handler_filter_is_active($op, $filter, $filterinfo, &$query) {
if ($op == 'handler') {
if ($filter['value'] == 1) {
$active = '1';
}
else {
$active = '0';
}
$query->ensure_table('uc_product_stock');
$query->add_where('uc_product_stock.active = '. $active);
}
}