WordPress WooCommerce Sales Report Plugin

At Win Authority, I developed a WordPress plugin for a client who runs their e-commerce website via WooCommerce. I developed a plugin that utilizes the WooCommerce API via wc_get_orders and sends the client an email at the start of each month using WordPress CRON with the previous month’s completed orders.

Development Highlights

The plugin is designed to integrate seamlessly with WooCommerce and WordPress, streamlining the process of fetching and emailing sales data. Here’s an in-depth look at its architecture and features, complete with code snippets from the repository to illustrate key functionalities.

The plugin shows the following data for the completed orders:

  1. Product name
  2. Product categories
  3. Product cost
  4. Commission for that product
  5. Total sales amount for the month

Architecture and Key Features

Data Retrieval and Processing: The plugin utilizes WooCommerce’s built-in functions to fetch data on completed orders from the previous month. Here’s a snippet demonstrating how this data is retrieved using wc-order-functions:

$orders = wc_get_orders(array(
    'status' => 'completed',
    'date_completed' => '>' . (new DateTime('first day of last month'))->format('Y-m-d')
));

This code fetches all completed orders from the previous month, forming the foundation for our sales report.

Automated Email Reports: An HTML template formats the sales data into an organized, readable table. The following snippet from the repository shows how the email content is constructed and sent:

<thead>
    <tr>
        <td colspan="8" style="padding-top:0; padding-bottom:9px; ">

            <p style="text-align: justify;">This email was sent from
                your website ' . get_site_url() . ' and is a summary of
                the monthly sales on your website for between ' . $sales_report_date . '.</p>

        </td>
    </tr>
    <tr>
        <td colspan="8">
            <p style="font-size: large;">Sales between ' . $sales_report_date . '</p>
        </td>
    </tr>
    <tr style="border: 1px solid #f2f2f2;">
        <th class="sales-report-table-cell-border-top">#</th>
        <th class="sales-report-table-cell-border-top">Order ID</th>
        <th class="sales-report-table-cell-border-top">Product Name</th>
        <th class="sales-report-table-cell-border-top">Product Category</th>
        <th class="sales-report-table-cell-border-top">Product Amount</th>
        <th class="sales-report-table-cell-border-top">Commission</th>
        <th class="sales-report-table-cell-border-top">Product Amount After Commission</th>
    </tr>
