uc_stock.module 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. <?php
  2. /**
  3. * @file
  4. * Allow ubercart products to have stock levels associated with their SKU
  5. *
  6. * uc_stock enables ubercart to manage stock for products. Store admins can set
  7. * the stock levels on a product edit page and a threshold for each SKU value
  8. * When that threshold is reached admins can be optionally notified about the
  9. * current stock level. Store admins can view all stock levels in the reports
  10. * section of Ubercart.
  11. *
  12. * Development sponsored by the Ubercart project. http://www.ubercart.org
  13. */
  14. /**
  15. * Implements hook_help().
  16. */
  17. function uc_stock_help($path, $arg) {
  18. switch ($path) {
  19. case 'node/%/edit/stock':
  20. return '<p>' . t('To keep track of stock for a particular product SKU, make sure it is marked as active and enter a stock value. When the stock level drops below the threshold value, you can be notified based on your stock settings.') . '</p>';
  21. case 'admin/store/reports/stock':
  22. case 'admin/store/reports/stock/threshold':
  23. return '<p>' . t('This is the list of product SKUs that are currently active. Stock levels below their threshold have highlighted rows. Toggle the checkbox below to alter which stock levels are shown.') . '</p>';
  24. }
  25. }
  26. /**
  27. * Implements hook_menu().
  28. */
  29. function uc_stock_menu() {
  30. $items = array();
  31. $items['admin/store/settings/stock'] = array(
  32. 'title' => 'Stock notifications',
  33. 'description' => 'Enable or disable stock level notifications.',
  34. 'page callback' => 'drupal_get_form',
  35. 'page arguments' => array('uc_stock_settings_form'),
  36. 'access arguments' => array('administer product stock'),
  37. 'file' => 'uc_stock.admin.inc',
  38. );
  39. if (module_exists('uc_reports')) {
  40. $items['admin/store/reports/stock'] = array(
  41. 'title' => 'Stock reports',
  42. 'description' => 'View reports for product stock.',
  43. 'page callback' => 'uc_stock_report',
  44. 'access arguments' => array('view reports'),
  45. 'file' => 'uc_stock.admin.inc',
  46. );
  47. }
  48. $items['node/%node/edit/stock'] = array(
  49. 'title' => 'Stock',
  50. 'page callback' => 'drupal_get_form',
  51. 'page arguments' => array('uc_stock_edit_form', 1),
  52. 'access callback' => 'uc_stock_product_access',
  53. 'access arguments' => array(1),
  54. 'weight' => 10,
  55. 'type' => MENU_LOCAL_TASK,
  56. 'file' => 'uc_stock.admin.inc',
  57. );
  58. return $items;
  59. }
  60. /**
  61. * Implements hook_admin_paths().
  62. */
  63. function uc_stock_admin_paths() {
  64. $paths = array(
  65. 'node/*/edit/stock' => TRUE,
  66. );
  67. return $paths;
  68. }
  69. /**
  70. * Access callback for node/%node/edit/stock.
  71. */
  72. function uc_stock_product_access($node) {
  73. if ($node->type == 'product_kit') {
  74. return FALSE;
  75. }
  76. return uc_product_is_product($node) && node_access('update', $node) && user_access('administer product stock');
  77. }
  78. /**
  79. * Implements hook_permission().
  80. */
  81. function uc_stock_permission() {
  82. return array(
  83. 'administer product stock' => array(
  84. 'title' => t('Administer product stock'),
  85. )
  86. );
  87. }
  88. /**
  89. * Implements hook_theme().
  90. */
  91. function uc_stock_theme() {
  92. return array(
  93. 'uc_stock_edit_form' => array(
  94. 'render element' => 'form',
  95. 'file' => 'uc_stock.admin.inc',
  96. ),
  97. );
  98. }
  99. /**
  100. * Implements hook_mail().
  101. */
  102. function uc_stock_mail($key, &$message, $params) {
  103. switch ($key) {
  104. case 'threshold':
  105. $message['subject'] = $params['subject'];
  106. $message['body'][] = $params['body'];
  107. break;
  108. }
  109. }
  110. /**
  111. * Implements hook_uc_message().
  112. */
  113. function uc_stock_uc_message() {
  114. $messages['uc_stock_threshold_notification_subject'] = t('[store:name]: Stock threshold limit reached');
  115. $messages['uc_stock_threshold_notification_message'] = t('This message has been sent to let you know that the stock level for "[node:title]" with SKU [uc_stock:model] has reached [uc_stock:level]. There may not be enough units in stock to fulfill order #[uc_order:link].');
  116. return $messages;
  117. }
  118. /**
  119. * Adjusts the product stock level by a set amount.
  120. *
  121. * @param $sku
  122. * The product SKU of the stock level to adjust.
  123. * @param $qty
  124. * The amount to add to or subtract from the stock level.
  125. * @param $check_active
  126. * If FALSE, don't check if stock tracking is active for this SKU.
  127. */
  128. function uc_stock_adjust($sku, $qty, $check_active = TRUE) {
  129. $stock = db_query("SELECT active, stock FROM {uc_product_stock} WHERE sku = :sku", array(':sku' => $sku))->fetchObject();
  130. if ($check_active) {
  131. if (!$stock || !$stock->active) {
  132. return;
  133. }
  134. }
  135. db_update('uc_product_stock')
  136. ->expression('stock', 'stock + :qty', array(':qty' => $qty))
  137. ->condition('sku', $sku)
  138. ->execute();
  139. module_invoke_all('uc_stock_adjusted', $sku, $stock->stock, $qty);
  140. }
  141. /**
  142. * Sets the product stock level.
  143. *
  144. * @param $sku
  145. * The product SKU of the stock level to set.
  146. * @param $qty
  147. * The number of items in stock.
  148. */
  149. function uc_stock_set($sku, $qty) {
  150. db_update('uc_product_stock')
  151. ->fields(array('stock' => $qty))
  152. ->condition('sku', $sku)
  153. ->execute();
  154. }
  155. /**
  156. * Gets the stock level of a particular product SKU.
  157. *
  158. * @param $sku
  159. * The Ubercart product SKU of the stock level to return.
  160. *
  161. * @return
  162. * The SKU's stock level, or FALSE if not active.
  163. */
  164. function uc_stock_level($sku) {
  165. $stock = db_query("SELECT active, stock FROM {uc_product_stock} WHERE sku = :sku", array(':sku' => $sku))->fetchObject();
  166. if ($stock && $stock->active) {
  167. return $stock->stock;
  168. }
  169. return FALSE;
  170. }
  171. /**
  172. * Checks if a SKU has an active stock record.
  173. *
  174. * @param $sku
  175. * The Ubercart product SKU to check
  176. *
  177. * @return
  178. * Boolean indicating whether or not the sku has an active stock record.
  179. */
  180. function uc_stock_is_active($sku) {
  181. return (bool) db_query("SELECT active FROM {uc_product_stock} WHERE sku = :sku", array(':sku' => $sku))->fetchField();
  182. }
  183. /**
  184. * Emails administrator regarding any stock level thresholds hit.
  185. *
  186. * @param $order
  187. * The order object that tripped the threshold limit.
  188. * @param $node
  189. * The node object that is associated with the SKU/model.
  190. * @param $stock
  191. * The stock level object that contains the stock level and SKU.
  192. *
  193. * @return
  194. * The result of drupal_mail().
  195. */
  196. function _uc_stock_send_mail($order, $node, $stock) {
  197. $account = user_load($order->uid);
  198. $token_filters = array('uc_order' => $order, 'user' => $account, 'uc_stock' => $stock, 'node' => $node);
  199. $to = variable_get('uc_stock_threshold_notification_recipients', uc_store_email());
  200. $to = explode(',', $to);
  201. $from = uc_store_email_from();
  202. $subject = variable_get('uc_stock_threshold_notification_subject', uc_get_message('uc_stock_threshold_notification_subject'));
  203. $subject = token_replace($subject, $token_filters);
  204. $body = variable_get('uc_stock_threshold_notification_message', uc_get_message('uc_stock_threshold_notification_message'));
  205. $body = token_replace($body, $token_filters);
  206. // Send to each recipient.
  207. foreach ($to as $email) {
  208. $sent = drupal_mail('uc_stock', 'threshold', $email, uc_store_mail_recipient_language($email), array('body' => $body, 'subject' => $subject, 'order' => $order, 'stock' => $stock), $from);
  209. if (!$sent['result']) {
  210. watchdog('uc_stock', 'Attempt to e-mail @email concerning stock level on sku @sku failed.', array('@email' => $email, '@sku' => $stock->sku), WATCHDOG_ERROR);
  211. }
  212. }
  213. }
  214. /**
  215. * Implements hook_views_api().
  216. */
  217. function uc_stock_views_api() {
  218. return array(
  219. 'api' => '2.0',
  220. 'path' => drupal_get_path('module', 'uc_stock') . '/views',
  221. );
  222. }
  223. /**
  224. * Decrement a product's stock.
  225. *
  226. * @param $product
  227. * The product whose stock is being adjusted.
  228. * @param $key
  229. * Internal, so this function can be used as a callback of array_walk().
  230. * @param $order
  231. * Order object.
  232. */
  233. function uc_stock_adjust_product_stock($product, $key, $order) {
  234. // Product has an active stock?
  235. if (!uc_stock_is_active($product->model)) {
  236. return;
  237. }
  238. // Do nothing if decrement quantity is 0.
  239. if ($product->qty == 0) {
  240. return;
  241. }
  242. // Adjust the product's stock.
  243. uc_stock_adjust($product->model, -$product->qty);
  244. // Load the new stock record.
  245. $stock = db_query("SELECT * FROM {uc_product_stock} WHERE sku = :sku", array(':sku' => $product->model))->fetchObject();
  246. // Should we notify?
  247. if (variable_get('uc_stock_threshold_notification', FALSE) && $stock->stock <= $stock->threshold) {
  248. $node = node_load($product->nid);
  249. _uc_stock_send_mail($order, $node, $stock);
  250. }
  251. // Save a comment about the stock level.
  252. uc_order_comment_save($order->order_id, 0, t('The stock level for %model_name has been !action to !qty.', array('%model_name' => $product->model, '!qty' => $stock->stock, '!action' => (-$product->qty <= 0) ? t('decreased') : t('increased') )));
  253. }
  254. /**
  255. * Increment a product's stock.
  256. */
  257. function uc_stock_increment_product_stock($product, $key, $order) {
  258. $product->qty = -$product->qty;
  259. return uc_stock_adjust_product_stock($product, $key, $order);
  260. }