IR_Neatly_Reporting - Version 1.1.0.0

Version Notes

You can now view total sales by product SKU, category or attribute using the "Product Search" feature.

Download this release

Release Info

Developer Jon Leighton
Extension IR_Neatly_Reporting
Version 1.1.0.0
Comparing to
See all releases


Code changes from version 1.0.1.0 to 1.1.0.0

app/code/community/IR/Neatly/.DS_Store DELETED
Binary file
app/code/community/IR/Neatly/Helper/Data.php CHANGED
@@ -6,7 +6,7 @@
6
  */
7
  class IR_Neatly_Helper_Data extends Mage_Core_Helper_Data
8
  {
9
- const VERSION = '1.0.0.2';
10
 
11
  /**
12
  * Checks whether JSON can be rendered at the /neatly endpoint.
6
  */
7
  class IR_Neatly_Helper_Data extends Mage_Core_Helper_Data
8
  {
9
+ const VERSION = '1.1.0.0';
10
 
11
  /**
12
  * Checks whether JSON can be rendered at the /neatly endpoint.
app/code/community/IR/Neatly/Model/Reports/Reporting.php CHANGED
@@ -1,4 +1,20 @@
1
  <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  class IR_Neatly_Model_Reports_Reporting extends IR_Neatly_Model_Reports_Abstract
4
  {
@@ -243,6 +259,229 @@ class IR_Neatly_Model_Reports_Reporting extends IR_Neatly_Model_Reports_Abstract
243
  return $customers;
244
  }
245
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  /**
247
  * Check to see whether the passed comparison operator is valid.
248
  *
@@ -267,4 +506,127 @@ class IR_Neatly_Model_Reports_Reporting extends IR_Neatly_Model_Reports_Abstract
267
 
268
  return $operator;
269
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  }
1
  <?php
2
+ function print_d()
3
+ {
4
+ echo "<pre>\n";
5
+ foreach (func_get_args() as $object) {
6
+ if (is_null($object)) {
7
+ var_dump($object);
8
+ } else {
9
+ print_r($object);
10
+ }
11
+ echo "\n";
12
+ }
13
+ echo "</pre>";
14
+ exit;
15
+ }
16
+
17
+ use IR_Neatly_Exception_Api as ApiException;
18
 
19
  class IR_Neatly_Model_Reports_Reporting extends IR_Neatly_Model_Reports_Abstract
20
  {
259
  return $customers;
260
  }
261
 
262
+ /**
263
+ * Get sales statistics by type.
264
+ *
265
+ * @param array $options
266
+ * @return array
267
+ */
268
+ public function getSalesByTypeStats($options = array())
269
+ {
270
+ $options = array_merge(array(
271
+ 'sku' => '',
272
+ 'category' => '',
273
+ 'manufacturer' => '',
274
+ 'date_format' => 'Y-m-d',
275
+ 'status' => 'complete',
276
+ 'group' => false,
277
+ 'categories' => array(),
278
+ 'attributes' => array(),
279
+ ), $this->options, $options);
280
+
281
+ $c = Mage::getSingleton('core/resource')->getConnection('default_write');
282
+
283
+ $onWhere = array();
284
+
285
+ if ($options['from']) {
286
+ $onWhere[] = sprintf('DATE(sfoi.created_at) >= %s', $c->quote($this->options['from']));
287
+ }
288
+
289
+ if ($options['to']) {
290
+ $onWhere[] = sprintf('DATE(sfoi.created_at) <= %s', $c->quote($this->options['to']));
291
+ }
292
+
293
+ $collection = Mage::getModel('catalog/product')->getCollection()
294
+ ->joinTable(
295
+ array('sfoi' => 'sales/order_item'),
296
+ 'product_id = entity_id',
297
+ array('p_id' => 'product_id'),
298
+ implode(" AND ", $onWhere) ?: null
299
+ );
300
+
301
+
302
+ $query = $collection->getSelect();
303
+
304
+ // if sku set.
305
+ if ($options['sku']) {
306
+ $query->where('sfoi.sku = ?', $options['sku']);
307
+ } else {
308
+ // join any attributes and categories set.
309
+ $this->joinAttributes($query, $options['attributes'])
310
+ ->joinCategories($query, $options['categories']);
311
+ }
312
+
313
+ if ($options['status']) {
314
+ $salesOrderTbl = $this->resource->getTableName('sales/order');
315
+ $query->join(array('sfo' => $salesOrderTbl), 'sfo.entity_id = sfoi.order_id', array())
316
+ ->where('`sfo`.`status` = ?', $options['status']);
317
+ }
318
+
319
+ $columns = array(
320
+ 'total_count' => 'SUM(`sfoi`.`qty_ordered`)',
321
+ 'total_value' => 'SUM(`sfoi`.`row_total_incl_tax`)',
322
+ );
323
+
324
+ if ($options['group']) {
325
+ $columns['date'] = sprintf('DATE_FORMAT(`sfoi`.`created_at`, "%s")', $options['date_format']);
326
+ $query->group($columns['date']);
327
+ }
328
+
329
+ // reset columns.
330
+ $query->reset(Zend_Db_Select::COLUMNS)
331
+ ->columns($columns);
332
+
333
+ $data = $collection->getData();
334
+
335
+ if (!$options['group']) {
336
+ return array(
337
+ 'total_count' => $data[0]['total_count'] ?: 0,
338
+ 'total_value' => $data[0]['total_value'] ?: 0,
339
+ );
340
+ }
341
+
342
+ $dateRange = $this->getDates($options['from'], $options['to'], $options['group_by']);
343
+
344
+ return $this->datesCombine($dateRange, $data, array_keys($columns));
345
+ }
346
+
347
+ /**
348
+ * Get an array of attributes and their acceptable values.
349
+ *
350
+ * @return array
351
+ */
352
+ public function getProductAttributes()
353
+ {
354
+ $query = Mage::getResourceModel('catalog/product_attribute_collection')->getSelect();
355
+
356
+ $query->where('frontend_input = ?', 'select')
357
+ ->order('frontend_label ASC');
358
+
359
+ $columns = array(
360
+ 'id' => 'attribute_id',
361
+ 'code' => 'attribute_code',
362
+ 'label' => 'frontend_label',
363
+ );
364
+
365
+ // reset columns.
366
+ $query->reset(Zend_Db_Select::COLUMNS)
367
+ ->columns($columns);
368
+
369
+ return $this->readConnection->fetchAll($query);
370
+ }
371
+
372
+ /**
373
+ * Get a product attribute and a collection of it's acceptable values.
374
+ *
375
+ * @param int $id
376
+ * @return stdClass
377
+ */
378
+ public function getProductAttribute($code)
379
+ {
380
+ $attr = $this->getAttribute($code);
381
+
382
+ return (object)array(
383
+ 'id' => $attr->getAttributeId(),
384
+ 'code' => $attr->getAttributeCode(),
385
+ 'label' => $attr->getFrontendLabel(),
386
+ 'options' => $attr->getSource()->getAllOptions(false),
387
+ );
388
+ }
389
+
390
+ /**
391
+ * Get product categories.
392
+ *
393
+ * @param array $options
394
+ * @return array
395
+ */
396
+ public function getCategories($options = array())
397
+ {
398
+ $options = array_merge(array(
399
+ 'store_id' => null,
400
+ ), $this->options, $options);
401
+
402
+ // new query.
403
+ $query = $this->readConnection->select();
404
+
405
+ $cceTable = $this->resource->getTableName('catalog/category');
406
+ $ccevTable = Mage::getConfig()->getTablePrefix() . 'catalog_category_entity_varchar';
407
+ // get category name attribute id.
408
+ $attributeId = $this->getCategoryNameAttributeId();
409
+
410
+ $columns = array(
411
+ 'id' => 'entity_id',
412
+ 'level',
413
+ );
414
+
415
+ $query->from(array('cce' => $cceTable), $columns)
416
+ ->join(
417
+ array('ccev' => $ccevTable),
418
+ implode(' AND ', array(
419
+ "`ccev`.`entity_id` = `cce`.`entity_id`",
420
+ "`ccev`.`attribute_id`={$attributeId}",
421
+ sprintf("`ccev`.`store_id` = %d", (int)$options['store_id'])
422
+ )),
423
+ array('name' => 'value')
424
+ )
425
+ ->order('path ASC');
426
+
427
+ $cats = $this->readConnection->fetchAll($query);
428
+
429
+ return $this->buildCatTree($cats);
430
+ }
431
+
432
+ /**
433
+ * Get product by sku.
434
+ *
435
+ * @param string $sku
436
+ * @return stdClass|null
437
+ */
438
+ public function getProductBySku($sku)
439
+ {
440
+ if (!$product = Mage::getModel('catalog/product')->loadByAttribute('sku', $sku)) {
441
+ // throw new ApiException(sprintf('Product "%s" not found', $sku), 400);
442
+ return "404";
443
+ }
444
+
445
+ return array(
446
+ 'sku' => $product->getSku(),
447
+ 'name' => $product->getName(),
448
+ 'price' => $product->getPrice(),
449
+ 'short_description' => $product->getShortDescription(),
450
+ 'url' => $product->getProductUrl(),
451
+ 'img' => array(
452
+ 'regular' => $product->getImageUrl(),
453
+ 'thumbnail' => $product->getThumbnailUrl()
454
+ )
455
+ );
456
+ }
457
+
458
+ /**
459
+ * Build category tree.
460
+ *
461
+ * @param array $cats
462
+ * @return array
463
+ */
464
+ protected function buildCatTree(&$cats)
465
+ {
466
+ $cat = (object)array_shift($cats);
467
+
468
+ $cat->children = array();
469
+
470
+ if (isset($this->lastCat[$cat->level - 1])) {
471
+ $this->lastCat[$cat->level - 1]->children[] = $cat;
472
+ } else {
473
+ $this->tree[] = $cat;
474
+ }
475
+
476
+ $this->lastCat[$cat->level] = $cat;
477
+
478
+ if ($cats) {
479
+ $this->buildCatTree($cats);
480
+ }
481
+
482
+ return $this->tree;
483
+ }
484
+
485
  /**
486
  * Check to see whether the passed comparison operator is valid.
487
  *
506
 
507
  return $operator;
508
  }
509
+
510
+
511
+ /**
512
+ * Join categories to select query.
513
+ *
514
+ * @param Varien_Db_Select $query
515
+ * @param array $categories
516
+ * @return self
517
+ */
518
+ protected function joinCategories($query, $categories = array())
519
+ {
520
+ // if no categories set.
521
+ if (!is_array($categories) || empty($categories)) {
522
+ return $this;
523
+ }
524
+
525
+ // get table names.
526
+ $ccpTable = $this->resource->getTableName('catalog/category_product');
527
+ $cceTable = $this->resource->getTableName('catalog/category');
528
+ $ccevTable = Mage::getConfig()->getTablePrefix() . 'catalog_category_entity_varchar';
529
+ // get "category name" attribute id.
530
+ $attributeId = $this->getCategoryNameAttributeId();
531
+
532
+ $query->join(array('ccp' => $ccpTable), '`ccp`.`product_id` = `e`.`entity_id`', array())
533
+ ->join(array('cce' => $cceTable), '`cce`.`entity_id` = `ccp`.`category_id`', array())
534
+ ->join(
535
+ array('ccev' => $ccevTable),
536
+ "`ccev`.`entity_id` = `cce`.`entity_id` AND `ccev`.`attribute_id`={$attributeId}",
537
+ array()
538
+ )
539
+ ->where('`ccp`.`category_id` IN(?)', $categories);
540
+
541
+ return $this;
542
+ }
543
+
544
+ /**
545
+ * Join attributes to select query.
546
+ *
547
+ * @param Varien_Db_Select $query
548
+ * @param array $attributes
549
+ * @return self
550
+ */
551
+ protected function joinAttributes($query, $attributes = array())
552
+ {
553
+ $c = Mage::getSingleton('core/resource')->getConnection('default_write');
554
+
555
+ $attributes = is_array($attributes) ? $attributes : array();
556
+
557
+ foreach ($attributes as $code => $value) {
558
+ // escape value
559
+ $value = $c->quote($value);
560
+
561
+ // get attriubte.
562
+ $attr = $this->getAttribute($code);
563
+ $alias = "{$code}_table";
564
+ $aliasEaov = "{$alias}_eaov";
565
+
566
+ // join attribute
567
+ $query->join(
568
+ array($alias => $attr->getBackendTable()),
569
+ "product_id = {$alias}.entity_id AND {$alias}.attribute_id={$attr->getId()}",
570
+ array($code => 'value')
571
+ );
572
+
573
+ $query->join(
574
+ array($aliasEaov => 'eav_attribute_option_value'),
575
+ sprintf(
576
+ "{$aliasEaov}.option_id = {$alias}.value AND {$aliasEaov}.value = %s AND {$aliasEaov}.store_id = %d",
577
+ $value,
578
+ $this->options['store_id']
579
+ ),
580
+ array()
581
+ );
582
+ }
583
+
584
+ return $this;
585
+ }
586
+
587
+ /**
588
+ * Get an attribute.
589
+ *
590
+ * @param string $code
591
+ * @return Mage_Catalog_Model_Resource_Eav_Attribute
592
+ * @throws IR_Neatly_Exception_Api
593
+ */
594
+ protected function getAttribute($code)
595
+ {
596
+ $attr = Mage::getSingleton('eav/config')
597
+ ->getAttribute(Mage_Catalog_Model_Product::ENTITY, $code);
598
+
599
+ // if attribute does not exist.
600
+ if (!$attr || !$attr->getId()) {
601
+ throw new ApiException(sprintf('"%s" is not a valid attribute.', $code), 400);
602
+ }
603
+
604
+ $attr->setStoreId($this->options['store_id']);
605
+
606
+ return $attr;
607
+ }
608
+
609
+ /**
610
+ * Get the ID of the category name attribute.
611
+ *
612
+ * @return int
613
+ */
614
+ protected function getCategoryNameAttributeId()
615
+ {
616
+ $eavAttributeTbl = $this->resource->getTableName('eav/attribute');
617
+ $eavEntityTypeTbl = $this->resource->getTableName('eav/entity_type');
618
+ $query = $this->readConnection->select();
619
+ $query->from(array('eat' => $eavEntityTypeTbl), array());
620
+ $query->join(array('ea' => $eavAttributeTbl), 'ea.entity_type_id = eat.entity_type_id', array('attribute_id'));
621
+ $query->where('eat.entity_type_code = ?', 'catalog_category');
622
+ $query->where('ea.attribute_code = ?', 'name');
623
+
624
+ $data = $this->readConnection->fetchAll($query);
625
+
626
+ if (!isset($data[0]['attribute_id'])) {
627
+ throw new Exception("Could not find category name attribute ID.");
628
+ }
629
+
630
+ return (int)$data[0]['attribute_id'];
631
+ }
632
  }
app/code/community/IR/Neatly/controllers/IndexController.php CHANGED
@@ -46,8 +46,19 @@ class IR_Neatly_IndexController extends Mage_Core_Controller_Front_Action
46
  'sales_by_customer_group' => 'getOrdersByCustomerGroup',
47
  'distinct_order_statuses' => 'getDistinctOrderStatuses',
48
  'customers' => 'getCustomers',
 
 
 
 
 
 
 
 
 
 
49
  'stores' => 'getStores',
50
  'meta' => 'getMeta',
 
51
  );
