Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.57% covered (warning)
65.57%
40 / 61
50.00% covered (danger)
50.00%
6 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Sales_By_Channel_Controller
65.00% covered (warning)
65.00%
39 / 60
50.00% covered (danger)
50.00%
6 / 12
18.17
0.00% covered (danger)
0.00%
0 / 1
 get_report_key
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_report_label
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_data_endpoint
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_column_headers
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 format_row_for_csv
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 get_default_values
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 get_additional_params
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 get_fields
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 get_matching_field
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_identifying_fields
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 use_array_filter_format
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_empty_row_check_field
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * REST API Reports Sales by Channel controller class.
4 *
5 * @package Automattic\Jetpack\PremiumAnalytics\Reports\Export\Exports
6 */
7
8declare( strict_types=1 );
9
10namespace Automattic\Jetpack\PremiumAnalytics\Reports\Export\Exports;
11
12defined( 'ABSPATH' ) || exit;
13
14use Automattic\Jetpack\PremiumAnalytics\Reports\Export\Abstract_Csv_Report_Controller;
15
16/**
17 * Sales by Channel CSV Export Controller.
18 *
19 * Handles CSV exports for the Sales by Channel report (order attribution data).
20 * Note: This is a ranked list report, not a time-series report.
21 * Comparison mode is supported using ID-based merging (matching by channel).
22 *
23 * @since $$next-version$$
24 */
25class Sales_By_Channel_Controller extends Abstract_Csv_Report_Controller {
26
27    /**
28     * Get the report key for this controller.
29     *
30     * @return string The report key.
31     */
32    public function get_report_key(): string {
33        return 'salesbychannel';
34    }
35
36    /**
37     * Get the report label for this controller.
38     *
39     * @return string The report label.
40     */
41    public function get_report_label(): string {
42        return __( 'Sales by Channel', 'jetpack-premium-analytics' );
43    }
44
45    /**
46     * Get the data endpoint for this controller.
47     *
48     * @return string The data endpoint.
49     */
50    public function get_data_endpoint(): string {
51        return 'reports/order-attribution/channel/items';
52    }
53
54    /**
55     * Get the column headers for this controller.
56     *
57     * @param string|null $interval Optional time interval for dynamic headers.
58     * @return array The column headers.
59     */
60    public function get_column_headers( ?string $interval = null ): array { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Signature required by the report controller interface.
61        return array(
62            'channel'             => __( 'Channel', 'jetpack-premium-analytics' ),
63            'gross_sales'         => __( 'Gross sales', 'jetpack-premium-analytics' ),
64            'coupons'             => __( 'Coupons', 'jetpack-premium-analytics' ),
65            'refunds'             => __( 'Refunds', 'jetpack-premium-analytics' ),
66            'net_sales'           => __( 'Net sales', 'jetpack-premium-analytics' ),
67            'new_customers'       => __( 'New customers', 'jetpack-premium-analytics' ),
68            'returning_customers' => __( 'Returning customers', 'jetpack-premium-analytics' ),
69            'avg_order_value'     => __( 'Average Order Value', 'jetpack-premium-analytics' ),
70            'avg_items_per_order' => __( 'Avg Items per order', 'jetpack-premium-analytics' ),
71        );
72    }
73
74    /**
75     * Format a row for CSV export.
76     *
77     * @param array       $item     The row data.
78     * @param string|null $interval Optional time interval for formatting.
79     * @return array The formatted row.
80     */
81    public function format_row_for_csv( array $item, ?string $interval = null ): array { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Signature required by the report controller interface.
82        $defaults = $this->get_default_values();
83
84        $returning_customers = max( ( $item['total_customers'] ?? $defaults['total_customers'] ) - ( $item['new_customers'] ?? $defaults['new_customers'] ), 0 );
85
86        return array(
87            'channel'             => $item['label'] ?? $item['channel'] ?? $defaults['channel'],
88            'gross_sales'         => self::format_amount( $item['gross_sales'] ?? $defaults['gross_sales'] ),
89            'coupons'             => self::format_amount( $item['coupons'] ?? $defaults['coupons'] ),
90            'refunds'             => self::format_amount( $item['refunds'] ?? $defaults['refunds'] ),
91            'net_sales'           => self::format_amount( $item['net_sales'] ?? $defaults['net_sales'] ),
92            'new_customers'       => $item['new_customers'] ?? $defaults['new_customers'],
93            'returning_customers' => $returning_customers,
94            'avg_order_value'     => self::format_amount( $item['avg_order_value'] ?? $defaults['avg_order_value'] ),
95            'avg_items_per_order' => number_format( floatval( $item['avg_items'] ?? $defaults['avg_items'] ), 2, '.', '' ),
96        );
97    }
98
99    /**
100     * Get default values for missing data fields.
101     *
102     * @return array Array of field_name => default_value pairs.
103     */
104    public function get_default_values(): array {
105        return array(
106            'channel'         => '',
107            'label'           => '',
108            'gross_sales'     => 0,
109            'coupons'         => 0,
110            'refunds'         => 0,
111            'net_sales'       => 0,
112            'new_customers'   => 0,
113            'total_customers' => 0,
114            'avg_order_value' => 0,
115            'avg_items'       => 0,
116        );
117    }
118
119    /**
120     * Get additional request parameters for data fetching.
121     *
122     * Sets orderby to gross_sales and view to channel.
123     *
124     * @return array Additional parameters to include in data requests.
125     */
126    public function get_additional_params(): array {
127        return array(
128            'date_type' => self::DEFAULT_DATE_TYPE,
129            'orderby'   => 'gross_sales',
130            'view'      => 'channel',
131        );
132    }
133
134    /**
135     * Get the list of API fields needed for this report.
136     *
137     * @return array
138     */
139    public function get_fields(): array {
140        return array(
141            'channel',
142            'label',
143            'gross_sales',
144            'coupons',
145            'refunds',
146            'net_sales',
147            'new_customers',
148            'total_customers',
149            'avg_order_value',
150            'avg_items',
151        );
152    }
153
154    /**
155     * Get the matching field for comparison data alignment.
156     *
157     * Sales by Channel is a ranked report, so comparison data should be matched by channel.
158     *
159     * @return string|null
160     */
161    public function get_matching_field(): ?string {
162        return 'channel';
163    }
164
165    /**
166     * Get the identifying fields that should be preserved in comparison data.
167     *
168     * When a channel exists in the original period but not in the comparison period,
169     * the label should still be shown for clarity.
170     *
171     * @return array Array of field names to preserve.
172     */
173    public function get_identifying_fields(): array {
174        return array( 'label' );
175    }
176
177    /**
178     * Whether to use array format for filter values in IN filters.
179     *
180     * Order-attribution endpoints require array format for filter values.
181     *
182     * @return bool True to use array format.
183     */
184    public function use_array_filter_format(): bool {
185        return true;
186    }
187
188    /**
189     * Get the field name to check for emptiness.
190     *
191     * @return array The field names to check.
192     */
193    public function get_empty_row_check_field() {
194        return array( 'channel' );
195    }
196}