uc_reports.admin.inc 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407
  1. <?php
  2. /**
  3. * @file
  4. * Reports administration menu items.
  5. */
  6. /**
  7. * Displays the customer report.
  8. */
  9. function uc_reports_customers() {
  10. $address_preference = variable_get('uc_customer_list_address', 'billing');
  11. $first_name = ($address_preference == 'billing') ? 'billing_first_name' : 'delivery_first_name';
  12. $last_name = ($address_preference == 'billing') ? 'billing_last_name' : 'delivery_last_name';
  13. $page = isset($_GET['page']) ? intval($_GET['page']) : 0;
  14. $page_size = isset($_GET['nopage']) ? UC_REPORTS_MAX_RECORDS : variable_get('uc_reports_table_size', 30);
  15. $order_statuses = uc_reports_order_statuses();
  16. $rows = array();
  17. $csv_rows = array();
  18. $header = array(
  19. array('data' => t('#')),
  20. array('data' => t('Customer'), 'field' => "ou.$last_name"),
  21. array('data' => t('Username'), 'field' => "u.name"),
  22. array('data' => t('Orders'), 'field' => 'orders'),
  23. array('data' => t('Products'), 'field' => 'products'),
  24. array('data' => t('Total'), 'field' => 'total', 'sort' => 'desc'),
  25. array('data' => t('Average'), 'field' => 'average'),
  26. );
  27. $csv_rows[] = array(t('#'), t('Customer'), t('Username'), t('Orders'), t('Products'), t('Total'), t('Average'));
  28. $query = db_select('users', 'u', array('fetch' => PDO::FETCH_ASSOC))->extend('PagerDefault')->extend('TableSort');
  29. $query->leftJoin('uc_orders', 'ou', 'u.uid = ou.uid');
  30. $query->fields('u', array(
  31. 'uid',
  32. 'name',
  33. ))
  34. ->fields('ou', array(
  35. $first_name,
  36. $last_name,
  37. ))
  38. ->condition('u.uid', 0, '>')
  39. ->groupBy('u.uid');
  40. $query->addExpression("(SELECT COUNT(DISTINCT(order_id)) FROM {uc_orders} o WHERE o.uid = u.uid AND o.order_status IN (:statuses))", 'orders', array(':statuses' => $order_statuses));
  41. $query->addExpression("(SELECT SUM(qty) FROM {uc_order_products} ps LEFT JOIN {uc_orders} os ON ps.order_id = os.order_id WHERE os.order_status IN (:statuses2) AND os.uid = u.uid)", 'products', array(':statuses2' => $order_statuses));
  42. $query->addExpression("(SELECT SUM(ot.order_total) FROM {uc_orders} ot WHERE ot.uid = u.uid AND ot.order_status IN (:statuses3))", 'total', array(':statuses3' => $order_statuses));
  43. $query->addExpression("ROUND((SELECT SUM(ot.order_total) FROM {uc_orders} ot WHERE ot.uid = u.uid AND ot.order_status IN (:sum_statuses))/(SELECT COUNT(DISTINCT(order_id)) FROM {uc_orders} o WHERE o.uid = u.uid AND o.order_status IN (:count_statuses)), 2)", 'average', array(':sum_statuses' => $order_statuses, ':count_statuses' => $order_statuses));
  44. $count_query = db_select('users', 'u');
  45. $count_query->leftJoin('uc_orders', 'ou', 'u.uid = ou.uid');
  46. $count_query->addExpression('COUNT(DISTINCT u.uid)');
  47. $count_query->condition('u.uid', 0, '>');
  48. $query->setCountQuery($count_query);
  49. $query->groupBy('u.uid')
  50. ->groupBy('u.name')
  51. ->groupBy("ou.$first_name")
  52. ->groupBy("ou.$last_name")
  53. ->orderByHeader($header)
  54. ->limit($page_size);
  55. $customers = $query->execute();
  56. foreach ($customers as $customer) {
  57. $name = (!empty($customer[$last_name]) || !empty($customer[$first_name])) ? l($customer[$last_name] . ', ' . $customer[$first_name], 'admin/store/customers/orders/' . $customer['uid']) : l($customer['name'], 'admin/store/customers/orders/' . $customer['uid']);
  58. $customer_number = ($page * variable_get('uc_reports_table_size', 30)) + (count($rows) + 1);
  59. $customer_order_name = (!empty($customer[$last_name]) || !empty($customer[$first_name])) ? $customer[$last_name] . ', ' . $customer[$first_name] : $customer['name'];
  60. $customer_name = $customer['name'];
  61. $orders = !empty($customer['orders']) ? $customer['orders'] : 0;
  62. $products = !empty($customer['products']) ? $customer['products'] : 0;
  63. $total_revenue = uc_currency_format($customer['total']);
  64. $average_revenue = uc_currency_format($customer['average']);
  65. $rows[] = array(
  66. array('data' => $customer_number),
  67. array('data' => $name),
  68. array('data' => l($customer_name, 'user/' . $customer['uid'])),
  69. array('data' => $orders),
  70. array('data' => $products),
  71. array('data' => $total_revenue),
  72. array('data' => $average_revenue),
  73. );
  74. $csv_rows[] = array($customer_number, $customer_order_name, $customer_name, $orders, $products, $customer['total'], $customer['average']);
  75. }
  76. $csv_data = uc_reports_store_csv('uc_customers', $csv_rows);
  77. $build['report'] = array(
  78. '#theme' => 'table',
  79. '#header' => $header,
  80. '#rows' => $rows,
  81. '#attributes' => array('width' => '100%', 'class' => array('uc-sales-table')),
  82. '#empty' => t('No customers found'),
  83. );
  84. $build['pager'] = array(
  85. '#theme' => 'pager',
  86. );
  87. $build['links'] = array(
  88. '#prefix' => '<div class="uc-reports-links">',
  89. '#suffix' => '</div>',
  90. );
  91. $build['links']['export_csv'] = array(
  92. '#markup' => l(t('Export to CSV file.'), 'admin/store/reports/getcsv/' . $csv_data['report'] . '/' . $csv_data['user']),
  93. '#suffix' => '&nbsp;&nbsp;&nbsp;',
  94. );
  95. if (isset($_GET['nopage'])) {
  96. $build['links']['toggle_pager'] = array(
  97. '#markup' => l(t('Show paged records'), 'admin/store/reports/customers'),
  98. );
  99. }
  100. else {
  101. $build['links']['toggle_pager'] = array(
  102. '#markup' => l(t('Show all records'), 'admin/store/reports/customers', array('query' => array('nopage' => '1'))),
  103. );
  104. }
  105. return $build;
  106. }
  107. /**
  108. * Displays the product reports.
  109. */
  110. function uc_reports_products() {
  111. $views_column = module_exists('statistics') && variable_get('statistics_count_content_views', FALSE);
  112. $page = isset($_GET['page']) ? intval($_GET['page']) : 0;
  113. $page_size = isset($_GET['nopage']) ? UC_REPORTS_MAX_RECORDS : variable_get('uc_reports_table_size', 30);
  114. $order_statuses = uc_reports_order_statuses();
  115. $row_cell = $page * variable_get('uc_reports_table_size', 30) + 1;
  116. $rows = array();
  117. $csv_rows = array();
  118. // Hard code the ignore of the product kit for this report.
  119. $ignored_types = array('product_kit');
  120. // Build an array of valid product types to include on the report.
  121. $product_types = array();
  122. foreach (uc_product_types() as $type) {
  123. // Pass over any ignored types.
  124. if (!in_array($type, $ignored_types)) {
  125. $product_types[] = $type;
  126. }
  127. }
  128. $query = db_select('node', 'n', array('fetch' => PDO::FETCH_ASSOC))
  129. ->extend('PagerDefault')
  130. ->extend('TableSort')
  131. ->limit($page_size);
  132. $query->addField('n', 'nid');
  133. $query->addField('n', 'title');
  134. $query->addExpression("(SELECT SUM(uop.qty) FROM {uc_order_products} uop LEFT JOIN {uc_orders} uo ON uop.order_id = uo.order_id WHERE uo.order_status IN (:statuses) AND uop.nid = n.nid)", 'sold', array(':statuses' => $order_statuses));
  135. $query->addExpression("(SELECT (SUM(uop.price * uop.qty) - SUM(uop.cost * uop.qty)) FROM {uc_order_products} uop LEFT JOIN {uc_orders} uo ON uop.order_id = uo.order_id WHERE uo.order_status IN (:statuses2) AND uop.nid = n.nid)", 'gross', array(':statuses2' => $order_statuses));
  136. $query->addExpression("(SELECT (SUM(uop.price * uop.qty)) FROM {uc_order_products} uop LEFT JOIN {uc_orders} uo ON uop.order_id = uo.order_id WHERE uo.order_status IN (:statuses3) AND uop.nid = n.nid)", 'revenue', array(':statuses3' => $order_statuses));
  137. $header = array(
  138. array('data' => t('#')),
  139. array('data' => t('Product'), 'field' => 'n.title'),
  140. array('data' => t('Sold'), 'field' => 'sold'),
  141. array('data' => t('Revenue'), 'field' => 'revenue', 'sort' => 'desc'),
  142. array('data' => t('Gross'), 'field' => 'gross'),
  143. );
  144. $csv_rows[] = array(t('#'), t('Product'), t('Sold'), t('Revenue'), t('Gross'));
  145. if ($views_column) {
  146. $header[] = array('data' => t('Views'), 'field' => 'nc.totalcount');
  147. $csv_rows[0][] = t('Views');
  148. }
  149. $query->orderByHeader($header);
  150. if ($views_column) {
  151. $query->leftJoin('node_counter', 'nc', 'n.nid = nc.nid');
  152. $query->addField('nc', 'totalcount');
  153. }
  154. $query->condition('n.type', $product_types, 'IN')
  155. ->groupBy('n.nid')
  156. ->groupBy('n.title');
  157. $products = $query->execute();
  158. foreach ($products as $product) {
  159. $product_cell = l($product['title'], 'node/' . $product['nid']);
  160. $product_csv = $product['title'];
  161. $sold_cell = empty($product['sold']) ? 0 : $product['sold'];
  162. $sold_csv = $sold_cell;
  163. $revenue_csv = empty($product['revenue']) ? 0 : $product['revenue'];
  164. $revenue_cell = uc_currency_format($revenue_csv);
  165. $gross_csv = empty($product['gross']) ? 0 : $product['gross'];
  166. $gross_cell = uc_currency_format($gross_csv);
  167. $row = array(
  168. 'data' => array(
  169. $row_cell,
  170. $product_cell,
  171. "<strong>$sold_cell</strong>",
  172. "<strong>$revenue_cell</strong>",
  173. "<strong>$gross_cell</strong>",
  174. ),
  175. 'primary' => TRUE,
  176. );
  177. $csv_row = array($row_cell, $product_csv, $sold_csv, $revenue_csv, $gross_csv);
  178. if ($views_column) {
  179. $views = isset($product['totalcount']) ? $product['totalcount'] : 0;
  180. $row['data'][] = $views;
  181. $csv_row[] = $views;
  182. }
  183. $rows[] = $row;
  184. $csv_rows[] = $csv_row;
  185. if (module_exists('uc_attribute')) {
  186. // Get the SKUs from this product.
  187. $models = uc_reports_product_get_skus($product['nid']);
  188. // Add the product breakdown rows
  189. foreach ($models as $model) {
  190. $sold = db_query("SELECT SUM(qty) FROM {uc_order_products} p LEFT JOIN {uc_orders} o ON p.order_id = o.order_id WHERE o.order_status IN (:statuses) AND p.model = :model AND p.nid = :nid", array(':statuses' => $order_statuses, ':model' => $model, ':nid' => $product['nid']))->fetchField();
  191. $revenue = db_query("SELECT SUM(p.price * p.qty) FROM {uc_order_products} p LEFT JOIN {uc_orders} o ON p.order_id = o.order_id WHERE o.order_status IN (:statuses) AND p.model = :model AND p.nid = :nid", array(':statuses' => $order_statuses, ':model' => $model, ':nid' => $product['nid']))->fetchField();
  192. $gross = db_query("SELECT (SUM(p.price * p.qty) - SUM(p.cost * p.qty)) FROM {uc_order_products} p LEFT JOIN {uc_orders} o ON p.order_id = o.order_id WHERE o.order_status IN (:statuses) AND p.model = :model AND p.nid = :nid", array(':statuses' => $order_statuses, ':model' => $model, ':nid' => $product['nid']))->fetchField();
  193. $breakdown_product = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$model";
  194. $product_csv = " $model";
  195. $sold_csv = !empty($sold) ? $sold : 0;
  196. $breakdown_sold = $sold_csv;
  197. $revenue_csv = !empty($revenue) ? $revenue : 0;
  198. $breakdown_revenue = theme('uc_price', array('price' => $revenue_csv));
  199. $gross_csv = !empty($gross) ? $gross : 0;
  200. $breakdown_gross = theme('uc_price', array('price' => $gross_csv));
  201. $row = array(
  202. 'data' => array(
  203. '',
  204. $breakdown_product,
  205. $breakdown_sold,
  206. $breakdown_revenue,
  207. $breakdown_gross,
  208. ),
  209. );
  210. $csv_row = array('', $product_csv, $sold_csv, $revenue_csv, $gross_csv);
  211. if ($views_column) {
  212. $row['data'][] = '';
  213. $csv_row[] = '';
  214. }
  215. $rows[] = $row;
  216. $csv_rows[] = $csv_row;
  217. }
  218. }
  219. $row_cell++;
  220. }
  221. $csv_data = uc_reports_store_csv('uc_products', $csv_rows);
  222. $build['report'] = array(
  223. '#theme' => 'uc_reports_product_table',
  224. '#header' => $header,
  225. '#rows' => $rows,
  226. '#attributes' => array('width' => '100%', 'class' => array('uc-sales-table')),
  227. '#empty' => t('No products found'),
  228. );
  229. $build['pager'] = array(
  230. '#theme' => 'pager',
  231. );
  232. $build['links'] = array(
  233. '#prefix' => '<div class="uc-reports-links">',
  234. '#suffix' => '</div>',
  235. );
  236. $build['links']['export_csv'] = array(
  237. '#markup' => l(t('Export to CSV file.'), 'admin/store/reports/getcsv/' . $csv_data['report'] . '/' . $csv_data['user']),
  238. '#suffix' => '&nbsp;&nbsp;&nbsp;',
  239. );
  240. if (isset($_GET['nopage'])) {
  241. $build['links']['toggle_pager'] = array(
  242. '#markup' => l(t('Show paged records'), 'admin/store/reports/products'),
  243. );
  244. }
  245. else {
  246. $build['links']['toggle_pager'] = array(
  247. '#markup' => l(t('Show all records'), 'admin/store/reports/products', array('query' => array('nopage' => '1'))),
  248. );
  249. }
  250. $build['instructions'] = array('#markup' => '<small>*' . t('Make sure %setting_name is set to %state in the <a href="!url">access log settings page</a> to enable views column.', array('%setting_name' => 'count content views', '%state' => 'enabled', '!url' => url('admin/config/system/statistics', array('query' => array('destination' => 'admin/store/reports/products'))))) . '</small>');
  251. return $build;
  252. }
  253. /**
  254. * Gets the SKUs on a product, including adjustments and past orders.
  255. *
  256. * @param $nid
  257. * The product's node ID.
  258. *
  259. * @return
  260. * A unique sorted array of all skus.
  261. */
  262. function uc_reports_product_get_skus($nid) {
  263. // Product SKU.
  264. $models = array(db_query("SELECT model FROM {uc_products} WHERE nid = :nid", array(':nid' => $nid))->fetchField());
  265. // Adjustment SKUs.
  266. $models = array_merge($models, db_query("SELECT model FROM {uc_product_adjustments} WHERE nid = :nid", array(':nid' => $nid))->fetchCol());
  267. // SKUs from orders.
  268. $models = array_merge($models, db_query("SELECT DISTINCT model FROM {uc_order_products} WHERE nid = :nid", array(':nid' => $nid))->fetchCol());
  269. // Unique, sorted.
  270. $models = array_unique($models);
  271. asort($models);
  272. return $models;
  273. }
  274. /**
  275. * Displays the custom product report.
  276. */
  277. function uc_reports_products_custom() {
  278. $views_column = module_exists('statistics') && variable_get('statistics_count_content_views', FALSE);
  279. $page = isset($_GET['page']) ? intval($_GET['page']) : 0;
  280. $page_size = isset($_GET['nopage']) ? UC_REPORTS_MAX_RECORDS : variable_get('uc_reports_table_size', 30);
  281. $rows = array();
  282. $csv_rows = array();
  283. // Hard code the ignore of the product kit for this report.
  284. $ignored_types = array('product_kit');
  285. // Build an array of valid product types to include on the report.
  286. $product_types = array();
  287. foreach (uc_product_types() as $type) {
  288. // Pass over any ignored types.
  289. if (!in_array($type, $ignored_types)) {
  290. $product_types[] = $type;
  291. }
  292. }
  293. // Use default report parameters if we don't detect values in the URL.
  294. if (arg(5) == '') {
  295. $args = array(
  296. 'start_date' => mktime(0, 0, 0, date('n'), 1, date('Y') - 1),
  297. 'end_date' => REQUEST_TIME,
  298. 'status' => uc_reports_order_statuses(),
  299. );
  300. }
  301. else {
  302. $args = array(
  303. 'start_date' => arg(5),
  304. 'end_date' => arg(6),
  305. 'status' => explode(',', arg(7)),
  306. );
  307. }
  308. $query = db_select('node', 'n', array('fetch' => PDO::FETCH_ASSOC))
  309. ->extend('PagerDefault')
  310. ->extend('TableSort')
  311. ->limit($page_size)
  312. ->fields('n', array(
  313. 'nid',
  314. 'title',
  315. ))
  316. ->condition('type', $product_types, 'IN')
  317. ->groupBy('n.nid');
  318. $query->addExpression("(SELECT SUM(qty) FROM {uc_order_products} p LEFT JOIN {uc_orders} o ON p.order_id = o.order_id WHERE o.order_status IN (:statuses) AND p.nid = n.nid AND o.created >= :start AND o.created <= :end)", 'sold', array(':statuses' => $args['status'], ':start' => $args['start_date'], ':end' => $args['end_date']));
  319. $query->addExpression("(SELECT (SUM(p2.price * p2.qty)) FROM {uc_order_products} p2 LEFT JOIN {uc_orders} o ON p2.order_id = o.order_id WHERE o.order_status IN (:statuses) AND p2.nid = n.nid AND o.created >= :start AND o.created <= :end)", 'revenue', array(':statuses' => $args['status'], ':start' => $args['start_date'], ':end' => $args['end_date']));
  320. $query->addExpression("(SELECT (SUM(p3.price * p3.qty) - SUM(p3.cost * p3.qty)) FROM {uc_order_products} p3 LEFT JOIN {uc_orders} o ON p3.order_id = o.order_id WHERE o.order_status IN (:statuses) AND p3.nid = n.nid AND o.created >= :start AND o.created <= :end)", 'gross', array(':statuses' => $args['status'], ':start' => $args['start_date'], ':end' => $args['end_date']));
  321. $header = array(
  322. array('data' => t('#')),
  323. array('data' => t('Product'), 'field' => 'n.title'),
  324. array('data' => t('Sold'), 'field' => 'sold'),
  325. array('data' => t('Revenue'), 'field' => 'revenue', 'sort' => 'desc'),
  326. array('data' => t('Gross'), 'field' => 'gross'),
  327. );
  328. $csv_rows[] = array(t('#'), t('Product'), t('Sold'), t('Revenue'), t('Gross'));
  329. if ($views_column) {
  330. $header[] = array('data' => t('Views'), 'field' => 'nc.totalcount');
  331. $csv_rows[0][] = t('Views');
  332. }
  333. $query->orderByHeader($header);
  334. if ($views_column) {
  335. $query->leftJoin('node_counter', 'c', 'n.nid = c.nid');
  336. $query->addField('c', 'totalcount');
  337. }
  338. $products = $query->execute();
  339. foreach ($products as $product) {
  340. $row_cell = ($page * variable_get('uc_reports_table_size', 30)) + count($rows) + 1;
  341. $product_cell = l($product['title'], 'node/' . $product['nid']);
  342. $product_csv = $product['title'];
  343. $sold_cell = empty($product['sold']) ? 0 : $product['sold'];
  344. $sold_csv = $sold_cell;
  345. $revenue_csv = empty($product['revenue']) ? 0 : $product['revenue'];
  346. $revenue_cell = uc_currency_format($revenue_csv);
  347. $gross_csv = empty($product['gross']) ? 0 : $product['gross'];
  348. $gross_cell = uc_currency_format($gross_csv);
  349. if (module_exists('uc_attribute')) {
  350. $breakdown_product = $breakdown_sold = $breakdown_revenue = $breakdown_gross = '';
  351. // Get the SKUs from this product.
  352. $models = uc_reports_product_get_skus($product['nid']);
  353. // Add the product breakdown rows
  354. foreach ($models as $model) {
  355. $sold = db_query("SELECT SUM(qty) FROM {uc_order_products} p LEFT JOIN {uc_orders} o ON p.order_id = o.order_id WHERE o.order_status IN (:statuses) AND p.model = :model AND p.nid = :nid AND o.created >= :start AND o.created <= :end", array(':statuses' => $args['status'], ':start' => $args['start_date'], ':end' => $args['end_date'], ':model' => $model, ':nid' => $product['nid']))->fetchField();
  356. $sold = empty($sold) ? 0 : $sold;
  357. $revenue = db_query("SELECT SUM(p.price * p.qty) FROM {uc_order_products} p LEFT JOIN {uc_orders} o ON p.order_id = o.order_id WHERE o.order_status IN (:statuses) AND p.model = :model AND p.nid = :nid AND o.created >= :start AND o.created <= :end", array(':statuses' => $args['status'], ':start' => $args['start_date'], ':end' => $args['end_date'], ':model' => $model, ':nid' => $product['nid']))->fetchField();
  358. $revenue = empty($revenue) ? 0 : $revenue;
  359. $gross = db_query("SELECT (SUM(p.price * p.qty) - SUM(p.cost * p.qty)) FROM {uc_order_products} p LEFT JOIN {uc_orders} o ON p.order_id = o.order_id WHERE o.order_status IN (:statuses) AND p.model = :model AND p.nid = :nid AND o.created >= :start AND o.created <= :end", array(':statuses' => $args['status'], ':start' => $args['start_date'], ':end' => $args['end_date'], ':model' => $model, ':nid' => $product['nid']))->fetchField();
  360. $gross = empty($gross) ? 0 : $gross;
  361. $breakdown_product .= "<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$model";
  362. $product_csv .= "\n $model";
  363. $breakdown_sold .= "<br />" . $sold;
  364. $sold_csv .= "\n " . $sold;
  365. $breakdown_revenue .= "<br />" . uc_currency_format($revenue);
  366. $revenue_csv .= "\n " . $revenue;
  367. $breakdown_gross .= "<br />" . uc_currency_format($gross);
  368. $gross_csv .= "\n " . $gross;
  369. }
  370. $product_cell = $product_cell . $breakdown_product;
  371. $sold_cell = '<strong>' . $sold_cell . '</strong>' . $breakdown_sold;
  372. $revenue_cell = '<strong>' . $revenue_cell . '</strong>' . $breakdown_revenue;
  373. $gross_cell = '<strong>' . $gross_cell . '</strong>' . $breakdown_gross;
  374. }
  375. if ($views_column) {
  376. $views = empty($product['totalcount']) ? 0 : $product['totalcount'];
  377. $rows[] = array(
  378. array('data' => $row_cell),
  379. array('data' => $product_cell),
  380. array('data' => $sold_cell),
  381. array('data' => $revenue_cell),
  382. array('data' => $gross_cell),
  383. array('data' => $views),
  384. );
  385. $csv_rows[] = array($row_cell, $product_csv, $sold_csv, $revenue_csv, $gross_csv, $views);
  386. }
  387. else {
  388. $rows[] = array(
  389. array('data' => $row_cell),
  390. array('data' => $product_cell),
  391. array('data' => $sold_cell),
  392. array('data' => $revenue_cell),
  393. array('data' => $gross_cell),
  394. );
  395. $csv_rows[] = array($row_cell, $product_csv, $sold_csv, $revenue_csv, $gross_csv);
  396. }
  397. }
  398. $csv_data = uc_reports_store_csv('uc_products', $csv_rows);
  399. // Build the page output holding the form, table, and CSV export link.
  400. $build['form'] = drupal_get_form('uc_reports_products_custom_form', $args);
  401. $build['report'] = array(
  402. '#theme' => 'table',
  403. '#header' => $header,
  404. '#rows' => $rows,
  405. '#attributes' => array('width' => '100%', 'class' => array('uc-sales-table')),
  406. '#empty' => t('No products found'),
  407. );
  408. $build['pager'] = array(
  409. '#theme' => 'pager',
  410. );
  411. $build['links'] = array(
  412. '#prefix' => '<div class="uc-reports-links">',
  413. '#suffix' => '</div>',
  414. );
  415. $build['links']['export_csv'] = array(
  416. '#markup' => l(t('Export to CSV file.'), 'admin/store/reports/getcsv/' . $csv_data['report'] . '/' . $csv_data['user']),
  417. '#suffix' => '&nbsp;&nbsp;&nbsp;',
  418. );
  419. if (isset($_GET['nopage'])) {
  420. $build['links']['toggle_pager'] = array(
  421. '#markup' => l(t('Show paged records'), 'admin/store/reports/products/custom'),
  422. );
  423. }
  424. else {
  425. $build['links']['toggle_pager'] = array(
  426. '#markup' => l(t('Show all records'), 'admin/store/reports/products/custom', array('query' => array('nopage' => '1'))),
  427. );
  428. }
  429. $build['instructions'] = array('#markup' => '<small>*' . t('Make sure %setting_name is set to %state in the <a href="!url">access log settings page</a> to enable views column.', array('%setting_name' => 'count content views', '%state' => 'enabled', '!url' => url('admin/config/system/statistics', array('query' => array('destination' => 'admin/store/reports/products/custom'))))) . '</small>');
  430. return $build;
  431. }
  432. /**
  433. * Form builder for the custom product report.
  434. *
  435. * @see uc_reports_products_custom_form_validate()
  436. * @see uc_reports_products_custom_form_submit()
  437. * @ingroup forms
  438. */
  439. function uc_reports_products_custom_form($form, &$form_state, $values) {
  440. $form['search'] = array(
  441. '#type' => 'fieldset',
  442. '#title' => t('Customize product report parameters'),
  443. '#description' => t('Adjust these values and update the report to build your custom product report. Once submitted, the report may be bookmarked for easy reference in the future.'),
  444. '#collapsible' => TRUE,
  445. '#collapsed' => TRUE,
  446. );
  447. $form['search']['start_date'] = array(
  448. '#type' => 'date',
  449. '#title' => t('Start date'),
  450. '#default_value' => array(
  451. 'month' => format_date($values['start_date'], 'custom', 'n'),
  452. 'day' => format_date($values['start_date'], 'custom', 'j'),
  453. 'year' => format_date($values['start_date'], 'custom', 'Y'),
  454. ),
  455. );
  456. $form['search']['end_date'] = array(
  457. '#type' => 'date',
  458. '#title' => t('End date'),
  459. '#default_value' => array(
  460. 'month' => format_date($values['end_date'], 'custom', 'n'),
  461. 'day' => format_date($values['end_date'], 'custom', 'j'),
  462. 'year' => format_date($values['end_date'], 'custom', 'Y'),
  463. ),
  464. );
  465. $options = array();
  466. foreach (uc_order_status_list() as $status) {
  467. $options[$status['id']] = $status['title'];
  468. }
  469. $form['search']['status'] = array(
  470. '#type' => 'checkboxes',
  471. '#title' => t('Order statuses'),
  472. '#description' => t('Only orders with selected statuses will be included in the report.'),
  473. '#options' => $options,
  474. '#default_value' => $values['status'],
  475. );
  476. $form['search']['actions'] = array('#type' => 'actions');
  477. $form['search']['actions']['submit'] = array(
  478. '#type' => 'submit',
  479. '#value' => t('Update report'),
  480. );
  481. return $form;
  482. }
  483. /**
  484. * Validation handler for the custom product report.
  485. *
  486. * @see uc_reports_products_custom_form()
  487. * @see uc_reports_products_custom_form_submit()
  488. */
  489. function uc_reports_products_custom_form_validate($form, &$form_state) {
  490. if (empty($form_state['values']['status'])) {
  491. form_set_error('status', t('You must select at least one order status.'));
  492. }
  493. }
  494. /**
  495. * Submission handler for the custom product report.
  496. *
  497. * @see uc_reports_products_custom_form()
  498. * @see uc_reports_products_custom_form_submit()
  499. */
  500. function uc_reports_products_custom_form_submit($form, &$form_state) {
  501. $start_date = mktime(0, 0, 0, $form_state['values']['start_date']['month'], $form_state['values']['start_date']['day'], $form_state['values']['start_date']['year']);
  502. $end_date = mktime(23, 59, 59, $form_state['values']['end_date']['month'], $form_state['values']['end_date']['day'], $form_state['values']['end_date']['year']);
  503. $args = array(
  504. $start_date,
  505. $end_date,
  506. implode(',', array_keys(array_filter($form_state['values']['status']))),
  507. );
  508. $form_state['redirect'] = array('admin/store/reports/products/custom/' . implode('/', $args));
  509. }
  510. /**
  511. * Return a themed table for product reports.
  512. *
  513. * Straight duplication of theme_table, but our row handling is different.
  514. *
  515. * @see theme_table()
  516. * @ingroup themeable
  517. */
  518. function theme_uc_reports_product_table($variables) {
  519. $header = $variables['header'];
  520. $rows = $variables['rows'];
  521. $attributes = $variables['attributes'];
  522. $caption = $variables['caption'];
  523. $colgroups = $variables['colgroups'];
  524. $sticky = $variables['sticky'];
  525. $empty = $variables['empty'];
  526. // Add sticky headers, if applicable.
  527. if (count($header) && $sticky) {
  528. drupal_add_js('misc/tableheader.js');
  529. // Add 'sticky-enabled' class to the table to identify it for JS.
  530. // This is needed to target tables constructed by this function.
  531. $attributes['class'][] = 'sticky-enabled';
  532. }
  533. $output = '<table' . drupal_attributes($attributes) . ">\n";
  534. if (isset($caption)) {
  535. $output .= '<caption>' . $caption . "</caption>\n";
  536. }
  537. // Format the table columns:
  538. if (count($colgroups)) {
  539. foreach ($colgroups as $number => $colgroup) {
  540. $attributes = array();
  541. // Check if we're dealing with a simple or complex column
  542. if (isset($colgroup['data'])) {
  543. foreach ($colgroup as $key => $value) {
  544. if ($key == 'data') {
  545. $cols = $value;
  546. }
  547. else {
  548. $attributes[$key] = $value;
  549. }
  550. }
  551. }
  552. else {
  553. $cols = $colgroup;
  554. }
  555. // Build colgroup
  556. if (is_array($cols) && count($cols)) {
  557. $output .= ' <colgroup' . drupal_attributes($attributes) . '>';
  558. $i = 0;
  559. foreach ($cols as $col) {
  560. $output .= ' <col' . drupal_attributes($col) . ' />';
  561. }
  562. $output .= " </colgroup>\n";
  563. }
  564. else {
  565. $output .= ' <colgroup' . drupal_attributes($attributes) . " />\n";
  566. }
  567. }
  568. }
  569. // Add the 'empty' row message if available.
  570. if (!count($rows) && $empty) {
  571. $header_count = 0;
  572. foreach ($header as $header_cell) {
  573. if (is_array($header_cell)) {
  574. $header_count += isset($header_cell['colspan']) ? $header_cell['colspan'] : 1;
  575. }
  576. else {
  577. $header_count++;
  578. }
  579. }
  580. $rows[] = array(array('data' => $empty, 'colspan' => $header_count, 'class' => array('empty', 'message')));
  581. }
  582. // Format the table header:
  583. if (count($header)) {
  584. $ts = tablesort_init($header);
  585. // HTML requires that the thead tag has tr tags in it follwed by tbody
  586. // tags. Using ternary operator to check and see if we have any rows.
  587. $output .= (count($rows) ? ' <thead><tr>' : ' <tr>');
  588. foreach ($header as $cell) {
  589. $cell = tablesort_header($cell, $header, $ts);
  590. $output .= _theme_table_cell($cell, TRUE);
  591. }
  592. // Using ternary operator to close the tags based on whether or not there are rows
  593. $output .= (count($rows) ? " </tr></thead>\n" : "</tr>\n");
  594. }
  595. else {
  596. $ts = array();
  597. }
  598. // Format the table rows:
  599. if (count($rows)) {
  600. $output .= "<tbody>\n";
  601. $flip = array('even' => 'odd', 'odd' => 'even');
  602. $class = 'even';
  603. foreach ($rows as $number => $row) {
  604. $attributes = array();
  605. // Check if we're dealing with a simple or complex row
  606. if (isset($row['data'])) {
  607. foreach ($row as $key => $value) {
  608. if ($key == 'data') {
  609. $cells = $value;
  610. }
  611. // The following elseif clause is where we differ from theme_table()
  612. elseif ($key == 'primary') {
  613. $class = $flip[$class];
  614. }
  615. else {
  616. $attributes[$key] = $value;
  617. }
  618. }
  619. }
  620. else {
  621. $cells = $row;
  622. }
  623. if (count($cells)) {
  624. // Add odd/even class
  625. // We don't flip here like theme_table(), because we did that above.
  626. $attributes['class'][] = $class;
  627. // Build row
  628. $output .= ' <tr' . drupal_attributes($attributes) . '>';
  629. $i = 0;
  630. foreach ($cells as $cell) {
  631. $cell = tablesort_cell($cell, $header, $ts, $i++);
  632. $output .= _theme_table_cell($cell);
  633. }
  634. $output .= " </tr>\n";
  635. }
  636. }
  637. $output .= "</tbody>\n";
  638. }
  639. $output .= "</table>\n";
  640. return $output;
  641. }
  642. /**
  643. * Displays the sales summary report.
  644. */
  645. function uc_reports_sales_summary() {
  646. $order_statuses = uc_reports_order_statuses();
  647. $date_day_of_month = date('j');
  648. $date_month = date('n');
  649. $month_start = mktime(0, 0, 0, $date_month, 1);
  650. $month_end = mktime(0, 0, 0, $date_month + 1, 1) - 1;
  651. $today_start = mktime(0, 0, 0);
  652. $today_end = mktime(23, 59, 59);
  653. // Build the report table header.
  654. $header = array(t('Sales data'), t('Number of orders'), t('Total revenue'), t('Average order'));
  655. // Calculate and add today's sales summary to the report table.
  656. $today = uc_reports_get_sales($today_start);
  657. $rows[] = array(
  658. l(t('Today, !date', array('!date' => format_date($today_start, 'uc_store'))), 'admin/store/orders/search/results/0/0/0/0/0/0/' . $today_start . '/' . $today_end),
  659. $today['total'],
  660. array('data' => array('#theme' => 'uc_price', '#price' => $today['income'])),
  661. array('data' => array('#theme' => 'uc_price', '#price' => $today['average'])),
  662. );
  663. // Calculate and add yesterday's sales summary to the report table.
  664. $yesterday = uc_reports_get_sales($today_start - 86400);
  665. $rows[] = array(
  666. l(t('Yesterday, !date', array('!date' => format_date($today_start - 86400, 'uc_store'))), 'admin/store/orders/search/results/0/0/0/0/0/0/' . ($today_start - 86400) . '/' . ($today_end - 86400)),
  667. $yesterday['total'],
  668. array('data' => array('#theme' => 'uc_price', '#price' => $yesterday['income'])),
  669. array('data' => array('#theme' => 'uc_price', '#price' => $yesterday['average'])),
  670. );
  671. // Get the sales report for the month.
  672. $month = uc_reports_get_sales($month_start, 'month');
  673. $month_title = format_date($month_start, 'custom', 'M Y');
  674. // Add the month-to-date details to the report table.
  675. $rows[] = array(
  676. l(t('Month-to-date, @month', array('@month' => $month_title)), 'admin/store/orders/search/results/0/0/0/0/0/0/' . $month_start . '/' . $month_end),
  677. $month['total'],
  678. array('data' => array('#theme' => 'uc_price', '#price' => $month['income'])),
  679. array('data' => array('#theme' => 'uc_price', '#price' => $month['average'])),
  680. );
  681. // Calculate the daily averages for the month.
  682. $daily_orders = round($month['total'] / $date_day_of_month, 2);
  683. $daily_revenue = round($month['income'] / $date_day_of_month, 2);
  684. if ($daily_orders > 0) {
  685. $daily_average = round($daily_revenue / $daily_orders, 2);
  686. }
  687. else {
  688. $daily_average = 0;
  689. }
  690. // Add the daily averages for the month to the report table.
  691. $rows[] = array(
  692. t('Daily average for @month', array('@month' => $month_title)),
  693. $daily_orders,
  694. array('data' => array('#theme' => 'uc_price', '#price' => $daily_revenue)),
  695. '',
  696. );
  697. // Store the number of days remaining in the month.
  698. $remaining_days = date('t') - $date_day_of_month;
  699. // Add the projected totals for the month to the report table.
  700. $rows[] = array(
  701. t('Projected totals for @date', array('@date' => $month_title)),
  702. round($month['total'] + ($daily_orders * $remaining_days), 2),
  703. array('data' => array('#theme' => 'uc_price', '#price' => round($month['income'] + ($daily_revenue * $remaining_days), 2))),
  704. '',
  705. );
  706. // Add the sales data report table to the output.
  707. $build['sales'] = array(
  708. '#theme' => 'table',
  709. '#header' => $header,
  710. '#rows' => $rows,
  711. '#attributes' => array('class' => array('uc-sales-table')),
  712. );
  713. // Build the header statistics table header.
  714. $header = array(array('data' => t('Statistics'), 'width' => '50%'), '');
  715. $rows = array(
  716. array(array('data' => t('Grand total sales')), array('data' => array('#theme' => 'uc_price', '#price' => db_query("SELECT SUM(order_total) FROM {uc_orders} WHERE order_status IN (:statuses)", array(':statuses' => $order_statuses))->fetchField()))),
  717. array(array('data' => t('Customers total')), array('data' => db_query("SELECT COUNT(DISTINCT uid) FROM {uc_orders} WHERE order_status IN (:statuses)", array(':statuses' => $order_statuses))->fetchField())),
  718. array(array('data' => t('New customers today')), array('data' => db_query("SELECT COUNT(DISTINCT uid) FROM {uc_orders} WHERE order_status IN (:statuses) AND :start <= created AND created <= :end", array(':statuses' => $order_statuses, ':start' => $today_start, ':end' => $today_end))->fetchField())),
  719. array(array('data' => t('Online customers')), array('data' => db_query("SELECT COUNT(DISTINCT s.uid) FROM {sessions} s LEFT JOIN {uc_orders} o ON s.uid = o.uid WHERE s.uid > 0 AND o.order_status IN (:statuses)", array(':statuses' => $order_statuses))->fetchField())),
  720. );
  721. // Add the statistics table to the output.
  722. $build['statistics'] = array(
  723. '#theme' => 'table',
  724. '#header' => $header,
  725. '#rows' => $rows,
  726. '#attributes' => array('width' => '100%', 'class' => array('uc-sales-table')),
  727. );
  728. // Build the total orders by status table header.
  729. $header = array(array('data' => t('Total orders by status'), 'width' => '50%'), '');
  730. $rows = array();
  731. $unknown = 0;
  732. // Loop through the order statuses with their total number of orders.
  733. $result = db_query("SELECT s.order_status_id, order_status, s.title, s.weight, COUNT(o.order_status) as order_count FROM {uc_orders} o LEFT JOIN {uc_order_statuses} s ON s.order_status_id = o.order_status GROUP BY s.order_status_id, order_status, s.title, s.weight ORDER BY s.weight DESC");
  734. while ($status = $result->fetchAssoc()) {
  735. if (!empty($status['title'])) {
  736. // Add the total number of orders with this status to the table.
  737. $rows[] = array(
  738. l($status['title'], 'admin/store/orders/view', array('query' => array('order_status' => $status['order_status_id']))),
  739. $status['order_count'],
  740. );
  741. }
  742. else {
  743. // Keep track of the count of orders with an unknown status.
  744. $unknown += $status['order_count'];
  745. }
  746. }
  747. // Add the unknown status count to the table.
  748. if ($unknown > 0) {
  749. $rows[] = array(
  750. t('Unknown status'),
  751. $unknown,
  752. );
  753. }
  754. // Add the total orders by status table to the output.
  755. $build['orders'] = array(
  756. '#theme' => 'table',
  757. '#header' => $header,
  758. '#rows' => $rows,
  759. '#attributes' => array('class' => array('uc-sales-table')),
  760. );
  761. return $build;
  762. }
  763. /**
  764. * Displays the yearly sales report form and table.
  765. */
  766. function uc_reports_sales_year() {
  767. $order_statuses = uc_reports_order_statuses();
  768. // Get the year for the report from the URL.
  769. if (intval(arg(5)) == 0) {
  770. $year = date('Y');
  771. }
  772. else {
  773. $year = arg(5);
  774. }
  775. // Build the header for the report table.
  776. $header = array(t('Month'), t('Number of orders'), t('Total revenue'), t('Average order'));
  777. // Build the header to the CSV export.
  778. $csv_rows = array(array(t('Month'), t('Number of orders'), t('Total revenue'), t('Average order')));
  779. // For each month of the year...
  780. for ($i = 1; $i <= 12; $i++) {
  781. // Calculate the start and end timestamps for the month in local time.
  782. $month_start = mktime(0, 0, 0, $i, 1, $year);
  783. $month_end = mktime(23, 59, 59, $i + 1, 0, $year);
  784. // Get the sales report for the month.
  785. $month_sales = uc_reports_get_sales($month_start, 'month');
  786. // Calculate the average order total for the month.
  787. if ($month_sales['total'] != 0) {
  788. $month_average = round($month_sales['income'] / $month_sales['total'], 2);
  789. }
  790. else {
  791. $month_average = 0;
  792. }
  793. // Add the month's row to the report table.
  794. $rows[] = array(
  795. l(date('M Y', $month_start), 'admin/store/orders/search/results/0/0/0/0/0/0/' . $month_start . '/' . $month_end),
  796. $month_sales['total'],
  797. uc_currency_format($month_sales['income']),
  798. uc_currency_format($month_average),
  799. );
  800. // Add the data to the CSV export.
  801. $csv_rows[] = array(
  802. date('M Y', $month_start),
  803. $month_sales['total'],
  804. $month_sales['income'],
  805. $month_average,
  806. );
  807. }
  808. // Calculate the start and end timestamps for the year in local time.
  809. $year_start = mktime(0, 0, 0, 1, 1, $year);
  810. $year_end = mktime(23, 59, 59, 1, 0, $year + 1);
  811. // Get the sales report for the year.
  812. $year_sales = uc_reports_get_sales($year_start, 'year');
  813. // Calculate the average order total for the year.
  814. if ($year_sales['total'] != 0) {
  815. $year_average = round($year_sales['income'] / $year_sales['total'], 2);
  816. }
  817. else {
  818. $year_average = 0;
  819. }
  820. // Add the total row to the report table.
  821. $rows[] = array(
  822. l(t('Total @year', array('@year' => $year)), 'admin/store/orders/search/results/0/0/0/0/0/0/' . $year_start . '/' . $year_end),
  823. $year_sales['total'],
  824. uc_currency_format($year_sales['income']),
  825. uc_currency_format($year_average),
  826. );
  827. // Add the total data to the CSV export.
  828. $csv_rows[] = array(
  829. t('Total @year', array('@year' => $year)),
  830. $year_sales['total'],
  831. $year_sales['income'],
  832. $year_average,
  833. );
  834. // Cache the CSV export.
  835. $csv_data = uc_reports_store_csv('uc_sales_yearly', $csv_rows);
  836. // Build the page output holding the form, table, and CSV export link.
  837. $build['form'] = drupal_get_form('uc_reports_sales_year_form', $year);
  838. $build['report'] = array(
  839. '#theme' => 'table',
  840. '#header' => $header,
  841. '#rows' => $rows,
  842. '#attributes' => array('width' => '100%', 'class' => array('uc-sales-table')),
  843. );
  844. $build['links'] = array(
  845. '#prefix' => '<div class="uc-reports-links">',
  846. '#suffix' => '</div>',
  847. );
  848. $build['links']['export_csv'] = array(
  849. '#markup' => l(t('Export to CSV file.'), 'admin/store/reports/getcsv/' . $csv_data['report'] . '/' . $csv_data['user']),
  850. );
  851. return $build;
  852. }
  853. /**
  854. * Form to specify a year for the yearly sales report.
  855. *
  856. * @see uc_reports_sales_year_form_submit()
  857. * @ingroup forms
  858. */
  859. function uc_reports_sales_year_form($form, &$form_state, $year) {
  860. $form['year'] = array(
  861. '#type' => 'textfield',
  862. '#title' => t('Sales year'),
  863. '#default_value' => $year,
  864. '#maxlength' => 4,
  865. '#size' => 4,
  866. '#prefix' => '<div class="sales-year">',
  867. '#suffix' => '</div>',
  868. );
  869. $form['actions'] = array('#type' => 'actions');
  870. $form['actions']['submit'] = array(
  871. '#type' => 'submit',
  872. '#value' => t('View'),
  873. '#prefix' => '<div class="sales-year">',
  874. '#suffix' => '</div>',
  875. );
  876. return $form;
  877. }
  878. /**
  879. * Submit handler for uc_reports_sales_year_form().
  880. *
  881. * @see uc_reports_sales_year_form()
  882. */
  883. function uc_reports_sales_year_form_submit($form, &$form_state) {
  884. $form_state['redirect'] = 'admin/store/reports/sales/year/' . $form_state['values']['year'];
  885. }
  886. /**
  887. * Displays the custom sales report form and table.
  888. */
  889. function uc_reports_sales_custom() {
  890. // Use default report parameters if we don't detect values in the URL.
  891. if (arg(5) == '') {
  892. $args = array(
  893. 'start_date' => mktime(0, 0, 0, date('n'), 1, date('Y') - 1),
  894. 'end_date' => REQUEST_TIME,
  895. 'length' => 'month',
  896. 'status' => uc_reports_order_statuses(),
  897. 'detail' => FALSE,
  898. );
  899. }
  900. else {
  901. $args = array(
  902. 'start_date' => arg(5),
  903. 'end_date' => arg(6),
  904. 'length' => arg(7),
  905. 'status' => explode(',', arg(8)),
  906. 'detail' => arg(9),
  907. );
  908. }
  909. // Build the header for the report table.
  910. $header = array(t('Date'), t('Number of orders'), t('Products sold'), t('Total revenue'));
  911. // Build the header to the CSV export.
  912. $csv_rows = array(array(t('Date'), t('Number of orders'), t('Products sold'), t('Total revenue')));
  913. // Grab the subreports based on the date range and the report breakdown.
  914. $subreports = uc_reports_subreport_intervals($args['start_date'], $args['end_date'], $args['length']);
  915. // Loop through the subreports and build the report table.
  916. foreach ($subreports as $subreport) {
  917. $product_data = '';
  918. $product_csv = '';
  919. $order_data = '';
  920. $order_csv = '';
  921. // Create the date title for the subreport.
  922. if ($args['length'] == 'day') {
  923. $date = format_date($subreport['start'], 'custom', variable_get('date_format_uc_store', 'm/d/Y') . ' - D');
  924. }
  925. else {
  926. $date = format_date($subreport['start'], 'uc_store') . ' - ' . format_date($subreport['end'], 'uc_store');
  927. }
  928. // Build the order data for the subreport.
  929. $result = db_query("SELECT COUNT(*) as count, title FROM {uc_orders} LEFT JOIN {uc_order_statuses} ON order_status_id = order_status WHERE :start <= created AND created <= :end AND order_status IN (:statuses) GROUP BY order_status, {uc_order_statuses}.title, {uc_order_statuses}.weight ORDER BY weight ASC", array(':statuses' => $args['status'], ':start' => $subreport['start'], ':end' => $subreport['end']));
  930. $statuses = array();
  931. // Put the order counts into an array by status.
  932. foreach ($result as $status) {
  933. $statuses[] = t('!count - @title', array('!count' => $status->count, '@title' => $status->title));
  934. }
  935. $order_data = implode('<br />', $statuses);
  936. $order_csv = implode("\n", $statuses);
  937. // Build the product data for the subreport.
  938. if ($args['detail']) {
  939. // Grab the detailed product breakdown if selected.
  940. $result = db_query("SELECT SUM(op.qty) as count, n.title, n.nid FROM {uc_order_products} op LEFT JOIN {uc_orders} o ON o.order_id = op.order_id LEFT JOIN {node} n ON n.nid = op.nid WHERE :start <= o.created AND o.created <= :end AND o.order_status IN (:statuses) GROUP BY n.nid ORDER BY count DESC, n.title ASC", array(':statuses' => $args['status'], ':start' => $subreport['start'], ':end' => $subreport['end']));
  941. foreach ($result as $product_breakdown) {
  942. $product_data .= $product_breakdown->count . ' x ' . l($product_breakdown->title, 'node/' . $product_breakdown->nid) . "<br />\n";
  943. $product_csv .= $product_breakdown->count . ' x ' . $product_breakdown->title . "\n";
  944. }
  945. }
  946. else {
  947. // Otherwise just display the total number of products sold.
  948. $product_data = db_query("SELECT SUM(qty) FROM {uc_orders} o LEFT JOIN {uc_order_products} op ON o.order_id = op.order_id WHERE :start <= created AND created <= :end AND order_status IN (:statuses)", array(':statuses' => $args['status'], ':start' => $subreport['start'], ':end' => $subreport['end']))->fetchField();
  949. $product_csv = $product_data;
  950. }
  951. // Tally up the revenue from the orders.
  952. $revenue_count = db_query("SELECT SUM(order_total) FROM {uc_orders} WHERE :start <= created AND created <= :end AND order_status IN (:statuses)", array(':statuses' => $args['status'], ':start' => $subreport['start'], ':end' => $subreport['end']))->fetchField();
  953. // Add the subreport's row to the report table.
  954. $rows[] = array(
  955. $date,
  956. empty($order_data) ? '0' : $order_data,
  957. empty($product_data) ? '0' : $product_data,
  958. uc_currency_format($revenue_count),
  959. );
  960. // Add the data to the CSV export.
  961. $csv_rows[] = array(
  962. $date,
  963. empty($order_csv) ? '0' : $order_csv,
  964. empty($product_csv) ? '0' : $product_csv,
  965. $revenue_count,
  966. );
  967. }
  968. // Calculate the totals for the report.
  969. $order_total = db_query("SELECT COUNT(*) FROM {uc_orders} WHERE :start <= created AND created <= :end AND order_status IN (:statuses)", array(':statuses' => $args['status'], ':start' => $args['start_date'], ':end' => $args['end_date']))->fetchField();
  970. $product_total = db_query("SELECT SUM(qty) FROM {uc_orders} o LEFT JOIN {uc_order_products} op ON o.order_id = op.order_id WHERE :start <= created AND created <= :end AND order_status IN (:statuses)", array(':statuses' => $args['status'], ':start' => $args['start_date'], ':end' => $args['end_date']))->fetchField();
  971. $revenue_total = db_query("SELECT SUM(order_total) FROM {uc_orders} WHERE :start <= created AND created <= :end AND order_status IN (:statuses)", array(':statuses' => $args['status'], ':start' => $args['start_date'], ':end' => $args['end_date']))->fetchField();
  972. // Add the total row to the report table.
  973. $rows[] = array(
  974. t('Total'),
  975. $order_total,
  976. $product_total,
  977. uc_currency_format($revenue_total),
  978. );
  979. // Add the total data to the CSV export.
  980. $csv_rows[] = array(
  981. t('Total'),
  982. $order_total,
  983. $product_total,
  984. $revenue_total,
  985. );
  986. // Cache the CSV export.
  987. $csv_data = uc_reports_store_csv('uc_sales_custom', $csv_rows);
  988. // Build the page output holding the form, table, and CSV export link.
  989. $build['form'] = drupal_get_form('uc_reports_sales_custom_form', $args, $args['status']);
  990. $build['report'] = array(
  991. '#theme' => 'table',
  992. '#header' => $header,
  993. '#rows' => $rows,
  994. '#attributes' => array('width' => '100%', 'class' => array('uc-sales-table')),
  995. );
  996. $build['links'] = array(
  997. '#prefix' => '<div class="uc-reports-links">',
  998. '#suffix' => '</div>',
  999. );
  1000. $build['links']['export_csv'] = array(
  1001. '#markup' => l(t('Export to CSV file.'), 'admin/store/reports/getcsv/' . $csv_data['report'] . '/' . $csv_data['user']),
  1002. );
  1003. return $build;
  1004. }
  1005. /**
  1006. * Form builder for the custom sales report.
  1007. *
  1008. * @see uc_reports_sales_custom_form_validate()
  1009. * @see uc_reports_sales_custom_form_submit()
  1010. * @ingroup forms
  1011. */
  1012. function uc_reports_sales_custom_form($form, &$form_state, $values, $statuses) {
  1013. $form['search'] = array(
  1014. '#type' => 'fieldset',
  1015. '#title' => t('Customize sales report parameters'),
  1016. '#description' => t('Adjust these values and update the report to build your custom sales summary. Once submitted, the report may be bookmarked for easy reference in the future.'),
  1017. '#collapsible' => TRUE,
  1018. '#collapsed' => TRUE,
  1019. );
  1020. $form['search']['start_date'] = array(
  1021. '#type' => 'date',
  1022. '#title' => t('Start date'),
  1023. '#default_value' => array(
  1024. 'month' => format_date($values['start_date'], 'custom', 'n'),
  1025. 'day' => format_date($values['start_date'], 'custom', 'j'),
  1026. 'year' => format_date($values['start_date'], 'custom', 'Y'),
  1027. ),
  1028. );
  1029. $form['search']['end_date'] = array(
  1030. '#type' => 'date',
  1031. '#title' => t('End date'),
  1032. '#default_value' => array(
  1033. 'month' => format_date($values['end_date'], 'custom', 'n'),
  1034. 'day' => format_date($values['end_date'], 'custom', 'j'),
  1035. 'year' => format_date($values['end_date'], 'custom', 'Y'),
  1036. ),
  1037. );
  1038. $form['search']['length'] = array(
  1039. '#type' => 'select',
  1040. '#title' => t('Results breakdown'),
  1041. '#description' => t('Large daily reports may take a long time to display.'),
  1042. '#options' => array(
  1043. 'day' => t('daily'),
  1044. 'week' => t('weekly'),
  1045. 'month' => t('monthly'),
  1046. 'year' => t('yearly'),
  1047. ),
  1048. '#default_value' => $values['length'],
  1049. );
  1050. $options = array();
  1051. foreach (uc_order_status_list() as $status) {
  1052. $options[$status['id']] = $status['title'];
  1053. }
  1054. if ($statuses === FALSE) {
  1055. $statuses = uc_reports_order_statuses();
  1056. }
  1057. $form['search']['status'] = array(
  1058. '#type' => 'checkboxes',
  1059. '#title' => t('Order statuses'),
  1060. '#description' => t('Only orders with selected statuses will be included in the report.'),
  1061. '#options' => $options,
  1062. '#default_value' => $statuses,
  1063. );
  1064. $form['search']['detail'] = array(
  1065. '#type' => 'checkbox',
  1066. '#title' => t('Show a detailed list of products ordered.'),
  1067. '#default_value' => $values['detail'],
  1068. );
  1069. $form['search']['actions'] = array('#type' => 'actions');
  1070. $form['search']['actions']['submit'] = array(
  1071. '#type' => 'submit',
  1072. '#value' => t('Update report'),
  1073. );
  1074. return $form;
  1075. }
  1076. /**
  1077. * Ensure an order status was selected.
  1078. *
  1079. * @see uc_reports_sales_custom_form()
  1080. * @see uc_reports_sales_custom_form_submit()
  1081. */
  1082. function uc_reports_sales_custom_form_validate($form, &$form_state) {
  1083. if (empty($form_state['values']['status'])) {
  1084. form_set_error('status', t('You must select at least one order status.'));
  1085. }
  1086. }
  1087. /**
  1088. * Submission handler for uc_reports_sales_custom_form().
  1089. *
  1090. * @see uc_reports_sales_custom_form()
  1091. * @see uc_reports_sales_custom_form_validate()
  1092. */
  1093. function uc_reports_sales_custom_form_submit($form, &$form_state) {
  1094. // Build the start and end dates from the form.
  1095. $start_date = mktime(0, 0, 0, $form_state['values']['start_date']['month'], $form_state['values']['start_date']['day'], $form_state['values']['start_date']['year']);
  1096. $end_date = mktime(23, 59, 59, $form_state['values']['end_date']['month'], $form_state['values']['end_date']['day'], $form_state['values']['end_date']['year']);
  1097. $args = array(
  1098. $start_date,
  1099. $end_date,
  1100. $form_state['values']['length'],
  1101. implode(',', array_keys(array_filter($form_state['values']['status']))),
  1102. $form_state['values']['detail'],
  1103. );
  1104. $form_state['redirect'] = 'admin/store/reports/sales/custom/' . implode('/', $args);
  1105. }
  1106. /**
  1107. * Stores a CSV file for a report in Drupal's cache to be retrieved later.
  1108. *
  1109. * @param $report_id
  1110. * A unique string that identifies the report of the CSV file.
  1111. * @param $rows
  1112. * The rows (table header included) that make CSV file.
  1113. *
  1114. * @return
  1115. * An array containing the values need to build URL that return the CSV file
  1116. * of the report and the CSV data itself.
  1117. */
  1118. function uc_reports_store_csv($report_id, $rows) {
  1119. global $user;
  1120. $csv_output = '';
  1121. $user_id = empty($user->uid) ? session_id() : $user->uid;
  1122. foreach ($rows as $row) {
  1123. foreach ($row as $index => $column) {
  1124. $row[$index] = '"' . str_replace('"', '""', $column) . '"';
  1125. }
  1126. $csv_output .= implode(',', $row) . "\n";
  1127. }
  1128. cache_set('uc_reports_' . $report_id . '_' . $user_id, $csv_output, 'cache', REQUEST_TIME + 86400);
  1129. return array('user' => $user_id, 'report' => $report_id, 'csv' => $csv_output);
  1130. }
  1131. /**
  1132. * Retrieves a cached CSV report & send its data.
  1133. *
  1134. * @param $report_id
  1135. * A unique string that identifies the specific report CSV to retrieve.
  1136. * @param $user_id
  1137. * The user id to who's retrieving the report:
  1138. * - uid: Equals uid for authenticated users.
  1139. * - sid: Equals session_id for anonymous users.
  1140. */
  1141. function uc_reports_get_csv($report_id, $user_id) {
  1142. global $user;
  1143. $user_check = empty($user->uid) ? session_id() : $user->uid;
  1144. $csv_data = cache_get('uc_reports_' . $report_id . '_' . $user_id, 'cache');
  1145. if (!$csv_data || $user_id != $user_check) {
  1146. drupal_set_message(t("The CSV data could not be retrieved. It's possible the data might have expired. Refresh the report page and try to retrieve the CSV file again."), 'error');
  1147. drupal_not_found();
  1148. exit();
  1149. }
  1150. else {
  1151. ob_end_clean();
  1152. $http_headers = array(
  1153. 'Pragma' => 'private',
  1154. 'Expires' => '0',
  1155. 'Cache-Control' => 'private, must-revalidate',
  1156. 'Content-Transfer-Encoding' => 'binary',
  1157. 'Content-Length' => strlen($csv_data->data),
  1158. 'Content-Disposition' => 'attachment; filename="' . $report_id . '.csv"',
  1159. 'Content-Type' => 'text/csv'
  1160. );
  1161. foreach ($http_headers as $header => $value) {
  1162. $value = preg_replace('/\r?\n(?!\t| )/', '', $value);
  1163. drupal_add_http_header($header, $value);
  1164. }
  1165. print $csv_data->data;
  1166. exit();
  1167. }
  1168. }
  1169. /**
  1170. * Returns sales that occurred in a given time period.
  1171. *
  1172. * @param $time
  1173. * A UNIX timestamp representing the time in which to get sales data.
  1174. * @param $interval
  1175. * The amount of time over which to count sales (e.g. [1] day, month, year).
  1176. *
  1177. * @return
  1178. * An associative array containing information about sales:
  1179. * - date: A string representing the day counting was started.
  1180. * - income: The total revenue that occurred during the time period.
  1181. * - total: The total number of orders completed during the time period.
  1182. * - average: The average revenue produced for each order.
  1183. */
  1184. function uc_reports_get_sales($start, $interval = 'day') {
  1185. // Add one to the granularity chosen, and use it to calc the new time.
  1186. $end = strtotime('+1 ' . $interval, $start) - 1;
  1187. // Set up the default SQL for getting orders with the proper status
  1188. // within this period.
  1189. $order_statuses = uc_reports_order_statuses();
  1190. // Get the total value of the orders.
  1191. $output = array('income' => 0);
  1192. $orders = db_query("SELECT o.order_total FROM {uc_orders} o WHERE o.order_status IN (:statuses) AND :start <= created AND created <= :end", array(':statuses' => $order_statuses, ':start' => $start, ':end' => $end));
  1193. while ($order = $orders->fetchObject()) {
  1194. $output['income'] += $order->order_total;
  1195. }
  1196. // Get the total amount of orders.
  1197. $count = db_query("SELECT COUNT(o.order_total) FROM {uc_orders} o WHERE o.order_status IN (:statuses) AND :start <= created AND created <= :end", array(':statuses' => $order_statuses, ':start' => $start, ':end' => $end))->fetchField();
  1198. $output['total'] = $count;
  1199. // Average for this period.
  1200. $output['average'] = ($count != 0) ? round($output['income'] / $count, 2) : 0;
  1201. return $output;
  1202. }
  1203. /**
  1204. * Returns a list of timespans for subreports over that report's time span.
  1205. *
  1206. * To be used with a given time span for a report and specified interval for
  1207. * subreports.
  1208. *
  1209. * @param $start
  1210. * A UNIX timestamp representing the time to start the report.
  1211. * @param $end
  1212. * A UNIX timestamp representing the time to end the report.
  1213. * @param $interval
  1214. * Text representing the time span of the subreport (e.g. 'day', 'week').
  1215. *
  1216. * @return
  1217. * An array of keyed arrays with the following values:
  1218. * - start: The starting point of the sub report.
  1219. * - end: The ending point of the sub report.
  1220. */
  1221. function uc_reports_subreport_intervals($start, $report_end, $interval) {
  1222. $subreports = array();
  1223. while ($start < $report_end) {
  1224. $end = strtotime('+1 ' . $interval, $start) - 1;
  1225. $subreports[] = array(
  1226. 'start' => $start,
  1227. 'end' => min($end, $report_end),
  1228. );
  1229. $start = $end + 1;
  1230. }
  1231. return $subreports;
  1232. }