52
 
53
  /**
@@ -105,27 +116,38 @@ class IR_Neatly_IndexController extends Mage_Core_Controller_Front_Action
105
  $this->customersReport = Mage::getModel('ir_neatly/reports_customers', $this->options);
106
 
107
  try {
 
108
  if (empty($this->options['to']) || empty($this->options['from'])) {
109
  throw new ApiException('"to" and "from" dates required.', 400);
110
  }
111
 
112
  $resp = array('version' => $helper->getVersion());
113
 
 
 
114
  // if action is an array and all requested actions exist.
115
  if (is_array($this->options['action']) &&
116
- array_intersect($this->options['action'], array_keys($this->actions)) === $this->options['action']) {
 
117
  foreach ($this->options['action'] as $action) {
118
- $method = $this->actions[$action];
119
  $resp[$action] = $this->{$method}();
120
  }
121
- } elseif (isset($this->actions[$this->options['action']])) {
122
  // if action is not an array but exists.
123
- $method = $this->actions[$this->options['action']];
124
  $resp[$this->options['action']] = $this->{$method}();
125
  } else {
126
- // get default actions (expect "customers").
127
- unset($this->actions['customers']);
128
- foreach ($this->actions as $action => $method) {
 
 
 
 
 
 
 
129
  $resp[$action] = $this->{$method}();
130
  }
131
  }
@@ -229,6 +251,56 @@ class IR_Neatly_IndexController extends Mage_Core_Controller_Front_Action
229
  return $this->reporting->getPagination($count, $customers);
230
  }
231
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  protected function getStores()
233
  {
234
  return $this->stores;
46
  'sales_by_customer_group' => 'getOrdersByCustomerGroup',
47
  'distinct_order_statuses' => 'getDistinctOrderStatuses',
48
  'customers' => 'getCustomers',
49
+ 'sales_by_type_stats' => 'getSalesByTypeStats',
50
+ );
51
+
52
+ /**
53
+ * @var array
54
+ */
55
+ protected $metaActions = array(
56
+ 'categories' => 'getCategories',
57
+ 'attributes' => 'getProductAttributes',
58
+ 'attribute' => 'getProductAttribute',
59
  'stores' => 'getStores',
60
  'meta' => 'getMeta',
61
+ 'product_by_sku' => 'getProductBySku',
62
  );
