diff --git a/modules/report/commerce_pos_report.module b/modules/report/commerce_pos_report.module
index 4e73b29..a0bf19a 100644
--- a/modules/report/commerce_pos_report.module
+++ b/modules/report/commerce_pos_report.module
@@ -6,6 +6,7 @@
*/
define('COMMERCE_POS_REPORT_JOURNAL_ROLE_DEFAULT_ITEMS_PER_PAGE', 25);
+define('COMMERCE_POS_REPORT_SALES_REPORT_DEFAULT_ITEMS_PER_PAGE', 25);
/**
* Implements hook_menu().
@@ -17,7 +18,6 @@ function commerce_pos_report_menu() {
'file' => 'includes/commerce_pos_report.pages.inc',
'access arguments' => array('view commerce pos reports'),
);
-
$items['admin/commerce/pos/reports/end-of-day'] = array(
'title' => 'End of Day Report',
'page callback' => 'drupal_get_form',
@@ -32,6 +32,13 @@ function commerce_pos_report_menu() {
'file' => 'includes/commerce_pos_report.pages.inc',
'access arguments' => array('view commerce pos reports'),
);
+ $items['admin/commerce/pos/reports/sales-report'] = array(
+ 'title' => 'Sales Report',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('commerce_pos_report_sales_report'),
+ 'file' => 'includes/commerce_pos_report.pages.inc',
+ 'access arguments' => array('view commerce pos reports'),
+ );
$items['admin/commerce/pos/end-of-day/print/%'] = array(
'title' => 'Print Transaction Receipt',
'page callback' => 'commerce_pos_report_receipt_print',
diff --git a/modules/report/css/commerce_pos_report.css b/modules/report/css/commerce_pos_report.css
index 3ec5fce..762cf1f 100644
--- a/modules/report/css/commerce_pos_report.css
+++ b/modules/report/css/commerce_pos_report.css
@@ -126,6 +126,22 @@ a:hover .commerce-pos-report-ico-journal-role {
vertical-align: middle;
}
+.commerce-pos-report-ico-sales-report{
+ position: relative;
+ bottom: 2px;
+ background: url(../../../images/sprite_icons.png) -56px 0;
+ width: 18px;
+ height: 20px;
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: 10px;
+
+}
+a.active .commerce-pos-report-ico-sales-report,
+a:hover .commerce-pos-report-ico-sales-report {
+ background-position: -80px 0;
+}
+
/* -- Report Filters -- */
#commerce-pos-report-journal-role-options-container {
@@ -556,3 +572,62 @@ a:hover .commerce-pos-report-ico-journal-role {
border-bottom: 2px solid #ddd;
padding-top: 5px;
}
+.element-form-report{
+ padding-bottom: 20px;
+}
+
+ /*Table theme for reports*/
+.report-table-theme table.sticky-enabled.tableheader-processed.sticky-table thead th {
+ border-bottom: 2px solid #ddd;
+ font-size: 16px;
+ text-transform: none;
+ text-align: center;
+ background: #fff;
+}
+.report-table-theme table.sticky-enabled thead th:first-of-type{
+ text-align: left;
+}
+.report-table-theme table.sticky-enabled thead th.active{
+ background: #fff;
+}
+.report-table-theme table.sticky-enabled thead th a {
+ color: #000;
+}
+.report-table-theme table.sticky-enabled thead th a:hover{
+ text-decoration: none;
+ color: #337ab7;
+}
+.report-table-theme table.sticky-enabled thead th img {
+ position: relative;
+ bottom: 1px;
+ left: 6px;
+ top: 0;
+ right: 0;
+}
+.report-table-theme table.tableheader-processed.sticky-table tbody tr.even, .report-table-theme table.tableheader-processed.sticky-table tbody tr.odd{
+ background-color: #f5f5f5;
+ color: #000;
+ border-top:1px solid #e2e2e2;
+ font-size: 14px;
+}
+.report-table-theme table.sticky-enabled.tableheader-processed.sticky-table tbody tr:last-of-type {
+ border-top: 2px solid #ddd;
+ font-weight: bold;
+}
+.report-table-theme table.tableheader-processed.sticky-table tbody tr:hover{
+ background: #e5ecf2;
+}
+.report-table-theme table.sticky-enabled tbody td {
+ text-align: center;
+ border: 0;
+}
+.report-table-theme table.sticky-enabled tbody td:first-of-type{
+ text-align: left;
+}
+.report-table-theme table.sticky-enabled tbody td.active {
+ background: transparent;
+ border-top: 1px solid #e2e2e2;
+}
+.report-table-theme table.sticky-enabled tbody td.empty{
+ text-align: center;
+}
\ No newline at end of file
diff --git a/modules/report/includes/commerce_pos_report.pages.inc b/modules/report/includes/commerce_pos_report.pages.inc
index dcd405f..144270d 100644
--- a/modules/report/includes/commerce_pos_report.pages.inc
+++ b/modules/report/includes/commerce_pos_report.pages.inc
@@ -184,8 +184,8 @@ function commerce_pos_report_end_of_day($form, &$form_state) {
if (isset($currency_totals['commerce_pos_change'])) {
$change_amounts = &$currency_totals['commerce_pos_change'];
- // The change amount reflects the change we GAVE BACK, so we have to add
- // it to the expected amount of cash.
+ // The change amount reflects the change we GAVE BACK, so we have to
+ // add it to the expected amount of cash.
$expected_amount += ($change_amounts[CommercePosService::TRANSACTION_TYPE_SALE] - $change_amounts[CommercePosService::TRANSACTION_TYPE_RETURN]);
unset($change_amounts);
}
@@ -337,7 +337,8 @@ function commerce_pos_report_end_of_day($form, &$form_state) {
* Get the reports for the specified day of a single register.
*
* @param string $date_filter
- * The date range to filter by in compatible strtotime format. Will search this time +1day.
+ * The date range to filter by in compatible strtotime format.
+ * Will search this time +1day.
* @param int $register_id
* The id of the register to get totals for.
*
@@ -412,8 +413,11 @@ function commerce_pos_report_get_totals($date_filter, $register_id) {
CommercePosService::TRANSACTION_TYPE_RETURN => 0,
);
}
- $method = &$totals[$row->commerce_order_total_currency_code][$row->payment_method];
- $method[$row->type] += $row->amount;
+
+ if ($row->status == COMMERCE_PAYMENT_STATUS_SUCCESS) {
+ $method = &$totals[$row->commerce_order_total_currency_code][$row->payment_method];
+ $method[$row->type] += $row->amount;
+ }
if (in_array($row->status, $visible_statuses)) {
if (!isset($transaction_counts[$row->payment_method])) {
@@ -608,6 +612,7 @@ function commerce_pos_report_journal_role($form, &$form_state) {
global $user;
$form['#tree'] = TRUE;
+ $form['#attributes']['class'][] = 'element-form-report';
$form['header'] = array(
'#markup' => theme('commerce_pos_header', array('account' => $user)),
);
@@ -641,7 +646,7 @@ function commerce_pos_report_journal_role($form, &$form_state) {
$form['#attached']['libraries_load'][] = array('jquery-print');
$form['#attached']['css'][] = drupal_get_path('module', 'commerce_pos') . '/css/commerce_pos_style.css';
$form['#attached']['css'][] = drupal_get_path('module', 'commerce_pos_report') . '/css/commerce_pos_report.css';
- $form['#attached']['js'][] = drupal_get_path('module', 'commerce_pos_report') . '/js/commerce_pos_report.journal_role.js';
+ $form['#attached']['js'][] = drupal_get_path('module', 'commerce_pos_report') . '/js/commerce_pos_report.sorter.js';
$form['#attached']['js'][] = array(
'type' => 'setting',
'data' => $js_settings,
@@ -742,6 +747,11 @@ function commerce_pos_report_journal_role($form, &$form_state) {
$form['results'] = array(
'#type' => 'container',
'#id' => $form_state['results_container_id'],
+ '#attributes' => array(
+ 'class' => array(
+ 'report-table-theme',
+ ),
+ ),
);
$form['results']['table'] = commerce_pos_report_build_journal_role_table($query_params);
@@ -785,7 +795,6 @@ function commerce_pos_report_journal_role_submit($form, &$form_state) {
* A render array for the result table.
*/
function commerce_pos_report_build_journal_role_table(array $filters = array()) {
-
$header = array(
array('data' => t('Order No.'), 'field' => 't.order_id'),
array('data' => t('Time'), 'field' => 't.completed'),
@@ -942,7 +951,7 @@ function commerce_pos_report_build_journal_role_table(array $filters = array())
'class' => array('commerce-pos-transaction-type-' . $row->type),
);
- // Add another row right below that will serve as a placeholder for loading
+ // Add another row right below it will serve as a placeholder for loading
// in order data.
$rows[] = array(
array(
@@ -988,7 +997,7 @@ function commerce_pos_report_build_journal_role_table(array $filters = array())
'#header' => $header,
'#attached' => array(
'js' => array(
- drupal_get_path('module', 'commerce_pos_report') . '/js/commerce_pos_report.journal_role.js',
+ drupal_get_path('module', 'commerce_pos_report') . '/js/commerce_pos_report.sorter.js',
),
'library' => array(
array('system', 'drupal.ajax'),
@@ -1004,3 +1013,310 @@ function commerce_pos_report_build_journal_role_table(array $filters = array())
);
}
}
+
+/**
+ * Builds a Sales Report result table.
+ *
+ * @param array $filters
+ * An array of filters for the query. Can include any of the following:
+ * 'cashier' - The cashier who performed the transactions.
+ * 'payment_type' - The type of payment the transactions had.
+ * 'results_per_page' - The number of results per page.
+ *
+ * @return array
+ * A render array for the result table.
+ */
+function commerce_pos_report_build_sales_report_table(array $filters = array()) {
+ $header = array(
+ array('data' => t('Cashier Id'), 'field' => 't.cashier'),
+ array('data' => t('Cashier'), 'field' => 'cashier.name'),
+ array('data' => t('Net Sales')),
+ array('data' => t('Transactions')),
+ array('data' => t('Items Sold')),
+ array('data' => t('Unit per Trans (UPT)')),
+ array('data' => t('$ Per Transaction (DPT)')),
+ );
+
+ $query = db_select('commerce_pos_transaction', 't')
+ ->extend('PagerDefault')
+ ->extend('TableSort');
+
+ $query->fields('t', array('order_id', 'completed', 'uid', 'type'));
+ $query->addField('t', 'cashier', 'cashier_id');
+
+ // Cashier data
+ // We left join because legacy transactions may not have a cashier.
+ $query->leftJoin('commerce_pos_cashier', 'cashier', 'cashier.cashier_id = t.cashier');
+ $query->addField('cashier', 'name', 'cashier');
+
+ // Payment transaction data.
+ $query->join('commerce_payment_transaction', 'pt', 'pt.order_id = t.order_id');
+ $query->fields('pt', array('transaction_id'));
+
+ // Join order data.
+ $query->join('commerce_order', 'o', 'o.order_id = t.order_id');
+ $query->leftJoin('users', 'customer', 'customer.uid = o.uid');
+ $query->addField('customer', 'mail', 'customer_mail');
+
+ // Join order total data.
+ $query->join('field_data_commerce_order_total', 'ot', 't.order_id = ot.entity_id AND ot.entity_type = :commerce_order', array(
+ ':commerce_order' => 'commerce_order',
+ ));
+ $query->fields('ot', array(
+ 'commerce_order_total_amount',
+ 'commerce_order_total_currency_code',
+ 'commerce_order_total_data',
+ ));
+
+ // Join line item data.
+ $query->join('commerce_line_item', 'l', 'l.order_id = t.order_id');
+ $query->addField('l', 'quantity');
+
+ $line_item_types = commerce_product_line_item_types();
+ $query->condition('l.type', $line_item_types, 'IN');
+
+ // Make sure line item count is aggregated.
+ $query->groupBy('l.order_id');
+
+ /* As mysql do not support limits in sub queries, create sub query
+ for pagination and to limit records use a separate query.*/
+ $pagination_query = db_select('commerce_pos_cashier', 'cpc')
+ ->extend('PagerDefault')
+ ->extend('TableSort');
+
+ $pagination_query->fields('cpc', array('cashier_id'));
+
+ if ($filters['results_per_page'] > 0) {
+ $pagination_query->limit($filters['results_per_page']);
+ }
+
+ $limit_rows_result = $pagination_query->execute();
+ if ($limit_rows_result->rowCount() > 0) {
+ foreach ($limit_rows_result as $fetchCashier) {
+ $cashiers_id[] = $fetchCashier->cashier_id;
+ }
+ }
+ $query->condition('cashier.cashier_id', $cashiers_id, 'IN');
+
+ // Set up our filters/conditions.
+ $query->orderByHeader($header);
+ $result = $query->execute();
+
+ $rows = array();
+ $totals = array(
+ 'net_sales' => 0,
+ 'transactions' => 0,
+ 'item_sold' => 0,
+ 'unit_per_transaction' => 0,
+ 'dollar_per_transaction' => 0,
+ );
+
+ if ($result->rowCount() > 0) {
+ $report = sales_process_data($result);
+ foreach ($report as $cashier_transactions) {
+ $table_row = array();
+ $transactions = $cashier_transactions['cashier']['transactions'];
+ $currency_code = $cashier_transactions['cashier']['currency_code'];
+ $net_sales = $cashier_transactions['transaction']['net_sales'];
+ $net_sold_items = ($cashier_transactions['transaction']['purchase_count'] - $cashier_transactions['transaction']['return_count']);
+ $unit_per_transaction = round(($net_sold_items / $transactions), 2);
+ $dollar_per_transaction = ($net_sales / $transactions);
+ $table_row[] = $cashier_transactions['cashier']['id'];
+ $table_row[] = $cashier_transactions['cashier']['name'];
+ $table_row[] = commerce_currency_format($net_sales, $currency_code);
+ $table_row[] = $transactions;
+ $table_row[] = $net_sold_items;
+ // Unit per Trans (UPT)
+ $table_row[] = $unit_per_transaction;
+ $table_row[] = commerce_currency_format($dollar_per_transaction, $currency_code);
+ // Table feet with total.
+ $totals['net_sales'] += $net_sales;
+ $totals['transactions'] += $transactions;
+ $totals['item_sold'] += $net_sold_items;
+ $totals['unit_per_transaction'] += $unit_per_transaction;
+ $totals['dollar_per_transaction'] += $dollar_per_transaction;
+ $rows[] = array(
+ 'data' => $table_row,
+ 'class' => array(),
+ );
+ }
+
+ $rows[] = array(
+ '',
+ '',
+ commerce_currency_format($totals['net_sales'], $currency_code),
+ $totals['transactions'],
+ $totals['item_sold'],
+ $totals['unit_per_transaction'],
+ commerce_currency_format($totals['dollar_per_transaction'], $currency_code),
+ );
+ }
+ $table = array(
+ '#theme' => 'table',
+ '#rows' => $rows,
+ '#header' => $header,
+ '#empty' => t('No transactions found'),
+ '#attached' => array(
+ 'js' => array(
+ drupal_get_path('module', 'commerce_pos_report') . '/js/commerce_pos_report.sorter.js',
+ ),
+ 'library' => array(
+ array('system', 'drupal.ajax'),
+ ),
+ ),
+ );
+ return $table;
+}
+
+/**
+ * Callback for the Sales form/report.
+ */
+function commerce_pos_report_sales_report($form, &$form_state) {
+ global $user;
+ $form['#tree'] = TRUE;
+ $form['#attributes']['class'][] = 'element-form-report';
+ $form['header'] = array(
+ '#markup' => theme('commerce_pos_header', array('account' => $user)),
+ );
+
+ $query_filter_defaults = array(
+ 'results_per_page' => COMMERCE_POS_REPORT_SALES_REPORT_DEFAULT_ITEMS_PER_PAGE,
+ );
+
+ $query_params = drupal_get_query_parameters() + $query_filter_defaults;
+
+ $form['filters'] = array(
+ '#type' => 'container',
+ );
+
+ if (!isset($form_state['results_container_id'])) {
+ $form_state['results_container_id'] = 'commerce-pos-report-sale-container';
+ }
+
+ $js_settings = array(
+ 'commercePosReport' => array(
+ 'cssUrl' => url(drupal_get_path('module', 'commerce_pos_report') . '/css/commerce_pos_report_receipt.css', array(
+ 'absolute' => TRUE,
+ )),
+ ),
+ );
+
+ $form['#attached']['libraries_load'][] = array('jquery-print');
+ $form['#attached']['css'][] = drupal_get_path('module', 'commerce_pos') . '/css/commerce_pos_style.css';
+ $form['#attached']['css'][] = drupal_get_path('module', 'commerce_pos_report') . '/css/commerce_pos_report.css';
+ $form['#attached']['js'][] = drupal_get_path('module', 'commerce_pos_report') . '/js/commerce_pos_report.sorter.js';
+ $form['#attached']['js'][] = array(
+ 'type' => 'setting',
+ 'data' => $js_settings,
+ );
+ $form['report-options'] = array(
+ '#theme' => 'commerce_pos_report_options',
+ );
+ $form['options'] = array(
+ '#type' => 'container',
+ '#id' => 'commerce-pos-report-journal-role-options-container',
+ );
+ $form['options']['results_per_page'] = array(
+ '#type' => 'select',
+ '#title' => t('Items per page'),
+ '#options' => array(
+ 25 => '25',
+ 50 => '50',
+ 100 => '100',
+ 500 => '500',
+ -1 => t('all'),
+ ),
+ '#default_value' => $query_params['results_per_page'],
+ '#attributes' => array(
+ 'class' => array('commerce-pos-report-journal-role-filter'),
+ ),
+ );
+ $form['results'] = array(
+ '#type' => 'container',
+ '#id' => $form_state['results_container_id'],
+ '#attributes' => array(
+ 'class' => array(
+ 'report-table-theme',
+ ),
+ ),
+ );
+ $form['results']['table'] = commerce_pos_report_build_sales_report_table($query_params);
+ // Pager has to come after we've build the journal role table.
+ $form['options']['pager'] = array('#theme' => 'pager');
+ $form['results']['pager'] = array('#theme' => 'pager');
+
+ $form['submit'] = array(
+ '#value' => t('Submit'),
+ '#type' => 'submit',
+ '#weight' => -50,
+ '#attributes' => array(
+ 'class' => array('element-invisible', 'commerce-pos-report-journal-role-submit'),
+ ),
+ );
+
+ return $form;
+}
+
+/**
+ * Submit handler for the journal role report form.
+ */
+function commerce_pos_report_sales_report_submit($form, &$form_state) {
+ $query_params = array_merge(drupal_get_query_parameters(), $form_state['values']['options']);
+ $form_state['redirect'] = array(current_path(), array('query' => $query_params));
+}
+
+/**
+ * Helper function for Sales table to process data.
+ */
+function sales_process_data($result) {
+ $table_row = array();
+ foreach ($result as $row) {
+ $return_count = 0;
+ $purchase_count = 0;
+ $transactions = 1;
+ $cashier_id = $row->cashier_id;
+
+ if (!isset($table_row[$cashier_id])) {
+ $table_row[$cashier_id] = array(
+ 'transaction' => array(
+ 'net_sales' => 0,
+ 'purchase_count' => 0,
+ 'return_count' => 0,
+ ),
+ 'cashier' => array(
+ 'transactions' => 0,
+ ),
+ );
+ }
+
+ switch ($row->type) {
+ case CommercePosService::TRANSACTION_TYPE_RETURN:
+ $return_count = $row->quantity;
+ // Order total.
+ $table_row[$cashier_id]['transaction']['net_sales'] -= $row->commerce_order_total_amount;
+ break;
+
+ default:
+ $purchase_count = $row->quantity;
+ // Order total.
+ $table_row[$cashier_id]['transaction']['net_sales'] += $row->commerce_order_total_amount;
+ break;
+ }
+
+ // Build order ID link.
+ $table_row[$cashier_id]['cashier']['id'] = $cashier_id;
+ // Cashier.
+ $table_row[$cashier_id]['cashier']['name'] = $row->cashier;
+ // Number of transactions.
+ $table_row[$cashier_id]['cashier']['transactions'] += $transactions;
+ // Purchased.
+ $table_row[$cashier_id]['transaction']['purchase_count'] += $purchase_count;
+ // Returned.
+ $table_row[$cashier_id]['transaction']['return_count'] += $return_count;
+
+ // Discount flag.
+ $table_row[$cashier_id]['cashier']['currency_code'] = $row->commerce_order_total_currency_code;
+
+ }
+ return $table_row;
+}
diff --git a/modules/report/js/commerce_pos_report.journal_role.js b/modules/report/js/commerce_pos_report.sorter.js
similarity index 100%
rename from modules/report/js/commerce_pos_report.journal_role.js
rename to modules/report/js/commerce_pos_report.sorter.js
diff --git a/modules/report/theme/commerce_pos_report.theme.inc b/modules/report/theme/commerce_pos_report.theme.inc
index 5e71f5a..c6f342d 100644
--- a/modules/report/theme/commerce_pos_report.theme.inc
+++ b/modules/report/theme/commerce_pos_report.theme.inc
@@ -53,6 +53,10 @@ function theme_commerce_pos_report_options(&$variables) {
'attributes' => array('class' => array('commerce-pos-journal-role')),
'html' => TRUE,
)),
+ l('' . t('Sales Report'), 'admin/commerce/pos/reports/sales-report', array(
+ 'attributes' => array('class' => array('commerce-pos-sales-report')),
+ 'html' => TRUE,
+ )),
),
'attributes' => array(
'class' => array('commerce-pos-report-options'),
diff --git a/theme/commerce_pos.theme.inc b/theme/commerce_pos.theme.inc
index 8206da7..6f7cd5d 100644
--- a/theme/commerce_pos.theme.inc
+++ b/theme/commerce_pos.theme.inc
@@ -139,19 +139,24 @@ function theme_commerce_pos_order_balance_summary(&$variables) {
$rows = array();
$totals = array();
foreach ($transactions as $transaction) {
+ $payment_action = "";
$payment_method = commerce_payment_method_load($transaction->payment_method);
$status = $transaction->status;
$currency_code = $transaction->currency_code;
$amount = $transaction->amount;
+ if ($status === COMMERCE_PAYMENT_STATUS_SUCCESS) {
+ $payment_action = commerce_currency_format($amount, $currency_code) . '
' . theme('commerce_pos_transaction_actions', array('transaction' => $transaction));
+ $action_class = "amount";
+ }
+ elseif ($status == 'void') {
+ $payment_action = t('VOID');
+ $action_class = "transaction-voided";
+ }
+
// If a payment transaction doesn't count toward the total paid, show its
// status in the title.
$title = $payment_method['title'];
- $formatted_amount = commerce_currency_format($amount, $currency_code);
- if (!empty($transaction_statuses[$status]) && !$transaction_statuses[$status]['total']) {
- $title .= ' - ' . $transaction_statuses[$status]['title'];
- $formatted_amount = '' . $formatted_amount . '';
- }
$title = '