</thead>
<tbody>
    ';

    $completed_sales_query = get_completed_sales_data();

    if ($completed_sales_query) {
    foreach ($completed_sales_query as $order) {
    $order_id = $order->get_id();
    $order = wc_get_order($order_id);

    foreach ($order->get_items() as $item_id => $item) {
    $product = $item->get_product();
    $product_name = '';

    if ($product) {
    if ($product->is_type('variation')) {
    // For variation products, get the parent product name
    $parent_product_id = $product->get_parent_id();
    $parent_product = wc_get_product($parent_product_id);
    if ($parent_product) {
    $product_name = $parent_product->get_name();
    }
    } else {
    // For simple products, directly get the product name
    $product_name = $product->get_name();
    }
    } else {
    // If product is not available, get the name from the order item
    $product_name = $item->get_name() ? $item->get_name() : 'Product deleted';
    }

    $product_url = $product ? $product->get_permalink() : '';
    // $product_categories = $product ? wp_get_post_terms($item->get_product_id(), 'product_cat', array('fields' => 'names')) : [];
    $product_categories = $item->get_product_id() ? wp_get_post_terms($item->get_product_id(), 'product_cat', array('fields' => 'names')) : array('[Cannot retreive categories as the product was deleted.]');
    $product_commission = 0;
    $total_price = $item->get_total();

    // Calculate product commission based on categories
    if ($product && in_array('FROK', $product_categories)) {
    $product_commission = $total_price * (60 / 100);
    } else {
    $product_commission = $total_price * (2 / 100);
    }

    // Format product commission
    $product_commission_final = wc_price($product_commission);
    $sales_report .= '<tr class="' . $row_class . '">';
        $sales_report .= '<td class="sales-report-table-cell-border">' . $count += 1 . '</td>';
        $sales_report .= '<td class="sales-report-table-cell-border"><a href="' . esc_url($order->get_edit_order_url()) . '">' . $order_id . '</a></td>';
        $sales_report .= '<td class="sales-report-table-cell-border"><a href="' . esc_url($product_url) . '">' . $product_name . '</a></td>';
        $sales_report .= '<td class="sales-report-table-cell-border">' . implode(', ', $product_categories) . '</td>';
        $sales_report .= '<td class="sales-report-table-cell-border">' . '' . wc_price($item->get_total()) . '</td>';
        $sales_report .= '<td class="sales-report-table-cell-border">' . $product_commission_final . '</td>';
        $sales_report .= '<td class="sales-report-table-cell-border">' . '' . wc_price($item->get_total() - $product_commission) . '</td>';
        $sales_report .= '</tr>';

    // Update totals
    $total_sales += $item->get_quantity();
    $total_order_amount += $item->get_total();
    $total_commission += $product_commission;
    $total_price_after_commission += $item->get_total() - $product_commission;
    }
    }
    // Add totals row to the sales report
    $sales_report .= '<tr style="padding-top: 20px">
        <td colspan="4" class="sales-report-table-cell-border-top sales-report-table-cell-border-bottom">Totals</td>
        <td class="sales-report-table-cell-border-top sales-report-table-cell-border-bottom">' . wc_price($total_order_amount) . '</td>
        <td class="sales-report-table-cell-border-top sales-report-table-cell-border-bottom">' . wc_price($total_commission) . '</td>
        <td class="sales-report-table-cell-border-top sales-report-table-cell-border-bottom">' . wc_price($total_price_after_commission) . '</td>
    </tr>';
    wp_reset_postdata();
    } else {
    $sales_report .= '<tr>
        <td colspan="8">No sales for the month.</td>
    </tr>';
    }
    $sales_report .= '


</tbody>

This snippet builds the HTML email content with the data and commission.

Dynamic Commission Calculations: The plugin includes logic to calculate commissions based on product categories as our client wanted to calculate the commission based on a product’s category, if the product included that category, then there should be a 60% commission, or else it should take a 2% commission.

Here’s a snippet from the repository demonstrating this functionality, please note that the following code is there in the above snippet:

$product_commission = 0;
    $total_price = $item->get_total();

    // Calculate product commission based on categories
    if ($product && in_array('FROK', $product_categories)) {
    $product_commission = $total_price * (60 / 100);
    } else {
    $product_commission = $total_price * (2 / 100);
    }

    // Format product commission
    $product_commission_final = wc_price($product_commission);

Customizable Email Settings: Users can configure recipient email addresses and set their preferred time for receiving reports. The following code from the repository shows how the interface is built:

<div class="wrap">
        <h1>WooCommerce Sales Insights</h1>
        <form method="post">
            <table class="form-table">
                <tr>
                    <th scope="row">Email addresses you want reply to be sent to</th>
                    <td>
                        <input type="text" name="email_addresses" value="<?php echo esc_attr($email_addresses); ?>" class="regular-text">
                        <p class="description">Enter comma-separated email addresses.</p>
                    </td>
                </tr>
                <tr>
                    <th scope="row">Receive the report at (monthly)</th>
                    <td>
                        <input type="time" name="send_time" value="<?php echo esc_attr($send_time); ?>" class="regular-text">
                        <p class="description">Enter the preferred time to receive the sales report.</p>
                    </td>
                </tr>
                <tr>
                    <th scope="row">The next email will be sent at</th>
                    <td>
                        <p><?php echo esc_html($next_scheduled_time); ?></p>
                    </td>
                </tr>
                <tr>
                    <th scope="row">Your website's current date and time is</th>
                    <td>
                        <p><?php echo wp_date('F j, Y g:i A'); ?></p>
                    </td>
                </tr>
            </table>
            <p class="submit"><input type="submit" name="woocommerce_sales_insights_submit" class="button-primary" value="Save Settings"></p>
        </form>
    </div>