63
 
64
  /**
116
  $this->customersReport = Mage::getModel('ir_neatly/reports_customers', $this->options);
117
 
118
  try {
119
+
120
  if (empty($this->options['to']) || empty($this->options['from'])) {
121
  throw new ApiException('"to" and "from" dates required.', 400);
122
  }
123
 
124
  $resp = array('version' => $helper->getVersion());
125
 
126
+ $actions = array_merge($this->actions, $this->metaActions);
127
+
128
  // if action is an array and all requested actions exist.
129
  if (is_array($this->options['action']) &&
130
+ array_intersect($this->options['action'], array_keys($actions)) === $this->options['action']) {
131
+
132
  foreach ($this->options['action'] as $action) {
133
+ $method = $actions[$action];
134
  $resp[$action] = $this->{$method}();
135
  }
136
+ } elseif (!is_array($this->options['action']) && isset($actions[$this->options['action']])) {
137
  // if action is not an array but exists.
138
+ $method = $actions[$this->options['action']];
139
  $resp[$this->options['action']] = $this->{$method}();
140
  } else {
141
+ // get default actions.
142
+ unset(
143
+ $actions['customers'],
144
+ $actions['attribute'],
145
+ $actions['attributes'],
146
+ $actions['categories'],
147
+ $actions['sales_by_type_stats'],
148
+ $actions['product_by_sku']
149
+ );
150
+ foreach ($actions as $action => $method) {
151
  $resp[$action] = $this->{$method}();
152
  }
153
  }
251
  return $this->reporting->getPagination($count, $customers);
252
  }
253
 
254
+ public function getSalesByTypeStats()
255
+ {
256
+ $options = array_merge($this->options, array(
257
+ 'group' => false
258
+ ));
259
+
260
+ $count = $this->reporting->getSalesByTypeStats($options);
261
+
262
+ $options['group'] = true;
263
+
264
+ $byDate = $this->reporting->getSalesByTypeStats($options);
265
+
266
+ return array(
267
+ 'count' => $count,
268
+ 'dates' => $byDate
269
+ );
270
+ }
271
+
272
+ public function getProductAttributes()
273
+ {
274
+ return $this->reporting->getProductAttributes();
275
+ }
276
+
277
+ public function getProductAttribute()
278
+ {
279
+ $options = array_merge(array('code' => null), $this->options);
280
+
281
+ if (!trim($options['code'])) {
282
+ throw new ApiException('"code" is required.', 400);
283
+ }
284
+
285
+ return $this->reporting->getProductAttribute($options['code']);
286
+ }
287
+
288
+ public function getProductBySku()
289
+ {
290
+ $options = array_merge(array('sku' => null), $this->options);
291
+
292
+ if (!trim($options['sku'])) {
293
+ throw new ApiException('"sku" is required.', 400);
294
+ }
295
+
296
+ return $this->reporting->getProductBySku($options['sku']);
297
+ }
298
+
299
+ public function getCategories()
300
+ {
301
+ return $this->reporting->getCategories();
302
+ }
303
+
304
  protected function getStores()
305
  {
306
  return $this->stores;
package.xml CHANGED
@@ -1,13 +1,16 @@
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>IR_Neatly_Reporting</name>
4
- <version>1.0.1.0</version>
5
  <stability>stable</stability>
6
  <license uri="http://www.gnu.org/copyleft/gpl.html">GNU General Public License (GPL)</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>View a range of detailed reports about your sales, products and customers from your Neatly dashboard.</summary>
10
- <description>Simply install the extension and enter your API key and you will be able to view the following data:&#xD;
 
 
 
11
  &#xD;
12
  &lt;ul&gt;&#xD;
13
  &lt;li&gt;Sales Overview&lt;/li&gt;&#xD;
@@ -22,12 +25,31 @@
22
  &lt;li&gt;Sales by Country&lt;/li&gt;&#xD;
23
  &lt;/ul&gt;&#xD;
24
  &#xD;
25
- You can also compare data against previous period of custom your date range comparison.</description>
26
- <notes>This latest release allows the extension to now support PHP 5.4.0</notes>
27
- <authors><author><name>Jon Leigton</name><user>iResources</user><email>j.leighton@i-resources.co.uk</email></author></authors>
28
- <date>2015-06-03</date>
29
- <time>11:05:27</time>
30
- <contents><target name="magecommunity"><dir name="IR"><dir name="Neatly"><dir name="Exception"><file name="Api.php" hash="079a77634e425c151fcc97e9d67a2ebf"/></dir><dir name="Helper"><file name="Data.php" hash="ad424a7ecee2e205356678d233908207"/></dir><dir name="Model"><dir name="Reports"><file name="Abstract.php" hash="5b908c879ffa6974085fedb19b530959"/><file name="Customers.php" hash="f6af2cf717dbb7d121d643d1171d6ccf"/><file name="Reporting.php" hash="1415934625ff2b9963d3d87cf1851201"/><file name="Sales.php" hash="6b0fb8adb0ec9cbf283508fe59adb797"/></dir></dir><dir name="controllers"><file name="IndexController.php" hash="a419ada141895a71c2337aa4e10ac24d"/></dir><dir name="etc"><file name="adminhtml.xml" hash="98b2b738856967354adade75f1830a9c"/><file name="config.xml" hash="33268e3951cce566d47534e7114cbe60"/><file name="system.xml" hash="3f13f3b44d36643f8bb9822a64ce4c29"/></dir><file name=".DS_Store" hash="601020d0d1c6b7a09b8efd396666f583"/></dir></dir></target><target name="mageetc"><dir name="modules"><file name="IR_Neatly.xml" hash="8cfc6d5b71ff25155628d065d6a7335b"/></dir></target></contents>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  <compatible/>
32
  <dependencies><required><php><min>5.4.0</min><max>5.6.9</max></php></required></dependencies>
33
  </package>
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>IR_Neatly_Reporting</name>
4
+ <version>1.1.0.0</version>
5
  <stability>stable</stability>
6
  <license uri="http://www.gnu.org/copyleft/gpl.html">GNU General Public License (GPL)</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>View a range of detailed reports about your sales, products and customers from your Neatly dashboard.</summary>
10
+ <description>This extension allows Neatly to pull and generate detailed reports about your sales, customers and products.&#xD;
11
+ &#xD;
12
+ Simply install the extension and enter your API key and you will be able to view the following data:&#xD;
13
+ &lt;br /&gt;&#xD;
14
  &#xD;
15
  &lt;ul&gt;&#xD;
16
  &lt;li&gt;Sales Overview&lt;/li&gt;&#xD;
25
  &lt;li&gt;Sales by Country&lt;/li&gt;&#xD;
26
  &lt;/ul&gt;&#xD;
27
  &#xD;
28
+ You can also compare data against previous period of custom your date range comparison.&#xD;
29
+ &#xD;
30
+ You can also create your own dashboard and compare against a wide range of metrics including Google Analytics.&#xD;
31
+ &#xD;
32
+ Customer Search Filter&#xD;
33
+ &#xD;
34
+ Want to send targeted emails to a specific group of customers? &#xD;
35
+ &#xD;
36
+ Use our customer search feature to segment your customers and export as a CSV or export directly to Mailchimp.&#xD;
37
+ &#xD;
38
+ Search for customers based on:&#xD;
39
+ &lt;br /&gt;&#xD;
40
+ &#xD;
41
+ &lt;ul&gt;&#xD;
42
+ &lt;li&gt;Number of Orders&lt;/li&gt;&#xD;
43
+ &lt;li&gt;Order Value&lt;/li&gt;&#xD;
44
+ &lt;li&gt;Product SKU&lt;/li&gt;&#xD;
45
+ &lt;li&gt;City/Postcode&lt;/li&gt;&#xD;
46
+ &lt;/ul&gt;&#xD;
47
+ So if you want to reward your top customers or determine who has shopped with you for a while then this is the tool for you.</description>
48
+ <notes>You can now view total sales by product SKU, category or attribute using the "Product Search" feature.</notes>
49
+ <authors><author><name>Jon Leighton</name><user>iResources</user><email>j.leighton@i-resources.co.uk</email></author></authors>
50
+ <date>2015-08-17</date>
51
+ <time>14:44:53</time>
52
+ <contents><target name="magecommunity"><dir name="IR"><dir name="Neatly"><dir name="Exception"><file name="Api.php" hash="079a77634e425c151fcc97e9d67a2ebf"/></dir><dir name="Helper"><file name="Data.php" hash="becc783e4402b1afc002f1a38e236d9b"/></dir><dir name="Model"><dir name="Reports"><file name="Abstract.php" hash="5b908c879ffa6974085fedb19b530959"/><file name="Customers.php" hash="f6af2cf717dbb7d121d643d1171d6ccf"/><file name="Reporting.php" hash="90cf8bc74cde479b2ddeda3419a0679b"/><file name="Sales.php" hash="6b0fb8adb0ec9cbf283508fe59adb797"/></dir></dir><dir name="controllers"><file name="IndexController.php" hash="4d6501eb4031f4b42bf041a9a8090486"/></dir><dir name="etc"><file name="adminhtml.xml" hash="98b2b738856967354adade75f1830a9c"/><file name="config.xml" hash="33268e3951cce566d47534e7114cbe60"/><file name="system.xml" hash="3f13f3b44d36643f8bb9822a64ce4c29"/></dir><file name=".DS_Store" hash="601020d0d1c6b7a09b8efd396666f583"/></dir></dir></target><target name="mageetc"><dir name="modules"><file name="IR_Neatly.xml" hash="8cfc6d5b71ff25155628d065d6a7335b"/></dir></target></contents>
53
  <compatible/>
54
  <dependencies><required><php><min>5.4.0</min><max>5.6.9</max></php></required></dependencies>
55
  </package>