/[drupal]/contributions/modules/ubercart/uc_stock/uc_stock.module
ViewVC logotype

Contents of /contributions/modules/ubercart/uc_stock/uc_stock.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.12 - (show annotations) (download) (as text)
Thu Jul 10 12:41:05 2008 UTC (16 months, 2 weeks ago) by islandusurper
Branch: MAIN
CVS Tags: DRUPAL-5--1-1, DRUPAL-5--1-2, HEAD
Branch point for: DRUPAL-5, DRUPAL-6--2
Changes since 1.11: +0 -0 lines
File MIME type: text/x-php
Begin the Ubercart 6.x-2.x branch.
1 <?php
2 // $Id$
3
4 /**
5 * @file
6 * Allow ubercart products to have stock levels associated with their SKU
7 *
8 * uc_stock enables ubercart to manage stock for products. Store admins can set
9 * the stock levels on a product edit page and a threshold for each SKU value
10 * When that threshold is reached admins can be optionally notified about the
11 * current stock level. Store admins can view all stock levels in the reports
12 * section of Ubercart.
13 *
14 * Development sponsored by the Ubercart project. http://www.ubercart.org
15 */
16
17 require_once('uc_stock_workflow.inc');
18
19 /*******************************************************************************
20 * Hook Functions (Drupal) *
21 ******************************************************************************/
22
23 /**
24 * Implementation of hook_help().
25 */
26 function uc_stock_help($section) {
27 switch ($section) {
28 case 'node/'. arg(1) .'/edit/stock':
29 return 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.');
30 case 'admin/store/reports/stock':
31 case 'admin/store/reports/stock/threshold':
32 return 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.');
33 }
34 }
35
36 /**
37 * Implementation of hook_menu().
38 */
39 function uc_stock_menu($may_cache) {
40 $items = array();
41 if ($may_cache) {
42 $items[] = array(
43 'path' => 'admin/store/reports/stock',
44 'title' => t('Stock reports'),
45 'description' => t('View reports for product stock.'),
46 'callback' => 'uc_stock_report',
47 'access' => user_access('view reports'),
48 'type' => MENU_NORMAL_ITEM,
49 );
50 $items[] = array(
51 'path' => 'admin/store/settings/stock',
52 'title' => t('Stock settings'),
53 'description' => t('View the stock settings.'),
54 'callback' => 'drupal_get_form',
55 'callback arguments' => array('uc_stock_settings_form'),
56 'access' => user_access('administer products'),
57 'type' => MENU_NORMAL_ITEM,
58 );
59 }
60 else {
61 $items[] = array(
62 'path' => 'node/'. arg(1) .'/edit/stock',
63 'title' => t('Stock'),
64 'callback' => 'drupal_get_form',
65 'callback arguments' => array('uc_stock_edit_form', arg(1)),
66 'access' => user_access('administer products'),
67 'weight' => 10,
68 'type' => MENU_LOCAL_TASK,
69 );
70 }
71
72 return $items;
73 }
74
75
76 /*******************************************************************************
77 * Ubercart Hooks *
78 ******************************************************************************/
79
80 /**
81 * Implementation of hook_token_list().
82 */
83 function uc_stock_token_list($type = 'all') {
84 if ($type == 'stock' || $type == 'ubercart' || $type == 'all') {
85 $tokens['stock']['stock-level'] = t('The current stock level');
86 $tokens['stock']['stock-model'] = t('The model or SKU of the stock level');
87 $tokens['stock']['stock-threshold'] = t('The threshold or warning limit of the stock level');
88 }
89
90 return $tokens;
91 }
92
93 /**
94 * Implementation of hook_token_values().
95 */
96 function uc_stock_token_values($type, $object = NULL) {
97 switch ($type) {
98 case 'stock':
99 $values['stock-level'] = $object->stock;
100 $values['stock-model'] = $object->sku;
101 $values['stock-threshold'] = $object->threshold;
102 break;
103 }
104
105 return $values;
106 }
107
108 /**
109 * Implementation of hook_uc_message().
110 */
111 function uc_stock_uc_message() {
112 $messages['uc_stock_threshold_notification_subject'] = t('[store-name]: Stock threshold limit reached');
113 $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].");
114 return $messages;
115 }
116
117
118 /*******************************************************************************
119 * Callback Functions, Forms, and Tables *
120 ******************************************************************************/
121
122 // Form builder for product stock edit form.
123 function uc_stock_edit_form($nid) {
124 $node = node_load($nid);
125 drupal_set_title(check_plain($node->title));
126
127 $form = array();
128 $skus = uc_stock_skus($nid);
129
130 if (!$skus) {
131 drupal_not_found();
132 }
133
134 $form['stock'] = array('#tree' => TRUE);
135
136 foreach ($skus as $id => $sku) {
137 $stock = db_fetch_array(db_query("SELECT * FROM {uc_product_stock} WHERE sku = '%s'", $sku));
138
139 $form['stock'][$id]['sku'] = array(
140 '#type' => 'value',
141 '#value' => $sku,
142 );
143
144 // Checkbox to mark this as active.
145 $form['stock'][$id]['active'] = array(
146 '#type' => 'checkbox',
147 '#default_value' => !empty($stock['active']) ? $stock['active'] : 0,
148 );
149
150 // Sanitized version of the SKU for display.
151 $form['stock'][$id]['display_sku'] = array(
152 '#value' => check_plain($sku),
153 );
154
155 // Textfield for entering the stock level.
156 $form['stock'][$id]['stock'] = array(
157 '#type' => 'textfield',
158 '#default_value' => !empty($stock['stock']) ? $stock['stock'] : 0,
159 '#maxlength' => 9,
160 '#size' => 9,
161 );
162
163 // Textfield for entering the threshold level.
164 $form['stock'][$id]['threshold'] = array(
165 '#type' => 'textfield',
166 '#default_value' => !empty($stock['threshold']) ? $stock['threshold'] : 0,
167 '#maxlength' => 9,
168 '#size' => 9,
169 );
170 }
171
172 $form['nid'] = array(
173 '#type' => 'value',
174 '#value' => $nid,
175 );
176
177 $form['save'] = array(
178 '#type' => 'submit',
179 '#value' => t('Save changes'),
180 );
181
182 return $form;
183 }
184
185 function theme_uc_stock_edit_form($form) {
186 $header = array(
187 array('data' => t('Active')),
188 array('data' => t('SKU')),
189 array('data' => t('Stock')),
190 array('data' => t('Threshold')),
191 );
192
193 foreach (element_children($form['stock']) as $id) {
194 $rows[] = array(
195 array('data' => drupal_render($form['stock'][$id]['active'])),
196 array('data' => drupal_render($form['stock'][$id]['display_sku'])),
197 array('data' => drupal_render($form['stock'][$id]['stock'])),
198 array('data' => drupal_render($form['stock'][$id]['threshold'])),
199 );
200 }
201
202 return theme('table', $header, $rows) . drupal_render($form);
203 }
204
205 function uc_stock_edit_form_submit($form_id, $form_values) {
206 foreach (element_children($form_values['stock']) as $id) {
207 db_query("UPDATE {uc_product_stock} SET active = %d, stock = %d, threshold = %d WHERE sku = '%s'",
208 $form_values['stock'][$id]['active'] ? 1 : 0, intval($form_values['stock'][$id]['stock']),
209 intval($form_values['stock'][$id]['threshold']), $form_values['stock'][$id]['sku']);
210
211 if (!db_affected_rows()) {
212 db_query("INSERT INTO {uc_product_stock} (sku, nid, active, stock, threshold) VALUES ('%s', %d, %d, %d, %d)",
213 $form_values['stock'][$id]['sku'], $form_values['nid'], $form_values['stock'][$id]['active'] ? 1 : 0,
214 intval($form_values['stock'][$id]['stock']), intval($form_values['stock'][$id]['threshold']));
215 }
216 }
217
218 drupal_set_message(t('Stock settings saved.'));
219 }
220
221 // Displays a stock report for products with stock tracking enabled.
222 function uc_stock_report() {
223 drupal_add_css(drupal_get_path('module', 'uc_stock') .'/uc_stock.css');
224
225 $page_size = (!is_null($_GET['nopage'])) ? UC_REPORTS_MAX_RECORDS : variable_get('uc_reports_table_size', 30);
226 $csv_rows = array();
227 $rows = array();
228
229 $header = array(
230 array('data' => t('SKU'), 'field' => 'sku', 'sort' => 'asc'),
231 array('data' => t('Product'), 'field' => 'title'),
232 array('data' => t('Stock'), 'field' => 'stock'),
233 array('data' => t('Threshold'), 'field' => 'threshold'),
234 array('data' => t('Operations')),
235 );
236
237 $csv_rows[] = array(t('SKU'), t('Product'), t('Stock'), t('Threshold'));
238
239 $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";
240 if (arg(4) == 'threshold') {
241 $sql .= ' AND threshold >= stock';
242 }
243
244 $result = pager_query($sql . tablesort_sql($header), $page_size, 0, NULL);
245 while ($stock = db_fetch_object($result)) {
246 $op = array();
247 if (user_access('administer products')) {
248 $op[] = l(t('edit'), 'node/'. $stock->nid .'/edit/stock', array(), 'destination=admin/store/reports/stock');
249 }
250
251 // Add the data to a table row for display.
252 $rows[] = array(
253 'data' => array(
254 array('data' => $stock->sku),
255 array('data' => l($stock->title, 'node/'. $stock->nid)),
256 array('data' => $stock->stock),
257 array('data' => $stock->threshold),
258 array('data' => implode(' ', $op)),
259 ),
260 'class' => ($stock->threshold >= $stock->stock) ? 'uc-stock-below-threshold' : 'uc-stock-above-threshold',
261 );
262
263 // Add the data to the CSV contents for export.
264 $csv_rows[] = array($stock->sku, $stock->title, $stock->stock, $stock->threshold);
265 }
266
267 $csv_data = uc_reports_store_csv('uc_stock', $csv_rows);
268
269 $output = drupal_get_form('uc_stock_report_form')
270 . theme('table', $header, $rows, array('width' => '100%', 'class' => 'uc-stock-table'))
271 . theme_pager(NULL, $page_size);
272
273 $output .= '<div class="uc-reports-links">'. l(t('Export to CSV file'), 'admin/store/reports/getcsv/'. $csv_data['report'] .'/'. $csv_data['user']) .'&nbsp;&nbsp;&nbsp;'. ((!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')) .'</div>';
274
275 return $output;
276 }
277
278 // Form builder for stock report threshold filter.
279 function uc_stock_report_form() {
280 $form['threshold'] = array(
281 '#type' => 'checkbox',
282 '#title' => t('Only show SKUs that are below their threshold.'),
283 '#default_value' => arg(4) == 'threshold' ? TRUE : FALSE,
284 '#attributes' => array('onchange' => 'this.form.submit();'),
285 );
286
287 $form['submit'] = array(
288 '#type' => 'submit',
289 '#value' => t('Submit'),
290 '#attributes' => array('style' => "display:none;"),
291 );
292
293 return $form;
294 }
295
296 function uc_stock_report_form_submit($form_id, $form_values) {
297 if ($form_values['threshold']) {
298 drupal_goto('admin/store/reports/stock/threshold');
299 }
300 else {
301 drupal_goto('admin/store/reports/stock');
302 }
303 }
304
305 // Form builder for stock settings form.
306 function uc_stock_settings_form() {
307 $form['uc_stock_threshold_notification'] = array(
308 '#type' => 'checkbox',
309 '#title' => t('Send email notification when stock level reaches its threshold value'),
310 '#default_value' => variable_get('uc_stock_threshold_notification', FALSE),
311 );
312
313 $form['uc_stock_threshold_notification_recipients'] = array(
314 '#type' => 'textfield',
315 '#title' => t('Notification recipients'),
316 '#default_value' => variable_get('uc_stock_threshold_notification_recipients', variable_get('uc_store_email', ini_get('sendmail_from'))),
317 '#description' => t('The list of comma seperated email addresses that will receive the notification.'),
318 );
319
320 $form['uc_stock_threshold_notification_subject'] = array(
321 '#type' => 'textfield',
322 '#title' => t('Message subject'),
323 '#default_value' => variable_get('uc_stock_threshold_notification_subject', uc_get_message('uc_stock_threshold_notification_subject')),
324 );
325
326 $form['uc_stock_threshold_notification_message'] = array(
327 '#type' => 'textarea',
328 '#title' => t('Message text'),
329 '#default_value' => variable_get('uc_stock_threshold_notification_message', uc_get_message('uc_stock_threshold_notification_message')),
330 '#description' => t('The message the user receives when the stock level reaches its threshold value (uses <a href="!token-help-page">global, order, and stock tokens</a>).', array('!token-help-page' => url('admin/store/help/tokens'))),
331 '#rows' => 10,
332 );
333 $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'));
334
335 return system_settings_form($form);
336 }
337
338 /*******************************************************************************
339 * Module and Helper Functions *
340 ******************************************************************************/
341
342 /**
343 * Adjust the product stock level by a set amount.
344 *
345 * @param $sku
346 * The product SKU of the stock level to adjust.
347 * @param $qty
348 * The amount to add to or subtract from the stock level.
349 */
350 function uc_stock_adjust($sku, $qty) {
351 db_query("UPDATE {uc_product_stock} SET stock = stock + %d WHERE sku = '%s'", $qty, $sku);
352 }
353
354 /**
355 * Get the stock level of a particular product SKU
356 * @param $sku
357 * The Ubercart product SKU of the stock level to return.
358 * @return:
359 * The associated stock level with the particular SKU or FALSE if not active.
360 */
361 function uc_stock_level($sku) {
362 return db_result(db_query("SELECT stock FROM {uc_product_stock} WHERE sku = '%s' AND active = 1", $sku));
363 }
364
365 /**
366 * Gets the SKUs associated with a particular product.
367 *
368 * @param $nid
369 * The node id of the ubercart product
370 * @return:
371 * An array containing all the SKUs that are associated with the product or
372 * FALSE if the node ID isn't an Ubercart product.
373 */
374 function uc_stock_skus($nid) {
375 $node = node_load($nid);
376
377 if (is_null($node->model)) {
378 return FALSE;
379 }
380
381 $skus = array($node->model);
382
383 if (module_exists('uc_attribute')) {
384 $models = db_query("SELECT model FROM {uc_product_adjustments} WHERE nid = %d", $node->nid);
385 while ($model = db_fetch_object($models)) {
386 if (!in_array($model->model, $skus)) {
387 $skus[] = $model->model;
388 }
389 }
390 }
391
392 return $skus;
393 }
394
395 /**
396 * Emails administrator regarding any stock level thresholds hit.
397 *
398 * @param $order
399 * The order object that tripped the threshold limit.
400 * @param $product
401 * The product object that is associated with the SKU.
402 * @param $stock
403 * The stock level object that contains the stock level and SKU.
404 * @return
405 * The result of drupal_mail().
406 */
407 function _uc_stock_send_mail($order, $stock) {
408 $token_filters = array('global' => NULL, 'order' => $order, 'stock' => $stock);
409
410 $to = variable_get('uc_stock_threshold_notification_recipients', variable_get('uc_store_email', ini_get('sendmail_from')));
411 $from = uc_store_email_from();
412
413 $subject = variable_get('uc_stock_threshold_notification_subject', uc_get_message('uc_stock_threshold_notification_subject'));
414 $subject = token_replace_multiple($subject, $token_filters);
415
416 $body = variable_get('uc_stock_threshold_notification_message', uc_get_message('uc_stock_threshold_notification_message'));
417 $body = token_replace_multiple($body, $token_filters);
418
419 return drupal_mail('uc_stock', $to, $subject, $body, $from);
420 }
421
422 /**
423 * Implementation of hook_views_tables().
424 */
425 function uc_stock_views_tables() {
426 $tables['uc_product_stock'] = array(
427 'name' => 'uc_product_stock',
428 'join' => array(
429 'left' => array(
430 'table' => 'node',
431 'field' => 'nid'
432 ),
433 'right' => array(
434 'field' => 'nid',
435 ),
436 ),
437 'fields' => array(
438 'sku' => array(
439 'name' => t('Stock: SKU'),
440 'help' => t('The model or SKU of the stock level'),
441 'sortable' => TRUE,
442 ),
443 'active' => array(
444 'name' => t('Stock: Active'),
445 'help' => t('Whether or not the stock level is currently being tracked'),
446 'handler' => 'uc_stock_views_handler_active',
447 'sortable' => TRUE,
448 ),
449 'stock' => array(
450 'name' => t('Stock: Stock Level'),
451 'help' => t('The current stock level'),
452 'sortable' => TRUE,
453 ),
454 'threshold' => array(
455 'name' => t('Stock: Threshold'),
456 'help' => t('The threshold or warning limit of the stock level'),
457 'sortable' => TRUE,
458 ),
459 ),
460 'sorts' => array(
461 'sku' => array(
462 'name' => t('Stock: SKU'),
463 ),
464 'stock' => array(
465 'name' => t('Stock: Stock Level'),
466 'help' => t('The current stock level'),
467 ),
468 'threshold' => array(
469 'name' => t('Stock: Threshold'),
470 'help' => t('The threshold or warning limit of the stock level'),
471 ),
472 ),
473 'filters' => array(
474 'sku' => array(
475 'name' => 'Stock: SKU',
476 'operator' => 'views_handler_operator_like',
477 'handler' => 'views_handler_filter_like',
478 'help' => t('Filter the node based on stock SKU.'),
479 ),
480 'stock' => array(
481 'name' => 'Stock: Stock Level',
482 'operator' => 'views_handler_operator_gtlt',
483 'help' => t('Filter the node based on stock level.'),
484 ),
485 'threshold' => array(
486 'name' => 'Stock: Threshold',
487 'operator' => 'views_handler_operator_gtlt',
488 'help' => t('Filter the node based on threshold level.'),
489 ),
490 'is_active' => array(
491 'name' => 'Stock: Is Active',
492 'operator' => array('=' => 'Equals'),
493 'list' => 'views_handler_operator_yesno',
494 'list-type' => 'select',
495 'handler' => 'uc_stock_views_handler_filter_is_active',
496 'help' => t('Filter the data based on whether or not stock tracking is active for the SKU.'),
497 ),
498 'below_threshold' => array(
499 'name' => 'Stock: Is Below Threshold',
500 'operator' => array('=' => 'Equals'),
501 'list' => 'views_handler_operator_yesno',
502 'list-type' => 'select',
503 'handler' => 'uc_stock_views_handler_filter_below_threshold',
504 'help' => t('Filter the node based on whether it stock level is below the threshold for the SKU.'),
505 ),
506 ),
507 );
508
509 return $tables;
510 }
511
512 function uc_stock_views_handler_active($fieldinfo, $fielddata, $value, $data) {
513 return $value ? t('Active') : t('Inactive');
514 }
515
516 function uc_stock_views_handler_filter_below_threshold($op, $filter, $filterinfo, &$query) {
517 if ($op == 'handler') {
518 if ($filter['value'] == 0) {
519 $operator = '>=';
520 }
521 else {
522 $operator = '<';
523 }
524 $query->add_where('uc_product_stock.stock '. $operator .' uc_product_stock.threshold');
525 }
526 }
527
528 function uc_stock_views_handler_filter_is_active($op, $filter, $filterinfo, &$query) {
529 if ($op == 'handler') {
530 if ($filter['value'] == 1) {
531 $active = '1';
532 }
533 else {
534 $active = '0';
535 }
536 $query->ensure_table('uc_product_stock');
537 $query->add_where('uc_product_stock.active = '. $active);
538 }
539 }
540

  ViewVC Help
Powered by ViewVC 1.1.2