The following code how the logic is applied to the interface:

<?php

// Settings page callback
function woocommerce_sales_insights_settings()
{
    // Get the site's timezone from WordPress settings
    $site_timezone = get_option('timezone_string');

    // Set the timezone
    if ($site_timezone) {
        date_default_timezone_set($site_timezone);
    }

    // Save the settings if form is submitted
    if (isset($_POST['woocommerce_sales_insights_submit'])) {
        // Retrieve and sanitize the email addresses
        $email_addresses = isset($_POST['email_addresses']) ? sanitize_text_field($_POST['email_addresses']) : '';
        $email_addresses = preg_replace('/\s+/', '', $email_addresses); // Remove whitespace

        // Split the email addresses by comma
        $email_addresses = array_map('trim', explode(',', $email_addresses));

        // Filter out empty values
        $email_addresses = array_filter($email_addresses);

        // Convert the array of email addresses into a comma-separated string
        $email_string = implode(',', $email_addresses);

        $send_time = isset($_POST['send_time']) ? sanitize_text_field($_POST['send_time']) : '';

        // Update the option with the array of email addresses
        update_option('woocommerce_sales_insights_email_addresses', $email_string);
        update_option('woocommerce_sales_insights_send_time', $send_time);

        // Calculate the next send time
        $next_send_time = strtotime('first day of +1 month', strtotime('today ' . $send_time . ' ' . $site_timezone));
        $current_time = strtotime(current_time('Y-m-d H:i:s'));

        // Check if the next send time is in the past
        if ($next_send_time < $current_time) {
            // Add one day to the current date
            $next_send_time = strtotime('first day of +1 month', $current_time);

            // Set the time for the next send time
            $next_send_time = strtotime($send_time, $next_send_time);

            // Validate if the time was successfully set
            if ($next_send_time === false) {
                // Time format is invalid, handle the error
                $error_message = 'Invalid time format. Please enter a valid time.';
                woocommerce_sales_insights_log_event($error_message);
                echo '<div class="error notice"><p>' . $error_message . '</p></div>';
                return;
            }
        }

        // Check if the next send time is valid
        if ($next_send_time === false) {
            // Date or timezone is invalid, handle the error
            $error_message = 'Invalid date or timezone. Please check your settings.';
            woocommerce_sales_insights_log_event($error_message);
            echo '<div class="error notice"><p>' . $error_message . '</p></div>';
            return;
        }

        // Unschedule the existing event
        wp_clear_scheduled_hook('send_sales_email');

        // Schedule the event with the new send time
        wp_schedule_event($next_send_time, 'monthly', 'send_sales_email');

        // Format the next scheduled time
        $next_scheduled_time = wp_date(get_option('date_format') . ' ' . get_option('time_format'), $next_send_time);

        // Display success message
        $success_message = sprintf('Settings saved. Next scheduled email: %s', $next_scheduled_time);
        woocommerce_sales_insights_log_event($success_message);
        echo '<div class="updated notice"><p>' . $success_message . '</p></div>';
    }

    // Retrieve saved settings
    $email_addresses = get_option('woocommerce_sales_insights_email_addresses', '');
    $send_time = get_option('woocommerce_sales_insights_send_time', '12:00 am');
    $next_send_time = wp_next_scheduled('send_sales_email');
    $next_scheduled_time = $next_send_time ? wp_date(get_option('date_format') . ' ' . get_option('time_format'), $next_send_time) : '';
?>
[The above interface code comes here]
<?php
}

This WooCommerce Sales Report plugin exemplifies my ability to create sophisticated, automated solutions that integrate seamlessly with existing platforms. It demonstrates my proficiency in leveraging WordPress and WooCommerce APIs to build customizable and user-friendly features.

For a deeper look into the implementation, you can explore the source code on my GitHub repository.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Share via
Copy link