Parcourir la source

Summary of Changes — v1.0.0 -> v1.1.0

New Feature: "Order Customers List" report view
  A new report type added to WooCommerce > Custom Reports that shows distinct customers (grouped by billing email) with   sortable columns: Customer Name, Email, Phone, Orders Count, Total Orders Price.
Files Changed (9 modified, 2 new)
  studiou-wc-custom-reports.php — Version bumped to 1.1.0 (header + constant)
  includes/class-db-manager.php (+130 lines) — Added get_order_customers_list(), get_order_customers_total_items(), HPOS   + legacy customer queries, customer ordering clause
  includes/class-admin-view.php (+52 lines) — Added "Order Customers List" to View dropdown, view-based data fetching     dispatch, render_customers_data_table() with sortable columns
  includes/class-export-handler.php (+70 lines) — Reads view param, added generate_customers_csv_output() (exports as     order-customers-{date}.csv)
  includes/class-report-factory.php — Registered order_customers_list type
  includes/class-report-data.php — Added customer_name, customer_email to valid orderby values
  readme.md — Updated description, features, file structure, class list, added v1.1.0 changelog
  languages/*.po — Version bumped, added 6 new Czech translation strings
  languages/*.mo — Binary updated
New files:
  includes/class-order-customers-report.php — Report class for order customers list (autoloaded via StudiouWC_            convention)
  CLAUDE.md — Claude Code guidance file for this repository
Dalibor Votruba il y a 3 mois
Parent
commit
c412571889

+ 40 - 0
studiou-wc-custom-reports/CLAUDE.md

@@ -0,0 +1,40 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## What This Is
+
+A WooCommerce extension plugin ("QDR - Studiou WC Custom Reports") that adds custom reporting pages under the WooCommerce admin menu. Currently provides a "Product Categories and Variants Summary" report with CSV export. Requires WordPress 6.8.1+, PHP 8.2+, WooCommerce 9.0+.
+
+## Development Environment
+
+This is a WordPress plugin — there is no build step, linter, or test runner. Development is done by deploying to a WordPress installation with WooCommerce active. Enable `WP_DEBUG` in `wp-config.php` to see debug logging (prefixed `[Studiou Debug]` and `[Studiou WC Custom Reports]`).
+
+## Architecture
+
+**Autoloading convention**: Classes prefixed `StudiouWC_` are autoloaded from `includes/`. The class `StudiouWC_Foo_Bar` maps to `includes/class-foo-bar.php`. This is implemented via `spl_autoload_register` in the main plugin file.
+
+**Wiring**: `StudiouWC_Main` is the entry point — it instantiates all services and registers WordPress/WooCommerce hooks. Dependencies flow via constructor injection: `DB_Manager` and `Settings_Manager` are created first, then passed into `Admin_View` and `Export_Handler`.
+
+**Dual query paths (HPOS)**: `DB_Manager` contains two versions of every SQL query — one for WooCommerce HPOS (High-Performance Order Storage using `wc_orders` table) and one for the legacy `wp_posts`-based order storage. The `is_hpos_enabled()` method selects the path at runtime. When modifying queries, both paths must be updated.
+
+**Report extensibility**: New report types are added through `Report_Factory`. The current only type is `product_categories_summary`. To add a new type: create a report class, register it in `Report_Factory::create_report()`, add DB methods in `DB_Manager`, and add the option to the view dropdown in `Admin_View`.
+
+**Settings**: Stored as a single WordPress option (`studiou_wc_custom_reports_settings`) managed by `Settings_Manager`. Settings are saved via AJAX (`wp_ajax_studiou_save_settings`). Default statuses include the custom status `wc-to-print`.
+
+**Property/variant columns**: The report dynamically generates columns based on WooCommerce product attribute taxonomies (e.g., `pa_format`). The selected attribute is configured in settings as `default_property`. Property values are resolved from `attribute_pa_{property}` post meta on variation products.
+
+## Key Constants
+
+Defined in `studiou-wc-custom-reports.php`:
+- `STUDIOU_WC_CUSTOM_REPORTS_TEXT_DOMAIN` — `'studiou-wc-custom-reports'`
+- `STUDIOU_WC_CUSTOM_REPORTS_VERSION` — current version string
+
+## Internationalization
+
+Text domain: `studiou-wc-custom-reports`. Translation files go in `languages/`. Czech translation (`cs_CZ`) exists. All user-facing strings use `__()` / `_e()` with the text domain constant.
+
+## Admin Pages
+
+- **WooCommerce > Custom Reports** (`studiou-custom-reports`) — main report page with filters and data table
+- **WooCommerce > Custom Report Settings** (`studiou-reports-settings`) — configuration page for defaults

+ 46 - 6
studiou-wc-custom-reports/includes/class-admin-view.php

@@ -72,12 +72,18 @@ class StudiouWC_Admin_View {
         
         $per_page = isset($_GET['per_page']) ? intval($_GET['per_page']) : 20;
         $paged = isset($_GET['paged']) ? intval($_GET['paged']) : 1;
-        $orderby = isset($_GET['orderby']) ? sanitize_text_field($_GET['orderby']) : 'category';
+        $default_orderby = ($view === 'order_customers_list') ? 'customer_name' : 'category';
+        $orderby = isset($_GET['orderby']) ? sanitize_text_field($_GET['orderby']) : $default_orderby;
         $order = isset($_GET['order']) ? sanitize_text_field($_GET['order']) : 'ASC';
-        
-        // Get report data
-        $report_data = $this->db_manager->get_product_categories_summary($date_from, $date_to, $statuses, $per_page, $paged, $orderby, $order);
-        $total_items = $this->db_manager->get_total_items($date_from, $date_to, $statuses);
+
+        // Get report data based on view
+        if ($view === 'order_customers_list') {
+            $report_data = $this->db_manager->get_order_customers_list($date_from, $date_to, $statuses, $per_page, $paged, $orderby, $order);
+            $total_items = $this->db_manager->get_order_customers_total_items($date_from, $date_to, $statuses);
+        } else {
+            $report_data = $this->db_manager->get_product_categories_summary($date_from, $date_to, $statuses, $per_page, $paged, $orderby, $order);
+            $total_items = $this->db_manager->get_total_items($date_from, $date_to, $statuses);
+        }
         $total_pages = ceil($total_items / $per_page);
         
         $this->render_reports_page($view, $date_from, $date_to, $statuses, $per_page, $paged, $orderby, $order, $report_data, $total_items, $total_pages, $settings);
@@ -97,7 +103,11 @@ class StudiouWC_Admin_View {
             
             <?php if (!empty($report_data)): ?>
                 <?php $this->render_pagination_top($total_items, $total_pages, $paged); ?>
-                <?php $this->render_data_table($report_data, $orderby, $order, $settings); ?>
+                <?php if ($view === 'order_customers_list'): ?>
+                    <?php $this->render_customers_data_table($report_data, $orderby, $order); ?>
+                <?php else: ?>
+                    <?php $this->render_data_table($report_data, $orderby, $order, $settings); ?>
+                <?php endif; ?>
             <?php else: ?>
                 <p><?php _e('No data found for the selected criteria.', StudiouWC_Main::get_text_domain()); ?></p>
             <?php endif; ?>
@@ -120,6 +130,9 @@ class StudiouWC_Admin_View {
                             <option value="product_categories_summary" <?php selected($view, 'product_categories_summary'); ?>>
                                 <?php _e('Product Categories and Variants Summary', StudiouWC_Main::get_text_domain()); ?>
                             </option>
+                            <option value="order_customers_list" <?php selected($view, 'order_customers_list'); ?>>
+                                <?php _e('Order Customers List', StudiouWC_Main::get_text_domain()); ?>
+                            </option>
                         </select>
                     </td>
                 </tr>
@@ -250,6 +263,33 @@ class StudiouWC_Admin_View {
         <?php
     }
     
+    private function render_customers_data_table($report_data, $orderby, $order) {
+        ?>
+        <table class="wp-list-table widefat fixed striped">
+            <thead>
+                <tr>
+                    <th><a href="<?php echo esc_url(add_query_arg(array('orderby' => 'customer_name', 'order' => $order === 'ASC' ? 'DESC' : 'ASC'))); ?>"><?php _e('Customer Name', StudiouWC_Main::get_text_domain()); ?></a></th>
+                    <th><a href="<?php echo esc_url(add_query_arg(array('orderby' => 'customer_email', 'order' => $order === 'ASC' ? 'DESC' : 'ASC'))); ?>"><?php _e('Email', StudiouWC_Main::get_text_domain()); ?></a></th>
+                    <th><?php _e('Phone', StudiouWC_Main::get_text_domain()); ?></th>
+                    <th><a href="<?php echo esc_url(add_query_arg(array('orderby' => 'orders_count', 'order' => $order === 'ASC' ? 'DESC' : 'ASC'))); ?>"><?php _e('Orders Count', StudiouWC_Main::get_text_domain()); ?></a></th>
+                    <th><a href="<?php echo esc_url(add_query_arg(array('orderby' => 'total_price', 'order' => $order === 'ASC' ? 'DESC' : 'ASC'))); ?>"><?php _e('Total Orders Price', StudiouWC_Main::get_text_domain()); ?></a></th>
+                </tr>
+            </thead>
+            <tbody>
+                <?php foreach ($report_data as $row): ?>
+                <tr>
+                    <td><?php echo esc_html($row['customer_name']); ?></td>
+                    <td><?php echo esc_html($row['customer_email']); ?></td>
+                    <td><?php echo esc_html($row['customer_phone']); ?></td>
+                    <td><?php echo esc_html($row['orders_count']); ?></td>
+                    <td><?php echo wc_price($row['total_price']); ?></td>
+                </tr>
+                <?php endforeach; ?>
+            </tbody>
+        </table>
+        <?php
+    }
+
     private function render_reports_javascript() {
         ?>
         <script>

+ 130 - 0
studiou-wc-custom-reports/includes/class-db-manager.php

@@ -470,6 +470,136 @@ class StudiouWC_DB_Manager {
         ";
     }
     
+    public function get_order_customers_list($date_from, $date_to, $statuses, $per_page, $paged, $orderby, $order) {
+        global $wpdb;
+
+        if (empty($statuses)) {
+            return array();
+        }
+
+        $statuses_placeholder = implode(',', array_fill(0, count($statuses), '%s'));
+        $offset = ($paged - 1) * $per_page;
+
+        if ($this->is_hpos_enabled()) {
+            $sql = $this->get_hpos_customers_query($statuses_placeholder);
+        } else {
+            $sql = $this->get_legacy_customers_query($statuses_placeholder);
+        }
+
+        $sql .= $this->get_customers_order_clause($orderby, $order);
+        $sql .= " LIMIT %d OFFSET %d";
+
+        $query_params = array_merge(
+            array($date_from, $date_to),
+            $statuses,
+            array($per_page, $offset)
+        );
+
+        return $wpdb->get_results($wpdb->prepare($sql, $query_params), ARRAY_A);
+    }
+
+    public function get_order_customers_total_items($date_from, $date_to, $statuses) {
+        global $wpdb;
+
+        if (empty($statuses)) {
+            return 0;
+        }
+
+        $statuses_placeholder = implode(',', array_fill(0, count($statuses), '%s'));
+
+        if ($this->is_hpos_enabled()) {
+            $sql = "
+                SELECT COUNT(DISTINCT addr.email)
+                FROM {$wpdb->prefix}wc_orders o
+                INNER JOIN {$wpdb->prefix}wc_order_addresses addr ON o.id = addr.order_id AND addr.address_type = 'billing'
+                WHERE o.date_created_gmt >= %s
+                AND o.date_created_gmt <= %s
+                AND o.status IN ($statuses_placeholder)
+                AND addr.email != ''
+            ";
+        } else {
+            $sql = "
+                SELECT COUNT(DISTINCT pm_email.meta_value)
+                FROM {$wpdb->posts} p
+                INNER JOIN {$wpdb->postmeta} pm_email ON p.ID = pm_email.post_id AND pm_email.meta_key = '_billing_email'
+                WHERE p.post_date >= %s
+                AND p.post_date <= %s
+                AND p.post_status IN ($statuses_placeholder)
+                AND p.post_type = 'shop_order'
+                AND pm_email.meta_value != ''
+            ";
+        }
+
+        $query_params = array_merge(
+            array($date_from, $date_to),
+            $statuses
+        );
+
+        return $wpdb->get_var($wpdb->prepare($sql, $query_params));
+    }
+
+    private function get_hpos_customers_query($statuses_placeholder) {
+        global $wpdb;
+
+        return "
+            SELECT
+                MAX(CONCAT(addr.first_name, ' ', addr.last_name)) as customer_name,
+                addr.email as customer_email,
+                MAX(addr.phone) as customer_phone,
+                COUNT(DISTINCT o.id) as orders_count,
+                SUM(o.total_amount) as total_price
+            FROM {$wpdb->prefix}wc_orders o
+            INNER JOIN {$wpdb->prefix}wc_order_addresses addr ON o.id = addr.order_id AND addr.address_type = 'billing'
+            WHERE o.date_created_gmt >= %s
+            AND o.date_created_gmt <= %s
+            AND o.status IN ($statuses_placeholder)
+            AND addr.email != ''
+            GROUP BY addr.email
+        ";
+    }
+
+    private function get_legacy_customers_query($statuses_placeholder) {
+        global $wpdb;
+
+        return "
+            SELECT
+                MAX(CONCAT(pm_fn.meta_value, ' ', pm_ln.meta_value)) as customer_name,
+                pm_email.meta_value as customer_email,
+                MAX(pm_phone.meta_value) as customer_phone,
+                COUNT(DISTINCT p.ID) as orders_count,
+                SUM(CAST(pm_total.meta_value AS DECIMAL(10,2))) as total_price
+            FROM {$wpdb->posts} p
+            INNER JOIN {$wpdb->postmeta} pm_email ON p.ID = pm_email.post_id AND pm_email.meta_key = '_billing_email'
+            LEFT JOIN {$wpdb->postmeta} pm_fn ON p.ID = pm_fn.post_id AND pm_fn.meta_key = '_billing_first_name'
+            LEFT JOIN {$wpdb->postmeta} pm_ln ON p.ID = pm_ln.post_id AND pm_ln.meta_key = '_billing_last_name'
+            LEFT JOIN {$wpdb->postmeta} pm_phone ON p.ID = pm_phone.post_id AND pm_phone.meta_key = '_billing_phone'
+            LEFT JOIN {$wpdb->postmeta} pm_total ON p.ID = pm_total.post_id AND pm_total.meta_key = '_order_total'
+            WHERE p.post_date >= %s
+            AND p.post_date <= %s
+            AND p.post_status IN ($statuses_placeholder)
+            AND p.post_type = 'shop_order'
+            AND pm_email.meta_value != ''
+            GROUP BY pm_email.meta_value
+        ";
+    }
+
+    private function get_customers_order_clause($orderby, $order) {
+        $order = ($order === 'DESC') ? 'DESC' : 'ASC';
+
+        switch ($orderby) {
+            case 'customer_name':
+                return " ORDER BY customer_name $order";
+            case 'customer_email':
+                return " ORDER BY customer_email $order";
+            case 'orders_count':
+                return " ORDER BY orders_count $order";
+            case 'total_price':
+                return " ORDER BY total_price $order";
+            default:
+                return " ORDER BY customer_name $order";
+        }
+    }
+
     private function get_order_clause($orderby, $order) {
         $order = ($order === 'DESC') ? 'DESC' : 'ASC';
         

+ 58 - 12
studiou-wc-custom-reports/includes/class-export-handler.php

@@ -24,22 +24,28 @@ class StudiouWC_Export_Handler {
             wp_die(__('You do not have sufficient permissions to access this page.', StudiouWC_Main::get_text_domain()));
         }
         
+        $view = isset($_POST['view']) ? sanitize_text_field($_POST['view']) : 'product_categories_summary';
         $date_from = isset($_POST['date_from']) ? sanitize_text_field($_POST['date_from']) : '';
         $date_to = isset($_POST['date_to']) ? sanitize_text_field($_POST['date_to']) : '';
         $statuses = isset($_POST['statuses']) ? array_map('sanitize_text_field', $_POST['statuses']) : array();
-        
-        // Get all data without pagination for export
-        $report_data = $this->db_manager->get_product_categories_summary($date_from, $date_to, $statuses, 999999, 1, 'category', 'ASC');
-        
-        $settings = $this->settings_manager->get_settings();
-        $default_property = $settings['default_property'] ?? '';
-        $property_values = array();
-        
-        if (!empty($default_property)) {
-            $property_values = $this->db_manager->get_property_values($default_property);
+
+        if ($view === 'order_customers_list') {
+            $report_data = $this->db_manager->get_order_customers_list($date_from, $date_to, $statuses, 999999, 1, 'customer_name', 'ASC');
+            $this->generate_customers_csv_output($report_data);
+        } else {
+            // Get all data without pagination for export
+            $report_data = $this->db_manager->get_product_categories_summary($date_from, $date_to, $statuses, 999999, 1, 'category', 'ASC');
+
+            $settings = $this->settings_manager->get_settings();
+            $default_property = $settings['default_property'] ?? '';
+            $property_values = array();
+
+            if (!empty($default_property)) {
+                $property_values = $this->db_manager->get_property_values($default_property);
+            }
+
+            $this->generate_csv_output($report_data, $property_values);
         }
-        
-        $this->generate_csv_output($report_data, $property_values);
     }
     
     private function generate_csv_output($report_data, $property_values) {
@@ -73,6 +79,46 @@ class StudiouWC_Export_Handler {
         exit;
     }
     
+    private function generate_customers_csv_output($report_data) {
+        header('Content-Type: text/csv; charset=utf-8');
+        $filename = sprintf(
+            /* translators: %s: current date */
+            __('order-customers-%s.csv', StudiouWC_Main::get_text_domain()),
+            date('Y-m-d')
+        );
+        header('Content-Disposition: attachment; filename="' . $filename . '"');
+        header('Pragma: no-cache');
+        header('Expires: 0');
+
+        $output = fopen('php://output', 'w');
+
+        // Add UTF-8 BOM for Excel compatibility
+        fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
+
+        // CSV headers
+        fputcsv($output, array(
+            __('Customer Name', StudiouWC_Main::get_text_domain()),
+            __('Email', StudiouWC_Main::get_text_domain()),
+            __('Phone', StudiouWC_Main::get_text_domain()),
+            __('Orders Count', StudiouWC_Main::get_text_domain()),
+            __('Total Orders Price', StudiouWC_Main::get_text_domain()),
+        ));
+
+        // CSV data
+        foreach ($report_data as $row) {
+            fputcsv($output, array(
+                $row['customer_name'],
+                $row['customer_email'],
+                $row['customer_phone'],
+                $row['orders_count'],
+                strip_tags(wc_price($row['total_price'])),
+            ));
+        }
+
+        fclose($output);
+        exit;
+    }
+
     private function get_csv_headers($property_values) {
         $headers = array(
             __('Product Category', StudiouWC_Main::get_text_domain()),

+ 53 - 0
studiou-wc-custom-reports/includes/class-order-customers-report.php

@@ -0,0 +1,53 @@
+<?php
+/**
+ * Order Customers Report Class - Specific implementation for order customers list report
+ *
+ * @package StudiouWCCustomReports
+ */
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+class StudiouWC_Order_Customers_Report {
+
+    private $db_manager;
+    private $data;
+
+    public function __construct($db_manager, $data) {
+        $this->db_manager = $db_manager;
+        $this->data = $data;
+    }
+
+    public function get_report_data() {
+        return $this->db_manager->get_order_customers_list(
+            $this->data->get_date_from(),
+            $this->data->get_date_to(),
+            $this->data->get_statuses(),
+            $this->data->get_per_page(),
+            $this->data->get_paged(),
+            $this->data->get_orderby(),
+            $this->data->get_order()
+        );
+    }
+
+    public function get_total_items() {
+        return $this->db_manager->get_order_customers_total_items(
+            $this->data->get_date_from(),
+            $this->data->get_date_to(),
+            $this->data->get_statuses()
+        );
+    }
+
+    public function get_export_data() {
+        return $this->db_manager->get_order_customers_list(
+            $this->data->get_date_from(),
+            $this->data->get_date_to(),
+            $this->data->get_statuses(),
+            999999,
+            1,
+            $this->data->get_orderby(),
+            $this->data->get_order()
+        );
+    }
+}

+ 1 - 1
studiou-wc-custom-reports/includes/class-report-data.php

@@ -49,7 +49,7 @@ class StudiouWC_Report_Data {
         }
         
         // Validate orderby column
-        $valid_orderby = array('category', 'orders_count', 'total_price');
+        $valid_orderby = array('category', 'orders_count', 'total_price', 'customer_name', 'customer_email');
         if (!in_array($this->orderby, $valid_orderby)) {
             $this->orderby = 'category';
         }

+ 5 - 2
studiou-wc-custom-reports/includes/class-report-factory.php

@@ -21,15 +21,18 @@ class StudiouWC_Report_Factory {
         switch ($type) {
             case 'product_categories_summary':
                 return new StudiouWC_Product_Categories_Report($this->db_manager, $data);
+            case 'order_customers_list':
+                return new StudiouWC_Order_Customers_Report($this->db_manager, $data);
             default:
                 /* translators: %s: report type name */
                 throw new InvalidArgumentException(sprintf(__('Unknown report type: %s', StudiouWC_Main::get_text_domain()), $type));
         }
     }
-    
+
     public function get_available_report_types() {
         return array(
-            'product_categories_summary' => __('Product Categories and Variants Summary', StudiouWC_Main::get_text_domain())
+            'product_categories_summary' => __('Product Categories and Variants Summary', StudiouWC_Main::get_text_domain()),
+            'order_customers_list' => __('Order Customers List', StudiouWC_Main::get_text_domain())
         );
     }
     

BIN
studiou-wc-custom-reports/languages/studiou-wc-custom-reports-cs_CZ.mo


+ 30 - 1
studiou-wc-custom-reports/languages/studiou-wc-custom-reports-cs_CZ.po

@@ -5,7 +5,7 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: QDR - Studiou WC Custom Reports 1.0.0\n"
+"Project-Id-Version: QDR - Studiou WC Custom Reports 1.1.0\n"
 "Report-Msgid-Bugs-To: https://www.quadarax.com\n"
 "POT-Creation-Date: 2025-06-16 09:42:33+0000\n"
 "PO-Revision-Date: 2025-06-16 09:42:33+0000\n"
@@ -190,6 +190,35 @@ msgstr "vlastni-reporty-%s.csv"
 msgid "Unknown report type: %s"
 msgstr "Neznámý typ reportu: %s"
 
+#: includes/class-admin-view.php
+#: includes/class-report-factory.php
+msgid "Order Customers List"
+msgstr "Seznam zákazníků objednávek"
+
+#: includes/class-admin-view.php
+#: includes/class-export-handler.php
+msgid "Customer Name"
+msgstr "Jméno zákazníka"
+
+#: includes/class-admin-view.php
+#: includes/class-export-handler.php
+msgid "Email"
+msgstr "E-mail"
+
+#: includes/class-admin-view.php
+#: includes/class-export-handler.php
+msgid "Phone"
+msgstr "Telefon"
+
+#: includes/class-admin-view.php
+#: includes/class-export-handler.php
+msgid "Total Orders Price"
+msgstr "Celková cena objednávek"
+
+#: includes/class-export-handler.php
+msgid "order-customers-%s.csv"
+msgstr "zakaznici-objednavek-%s.csv"
+
 # Plugin description from header
 msgid "Allows view WooCommerce/Reports that shows custom overviews according to order, product, product categories, properties"
 msgstr "Umožňuje zobrazení WooCommerce/Reportů, které ukazují vlastní přehledy podle objednávek, produktů, kategorií produktů a vlastností"

+ 13 - 4
studiou-wc-custom-reports/readme.md

@@ -1,15 +1,16 @@
 # QDR - Studiou WC Custom Reports
 
-A WordPress plugin that extends WooCommerce with custom reporting functionality for product categories and variants analysis.
+A WordPress plugin that extends WooCommerce with custom reporting functionality for product categories, variants analysis, and customer order summaries.
 
 ## Description
 
-This plugin adds advanced reporting capabilities to WooCommerce, allowing you to analyze sales data by product categories and product properties/variants. It provides detailed insights into order counts, revenue, and property-based breakdowns.
+This plugin adds advanced reporting capabilities to WooCommerce, allowing you to analyze sales data by product categories, product properties/variants, and customer order history. It provides detailed insights into order counts, revenue, property-based breakdowns, and customer purchasing patterns.
 
 ## Features
 
 - **Custom Reports Page**: Advanced filtering and data visualization
 - **Product Categories Summary**: Analyze sales by product categories
+- **Order Customers List**: View distinct customers with order counts and total spending
 - **Dynamic Property Columns**: Show counts by product properties (size, color, etc.)
 - **Flexible Date Filtering**: Custom date ranges with preset options
 - **Order Status Filtering**: Include/exclude specific order statuses
@@ -43,7 +44,8 @@ studiou-wc-custom-reports/
 │   ├── class-export-handler.php      # CSV export functionality
 │   ├── class-report-data.php         # Data model and validation
 │   ├── class-report-factory.php      # Report factory pattern
-│   ├── class-product-categories-report.php  # Specific report implementation
+│   ├── class-product-categories-report.php  # Product categories report implementation
+│   ├── class-order-customers-report.php     # Order customers report implementation
 │   └── class-utils.php               # Utility functions
 ├── languages/                        # Translation files
 └── README.md                         # This file
@@ -63,7 +65,8 @@ studiou-wc-custom-reports/
 
 - **StudiouWC_Report_Data**: Data model with validation
 - **StudiouWC_Report_Factory**: Factory for creating different report types
-- **StudiouWC_Product_Categories_Report**: Specific report implementation
+- **StudiouWC_Product_Categories_Report**: Product categories report implementation
+- **StudiouWC_Order_Customers_Report**: Order customers list report implementation
 - **StudiouWC_Utils**: Common utility functions
 
 ## Usage
@@ -119,6 +122,12 @@ This plugin is licensed under GPL v2 or later.
 
 ## Changelog
 
+### 1.1.0
+- Added "Order Customers List" report view
+- Shows distinct customers with name, email, phone, orders count, and total orders price
+- CSV export support for customer list report
+- HPOS and legacy order storage support for customer queries
+
 ### 1.0.0
 - Initial release
 - Product Categories and Variants Summary report

+ 2 - 2
studiou-wc-custom-reports/studiou-wc-custom-reports.php

@@ -3,7 +3,7 @@
  * Plugin Name: QDR - Studiou WC Custom Reports
  * Plugin URI: https://www.quadarax.com/plugins/studiou-wc-custom-reports
  * Description: Allows view WooCommerce/Reports that shows custom overviews according to order, product, product categories, properties
- * Version: 1.0.0
+ * Version: 1.1.0
  * Requires at least: 6.8.1
  * Requires PHP: 8.2
  * Author: Dalibor Votruba
@@ -26,7 +26,7 @@ if (!defined('ABSPATH')) {
 define('STUDIOU_WC_CUSTOM_REPORTS_PLUGIN_FILE', __FILE__);
 define('STUDIOU_WC_CUSTOM_REPORTS_PLUGIN_DIR', plugin_dir_path(__FILE__));
 define('STUDIOU_WC_CUSTOM_REPORTS_PLUGIN_URL', plugin_dir_url(__FILE__));
-define('STUDIOU_WC_CUSTOM_REPORTS_VERSION', '1.0.0');
+define('STUDIOU_WC_CUSTOM_REPORTS_VERSION', '1.1.0');
 define('STUDIOU_WC_CUSTOM_REPORTS_TEXT_DOMAIN', 'studiou-wc-custom-reports');
 
 // Check if WooCommerce is active