Implementing automated compliance reporting for custom vendor commission records ledgers using mpdf engine
namespace App\Reporting;
use Mpdf\Mpdf;
use Mpdf\MpdfException;
use WP_Query; // Assuming WordPress environment
class CommissionReporter {
private $mpdf;
public function __construct() {
try {
$this->mpdf = new Mpdf([
'mode' => 'utf-8',
'format' => 'A4',
'margin_top' => 20,
'margin_bottom' => 20,
'margin_left' => 15,
'margin_right' => 15,
'default_font_size' => 10,
'default_font' => 'helvetica',
]);
$this->mpdf->SetCreator("Your Company Name");
$this->mpdf->SetAuthor("Automated Reporter");
$this->mpdf->SetTitle("Vendor Commission Report");
$this->mpdf->SetSubject("Monthly Commission Ledger");
$this->mpdf->SetKeywords("commission, report, vendor, ledger");
// Optional: Add header and footer
$this->mpdf->SetHTMLHeader('<div style="text-align: right; font-weight: bold;">Page {PAGENO} of {nbpg}</div>');
$this->mpdf->SetHTMLFooter('<div style="text-align: center;">Confidential - © ' . date('Y') . ' Your Company Name</div>');
} catch (MpdfException $e) {
// Log the error appropriately in a production environment
error_log("mPDF initialization error: " . $e->getMessage());
throw $e; // Re-throw to indicate failure
}
}
/**
* Fetches commission records from the database.
*
* @param array $args Query arguments (e.g., date range, vendor ID).
* @return array Array of commission records.
*/
public function fetchCommissionData(array $args = []): array {
// Example using WordPress WP_Query for custom post type 'commission_record'
// Adjust meta_query and tax_query as needed for your specific setup.
$default_args = [
'post_type' => 'commission_record',
'posts_per_page' => -1, // Fetch all
'post_status' => 'publish',
'meta_query' => [],
'orderby' => 'meta_value',
'order' => 'ASC',
];
// Example: Filter by date range
if (isset($args['start_date']) && isset($args['end_date'])) {
$default_args['meta_query'][] = [
'key' => '_transaction_date',
'value' => [$args['start_date'], $args['end_date']],
'type' => 'DATE',
'compare' => 'BETWEEN',
];
// Ensure order by date if filtering by date
$default_args['orderby'] = 'meta_value';
$default_args['order'] = 'ASC';
}
// Example: Filter by vendor ID
if (isset($args['vendor_id'])) {
$default_args['meta_query'][] = [
'key' => '_vendor_id',
'value' => $args['vendor_id'],
'compare' => '=',
];
}
// Example: Filter by payout status
if (isset($args['payout_status'])) {
$default_args['meta_query'][] = [
'key' => '_payout_status',
'value' => $args['payout_status'],
'compare' => '=',
];
}
// Merge user-provided args with defaults
$query_args = array_merge_recursive($default_args, $args);
// Ensure meta_query is correctly structured if only one condition is present
if (isset($query_args['meta_query']) && count($query_args['meta_query']) > 1) {
$query_args['meta_query']['relation'] = 'AND';
} elseif (isset($query_args['meta_query']) && count($query_args['meta_query']) === 1) {
// If only one meta query item, it should be an array, not nested further
$query_args['meta_query'] = [$query_args['meta_query']];
}
$commission_query = new WP_Query($query_args);
$records = [];
if ($commission_query->have_posts()) {
while ($commission_query->have_posts()) {
$commission_query->the_post();
$post_id = get_the_ID();
$records[] = [
'id' => $post_id,
'vendor_name' => get_post_meta($post_id, '_vendor_name', true) ?: 'N/A', // Assuming vendor name is stored
'client_name' => get_post_meta($post_id, '_client_name', true) ?: 'N/A', // Assuming client name is stored
'invoice_id' => get_post_meta($post_id, '_invoice_id', true) ?: 'N/A',
'commission_amount' => (float) get_post_meta($post_id, '_commission_amount', true),
'commission_rate' => (float) get_post_meta($post_id, '_commission_rate', true),
'transaction_date' => get_post_meta($post_id, '_transaction_date', true),
'payout_status' => get_post_meta($post_id, '_payout_status', true) ?: 'Pending',
'payout_date' => get_post_meta($post_id, '_payout_date', true) ?: 'N/A',
'notes' => get_post_meta($post_id, '_notes', true) ?: '',
];
}
wp_reset_postdata();
}
return $records;
}
/**
* Generates the PDF report.
*
* @param array $commission_data Commission records.
* @param string $report_title Title for the report.
* @return string The generated PDF content as a string.
* @throws MpdfException If PDF generation fails.
*/
public function generateReport(array $commission_data, string $report_title = "Commission Ledger Report"): string {
if (empty($commission_data)) {
$this->mpdf->WriteHTML('<h1>No Data Available</h1><p>No commission records found for the specified criteria.</p>');
return $this->mpdf->Output('', 'S');
}
$html = '<h1>' . esc_html($report_title) . '</h1>';
$html .= '<p>Report Generated On: ' . date('Y-m-d H:i:s') . '</p>';
// Table Header
$html .= '<table border="1" cellpadding="5" cellspacing="0" style="width: 100%; border-collapse: collapse;">';
$html .= '<thead><tr style="background-color: #f2f2f2;">';
$html .= '<th>Date</th>';
$html .= '<th>Vendor</th>';
$html .= '<th>Client</th>';
$html .= '<th>Invoice ID</th>';
$html .= '<th style="text-align: right;">Amount</th>';
$html .= '<th style="text-align: right;">Rate</th>';
$html .= '<th>Status</th>';
$html .= '<th>Payout Date</th>';
$html .= '</tr></thead><tbody>';
$total_commission = 0;
// Table Rows
foreach ($commission_data as $record) {
$html .= '<tr>';
$html .= '<td>' . esc_html($record['transaction_date']) . '</td>';
$html .= '<td>' . esc_html($record['vendor_name']) . '</td>';
$html .= '<td>' . esc_html($record['client_name']) . '</td>';
$html .= '<td>' . esc_html($record['invoice_id']) . '</td>';
$html .= '<td style="text-align: right;">' . number_format($record['commission_amount'], 2) . '</td>';
$html .= '<td style="text-align: right;">' . number_format($record['commission_rate'], 2) . '%</td>';
$html .= '<td>' . esc_html($record['payout_status']) . '</td>';
$html .= '<td>' . ($record['payout_date'] === 'N/A' ? 'N/A' : esc_html($record['payout_date'])) . '</td>';
$html .= '</tr>';
$total_commission += $record['commission_amount'];
}
// Table Footer (Totals)
$html .= '</tbody></table>';
$html .= '<br />';
$html .= '<table border="1" cellpadding="5" cellspacing="0" style="width: 30%; margin-left: auto; border-collapse: collapse;">';
$html .= '<tfoot><tr style="background-color: #f2f2f2;">';
$html .= '<th style="text-align: left;">Total Commission:</th>';
$html .= '<th style="text-align: right;">' . number_format($total_commission, 2) . '</th>';
$html .= '</tr></tfoot></table>';
try {
$this->mpdf->WriteHTML($html);
// Output as string (binary data)
return $this->mpdf->Output('', 'S');
} catch (MpdfException $e) {
error_log("mPDF generation error: " . $e->getMessage());
throw $e;
}
}
}
Explanation:
- The constructor initializes mPDF with basic settings and defines header/footer content. Error handling is included for initialization failures.
fetchCommissionDatais a placeholder for your data retrieval logic. The example usesWP_Queryto fetch custom post types, demonstrating filtering by date range, vendor ID, and payout status. Adapt this to your specific database schema (e.g., direct SQL queries if using custom tables).generateReporttakes the fetched data, constructs an HTML table, and uses mPDF’sWriteHTMLmethod to render it. It calculates and displays a total commission. The output is returned as a binary string usingOutput('', 'S').
Integrating with WordPress for Automated Triggering
To automate the reporting process, we can hook into WordPress’s action scheduler or use cron jobs. For this example, we’ll outline a method using a custom WordPress action that can be triggered manually or via a scheduled event.
First, ensure the CommissionReporter class is autoloaded. If you’re using Composer within WordPress, this is handled automatically. Otherwise, you might need to include the vendor autoloader manually.
// Include Composer's autoloader if not already done
// require_once __DIR__ . '/vendor/autoload.php'; // Adjust path as necessary
use App\Reporting\CommissionReporter;
/**
* Function to generate and save the commission report.
* Can be triggered via AJAX, WP-Cron, or Action Scheduler.
*/
function generate_and_save_commission_report() {
// Define reporting parameters (e.g., for the previous month)
$end_date = date('Y-m-t'); // End of current month
$start_date = date('Y-m-01', strtotime('-1 month')); // Start of previous month
$report_args = [
'start_date' => $start_date,
'end_date' => $end_date,
// 'vendor_id' => 123, // Optional: filter by specific vendor
// 'payout_status' => 'Paid', // Optional: filter by status
];
$reporter = new CommissionReporter();
$report_title = "Commission Report: " . date('F Y', strtotime($start_date));
try {
$commission_data = $reporter->fetchCommissionData($report_args);
$pdf_content = $reporter->generateReport($commission_data, $report_title);
// Define the upload directory
$upload_dir = wp_upload_dir();
$report_dir = $upload_dir['basedir'] . '/commission-reports/';
// Create directory if it doesn't exist
if (!file_exists($report_dir)) {
wp_mkdir_p($report_dir);
}
// Define filename
$filename = sanitize_title($report_title) . '_' . date('YmdHis') . '.pdf';
$filepath = $report_dir . $filename;
// Save the PDF file
if (file_put_contents($filepath, $pdf_content) !== false) {
// Optionally, log success or store the file path in post meta
error_log("Commission report generated successfully: " . $filepath);
return $filepath; // Return path for further processing
} else {
error_log("Failed to save commission report to: " . $filepath);
return false;
}
} catch (\Mpdf\MpdfException $e) {
error_log("Error generating commission report: " . $e->getMessage());
return false;
} catch (\Exception $e) {
error_log("An unexpected error occurred: " . $e->getMessage());
return false;
}
}
// Example: Hooking into an admin action for manual generation
add_action('admin_post_generate_commission_report', 'generate_and_save_commission_report');
// To trigger manually via URL:
// your-site.com/wp-admin/admin-post.php?action=generate_commission_report
// For scheduled generation, consider using WP-Cron or Action Scheduler.
// Example using WP-Cron (add to functions.php or a plugin file):
/*
if (!wp_next_scheduled('daily_commission_report_cron')) {
wp_schedule_event(time(), 'daily', 'daily_commission_report_cron');
}
add_action('daily_commission_report_cron', 'generate_and_save_commission_report');
*/
Advanced Considerations and Best Practices
When deploying this solution in a production environment, several advanced aspects should be addressed:
- Error Logging: Implement robust logging for mPDF exceptions, file writing errors, and data fetching issues. Use WordPress’s
error_log()or a dedicated logging library. - Security: Sanitize all data before outputting it into HTML for mPDF. Use WordPress functions like
esc_html(),esc_attr(), and ensure database queries are properly parameterized or use WordPress’s prepared statements. Restrict access to the manual trigger URL. - Performance: For very large datasets, consider pagination within the PDF generation or generating reports in batches. Optimize database queries using appropriate indexes.
- Configuration: Externalize settings like report templates, default date ranges, and output directories into WordPress options or a configuration file.
- Templating: For more complex layouts, explore mPDF’s templating capabilities or use a PHP templating engine (like Twig) to generate the HTML before passing it to mPDF.
- File Management: Implement a strategy for managing generated reports (e.g., archiving old reports, setting retention policies) to prevent disk space exhaustion.
- Action Scheduler: For reliable background job processing, especially in high-traffic sites, integrate with the Action Scheduler library. This provides robust queuing, retries, and status tracking for your report generation tasks.
- Internationalization: Ensure mPDF is configured for appropriate character sets and fonts if your data includes non-ASCII characters.
By following these guidelines, you can build a secure, efficient, and automated system for generating compliance reports for your custom vendor commission ledgers.
CREATE TABLE wp_commission_ledger (
record_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
vendor_id BIGINT(20) UNSIGNED NOT NULL,
client_id BIGINT(20) UNSIGNED NOT NULL,
invoice_id BIGINT(20) UNSIGNED NULL,
commission_amount DECIMAL(10, 2) NOT NULL,
commission_rate DECIMAL(5, 2) NOT NULL,
transaction_date DATE NOT NULL,
payout_status VARCHAR(50) NOT NULL DEFAULT 'Pending',
payout_date DATETIME NULL,
notes TEXT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (record_id),
INDEX idx_vendor_id (vendor_id),
INDEX idx_transaction_date (transaction_date)
);
For WordPress post meta, you would typically associate commission records with a custom post type (e.g., ‘Commission Record’) or directly with vendor/client posts, using meta keys like _commission_amount, _transaction_date, etc.
Developing the PDF Generation Class
We’ll create a dedicated PHP class to encapsulate the PDF generation logic. This class will handle fetching data, formatting it, and rendering it into a PDF document using mPDF.
namespace App\Reporting;
use Mpdf\Mpdf;
use Mpdf\MpdfException;
use WP_Query; // Assuming WordPress environment
class CommissionReporter {
private $mpdf;
public function __construct() {
try {
$this->mpdf = new Mpdf([
'mode' => 'utf-8',
'format' => 'A4',
'margin_top' => 20,
'margin_bottom' => 20,
'margin_left' => 15,
'margin_right' => 15,
'default_font_size' => 10,
'default_font' => 'helvetica',
]);
$this->mpdf->SetCreator("Your Company Name");
$this->mpdf->SetAuthor("Automated Reporter");
$this->mpdf->SetTitle("Vendor Commission Report");
$this->mpdf->SetSubject("Monthly Commission Ledger");
$this->mpdf->SetKeywords("commission, report, vendor, ledger");
// Optional: Add header and footer
$this->mpdf->SetHTMLHeader('<div style="text-align: right; font-weight: bold;">Page {PAGENO} of {nbpg}</div>');
$this->mpdf->SetHTMLFooter('<div style="text-align: center;">Confidential - © ' . date('Y') . ' Your Company Name</div>');
} catch (MpdfException $e) {
// Log the error appropriately in a production environment
error_log("mPDF initialization error: " . $e->getMessage());
throw $e; // Re-throw to indicate failure
}
}
/**
* Fetches commission records from the database.
*
* @param array $args Query arguments (e.g., date range, vendor ID).
* @return array Array of commission records.
*/
public function fetchCommissionData(array $args = []): array {
// Example using WordPress WP_Query for custom post type 'commission_record'
// Adjust meta_query and tax_query as needed for your specific setup.
$default_args = [
'post_type' => 'commission_record',
'posts_per_page' => -1, // Fetch all
'post_status' => 'publish',
'meta_query' => [],
'orderby' => 'meta_value',
'order' => 'ASC',
];
// Example: Filter by date range
if (isset($args['start_date']) && isset($args['end_date'])) {
$default_args['meta_query'][] = [
'key' => '_transaction_date',
'value' => [$args['start_date'], $args['end_date']],
'type' => 'DATE',
'compare' => 'BETWEEN',
];
// Ensure order by date if filtering by date
$default_args['orderby'] = 'meta_value';
$default_args['order'] = 'ASC';
}
// Example: Filter by vendor ID
if (isset($args['vendor_id'])) {
$default_args['meta_query'][] = [
'key' => '_vendor_id',
'value' => $args['vendor_id'],
'compare' => '=',
];
}
// Example: Filter by payout status
if (isset($args['payout_status'])) {
$default_args['meta_query'][] = [
'key' => '_payout_status',
'value' => $args['payout_status'],
'compare' => '=',
];
}
// Merge user-provided args with defaults
$query_args = array_merge_recursive($default_args, $args);
// Ensure meta_query is correctly structured if only one condition is present
if (isset($query_args['meta_query']) && count($query_args['meta_query']) > 1) {
$query_args['meta_query']['relation'] = 'AND';
} elseif (isset($query_args['meta_query']) && count($query_args['meta_query']) === 1) {
// If only one meta query item, it should be an array, not nested further
$query_args['meta_query'] = [$query_args['meta_query']];
}
$commission_query = new WP_Query($query_args);
$records = [];
if ($commission_query->have_posts()) {
while ($commission_query->have_posts()) {
$commission_query->the_post();
$post_id = get_the_ID();
$records[] = [
'id' => $post_id,
'vendor_name' => get_post_meta($post_id, '_vendor_name', true) ?: 'N/A', // Assuming vendor name is stored
'client_name' => get_post_meta($post_id, '_client_name', true) ?: 'N/A', // Assuming client name is stored
'invoice_id' => get_post_meta($post_id, '_invoice_id', true) ?: 'N/A',
'commission_amount' => (float) get_post_meta($post_id, '_commission_amount', true),
'commission_rate' => (float) get_post_meta($post_id, '_commission_rate', true),
'transaction_date' => get_post_meta($post_id, '_transaction_date', true),
'payout_status' => get_post_meta($post_id, '_payout_status', true) ?: 'Pending',
'payout_date' => get_post_meta($post_id, '_payout_date', true) ?: 'N/A',
'notes' => get_post_meta($post_id, '_notes', true) ?: '',
];
}
wp_reset_postdata();
}
return $records;
}
/**
* Generates the PDF report.
*
* @param array $commission_data Commission records.
* @param string $report_title Title for the report.
* @return string The generated PDF content as a string.
* @throws MpdfException If PDF generation fails.
*/
public function generateReport(array $commission_data, string $report_title = "Commission Ledger Report"): string {
if (empty($commission_data)) {
$this->mpdf->WriteHTML('<h1>No Data Available</h1><p>No commission records found for the specified criteria.</p>');
return $this->mpdf->Output('', 'S');
}
$html = '<h1>' . esc_html($report_title) . '</h1>';
$html .= '<p>Report Generated On: ' . date('Y-m-d H:i:s') . '</p>';
// Table Header
$html .= '<table border="1" cellpadding="5" cellspacing="0" style="width: 100%; border-collapse: collapse;">';
$html .= '<thead><tr style="background-color: #f2f2f2;">';
$html .= '<th>Date</th>';
$html .= '<th>Vendor</th>';
$html .= '<th>Client</th>';
$html .= '<th>Invoice ID</th>';
$html .= '<th style="text-align: right;">Amount</th>';
$html .= '<th style="text-align: right;">Rate</th>';
$html .= '<th>Status</th>';
$html .= '<th>Payout Date</th>';
$html .= '</tr></thead><tbody>';
$total_commission = 0;
// Table Rows
foreach ($commission_data as $record) {
$html .= '<tr>';
$html .= '<td>' . esc_html($record['transaction_date']) . '</td>';
$html .= '<td>' . esc_html($record['vendor_name']) . '</td>';
$html .= '<td>' . esc_html($record['client_name']) . '</td>';
$html .= '<td>' . esc_html($record['invoice_id']) . '</td>';
$html .= '<td style="text-align: right;">' . number_format($record['commission_amount'], 2) . '</td>';
$html .= '<td style="text-align: right;">' . number_format($record['commission_rate'], 2) . '%</td>';
$html .= '<td>' . esc_html($record['payout_status']) . '</td>';
$html .= '<td>' . ($record['payout_date'] === 'N/A' ? 'N/A' : esc_html($record['payout_date'])) . '</td>';
$html .= '</tr>';
$total_commission += $record['commission_amount'];
}
// Table Footer (Totals)
$html .= '</tbody></table>';
$html .= '<br />';
$html .= '<table border="1" cellpadding="5" cellspacing="0" style="width: 30%; margin-left: auto; border-collapse: collapse;">';
$html .= '<tfoot><tr style="background-color: #f2f2f2;">';
$html .= '<th style="text-align: left;">Total Commission:</th>';
$html .= '<th style="text-align: right;">' . number_format($total_commission, 2) . '</th>';
$html .= '</tr></tfoot></table>';
try {
$this->mpdf->WriteHTML($html);
// Output as string (binary data)
return $this->mpdf->Output('', 'S');
} catch (MpdfException $e) {
error_log("mPDF generation error: " . $e->getMessage());
throw $e;
}
}
}
Explanation:
- The constructor initializes mPDF with basic settings and defines header/footer content. Error handling is included for initialization failures.
fetchCommissionDatais a placeholder for your data retrieval logic. The example usesWP_Queryto fetch custom post types, demonstrating filtering by date range, vendor ID, and payout status. Adapt this to your specific database schema (e.g., direct SQL queries if using custom tables).generateReporttakes the fetched data, constructs an HTML table, and uses mPDF’sWriteHTMLmethod to render it. It calculates and displays a total commission. The output is returned as a binary string usingOutput('', 'S').
Integrating with WordPress for Automated Triggering
To automate the reporting process, we can hook into WordPress’s action scheduler or use cron jobs. For this example, we’ll outline a method using a custom WordPress action that can be triggered manually or via a scheduled event.
First, ensure the CommissionReporter class is autoloaded. If you’re using Composer within WordPress, this is handled automatically. Otherwise, you might need to include the vendor autoloader manually.
// Include Composer's autoloader if not already done
// require_once __DIR__ . '/vendor/autoload.php'; // Adjust path as necessary
use App\Reporting\CommissionReporter;
/**
* Function to generate and save the commission report.
* Can be triggered via AJAX, WP-Cron, or Action Scheduler.
*/
function generate_and_save_commission_report() {
// Define reporting parameters (e.g., for the previous month)
$end_date = date('Y-m-t'); // End of current month
$start_date = date('Y-m-01', strtotime('-1 month')); // Start of previous month
$report_args = [
'start_date' => $start_date,
'end_date' => $end_date,
// 'vendor_id' => 123, // Optional: filter by specific vendor
// 'payout_status' => 'Paid', // Optional: filter by status
];
$reporter = new CommissionReporter();
$report_title = "Commission Report: " . date('F Y', strtotime($start_date));
try {
$commission_data = $reporter->fetchCommissionData($report_args);
$pdf_content = $reporter->generateReport($commission_data, $report_title);
// Define the upload directory
$upload_dir = wp_upload_dir();
$report_dir = $upload_dir['basedir'] . '/commission-reports/';
// Create directory if it doesn't exist
if (!file_exists($report_dir)) {
wp_mkdir_p($report_dir);
}
// Define filename
$filename = sanitize_title($report_title) . '_' . date('YmdHis') . '.pdf';
$filepath = $report_dir . $filename;
// Save the PDF file
if (file_put_contents($filepath, $pdf_content) !== false) {
// Optionally, log success or store the file path in post meta
error_log("Commission report generated successfully: " . $filepath);
return $filepath; // Return path for further processing
} else {
error_log("Failed to save commission report to: " . $filepath);
return false;
}
} catch (\Mpdf\MpdfException $e) {
error_log("Error generating commission report: " . $e->getMessage());
return false;
} catch (\Exception $e) {
error_log("An unexpected error occurred: " . $e->getMessage());
return false;
}
}
// Example: Hooking into an admin action for manual generation
add_action('admin_post_generate_commission_report', 'generate_and_save_commission_report');
// To trigger manually via URL:
// your-site.com/wp-admin/admin-post.php?action=generate_commission_report
// For scheduled generation, consider using WP-Cron or Action Scheduler.
// Example using WP-Cron (add to functions.php or a plugin file):
/*
if (!wp_next_scheduled('daily_commission_report_cron')) {
wp_schedule_event(time(), 'daily', 'daily_commission_report_cron');
}
add_action('daily_commission_report_cron', 'generate_and_save_commission_report');
*/
Advanced Considerations and Best Practices
When deploying this solution in a production environment, several advanced aspects should be addressed:
- Error Logging: Implement robust logging for mPDF exceptions, file writing errors, and data fetching issues. Use WordPress’s
error_log()or a dedicated logging library. - Security: Sanitize all data before outputting it into HTML for mPDF. Use WordPress functions like
esc_html(),esc_attr(), and ensure database queries are properly parameterized or use WordPress’s prepared statements. Restrict access to the manual trigger URL. - Performance: For very large datasets, consider pagination within the PDF generation or generating reports in batches. Optimize database queries using appropriate indexes.
- Configuration: Externalize settings like report templates, default date ranges, and output directories into WordPress options or a configuration file.
- Templating: For more complex layouts, explore mPDF’s templating capabilities or use a PHP templating engine (like Twig) to generate the HTML before passing it to mPDF.
- File Management: Implement a strategy for managing generated reports (e.g., archiving old reports, setting retention policies) to prevent disk space exhaustion.
- Action Scheduler: For reliable background job processing, especially in high-traffic sites, integrate with the Action Scheduler library. This provides robust queuing, retries, and status tracking for your report generation tasks.
- Internationalization: Ensure mPDF is configured for appropriate character sets and fonts if your data includes non-ASCII characters.
By following these guidelines, you can build a secure, efficient, and automated system for generating compliance reports for your custom vendor commission ledgers.
composer require mpdf/mpdf
This command will download mPDF and its dependencies into your project’s vendor directory and update your composer.json and composer.lock files. You’ll need to include the Composer autoloader in your PHP scripts to access mPDF classes.
Designing the Commission Ledger Data Structure
A well-defined data structure is crucial for accurate reporting. For this system, we’ll assume a custom database table or a set of post meta fields within WordPress to store commission records. Each record should ideally contain:
record_id(Unique identifier)vendor_id(Foreign key to vendor)client_id(Foreign key to client)invoice_id(Associated invoice)commission_amount(Numeric value)commission_rate(Percentage)transaction_date(Date of the transaction)payout_status(e.g., ‘Pending’, ‘Paid’, ‘Failed’)payout_date(Date of payout, if applicable)notes(Optional textual notes)
If you’re using custom tables, a simplified SQL schema might look like this:
CREATE TABLE wp_commission_ledger (
record_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
vendor_id BIGINT(20) UNSIGNED NOT NULL,
client_id BIGINT(20) UNSIGNED NOT NULL,
invoice_id BIGINT(20) UNSIGNED NULL,
commission_amount DECIMAL(10, 2) NOT NULL,
commission_rate DECIMAL(5, 2) NOT NULL,
transaction_date DATE NOT NULL,
payout_status VARCHAR(50) NOT NULL DEFAULT 'Pending',
payout_date DATETIME NULL,
notes TEXT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (record_id),
INDEX idx_vendor_id (vendor_id),
INDEX idx_transaction_date (transaction_date)
);
For WordPress post meta, you would typically associate commission records with a custom post type (e.g., ‘Commission Record’) or directly with vendor/client posts, using meta keys like _commission_amount, _transaction_date, etc.
Developing the PDF Generation Class
We’ll create a dedicated PHP class to encapsulate the PDF generation logic. This class will handle fetching data, formatting it, and rendering it into a PDF document using mPDF.
namespace App\Reporting;
use Mpdf\Mpdf;
use Mpdf\MpdfException;
use WP_Query; // Assuming WordPress environment
class CommissionReporter {
private $mpdf;
public function __construct() {
try {
$this->mpdf = new Mpdf([
'mode' => 'utf-8',
'format' => 'A4',
'margin_top' => 20,
'margin_bottom' => 20,
'margin_left' => 15,
'margin_right' => 15,
'default_font_size' => 10,
'default_font' => 'helvetica',
]);
$this->mpdf->SetCreator("Your Company Name");
$this->mpdf->SetAuthor("Automated Reporter");
$this->mpdf->SetTitle("Vendor Commission Report");
$this->mpdf->SetSubject("Monthly Commission Ledger");
$this->mpdf->SetKeywords("commission, report, vendor, ledger");
// Optional: Add header and footer
$this->mpdf->SetHTMLHeader('<div style="text-align: right; font-weight: bold;">Page {PAGENO} of {nbpg}</div>');
$this->mpdf->SetHTMLFooter('<div style="text-align: center;">Confidential - © ' . date('Y') . ' Your Company Name</div>');
} catch (MpdfException $e) {
// Log the error appropriately in a production environment
error_log("mPDF initialization error: " . $e->getMessage());
throw $e; // Re-throw to indicate failure
}
}
/**
* Fetches commission records from the database.
*
* @param array $args Query arguments (e.g., date range, vendor ID).
* @return array Array of commission records.
*/
public function fetchCommissionData(array $args = []): array {
// Example using WordPress WP_Query for custom post type 'commission_record'
// Adjust meta_query and tax_query as needed for your specific setup.
$default_args = [
'post_type' => 'commission_record',
'posts_per_page' => -1, // Fetch all
'post_status' => 'publish',
'meta_query' => [],
'orderby' => 'meta_value',
'order' => 'ASC',
];
// Example: Filter by date range
if (isset($args['start_date']) && isset($args['end_date'])) {
$default_args['meta_query'][] = [
'key' => '_transaction_date',
'value' => [$args['start_date'], $args['end_date']],
'type' => 'DATE',
'compare' => 'BETWEEN',
];
// Ensure order by date if filtering by date
$default_args['orderby'] = 'meta_value';
$default_args['order'] = 'ASC';
}
// Example: Filter by vendor ID
if (isset($args['vendor_id'])) {
$default_args['meta_query'][] = [
'key' => '_vendor_id',
'value' => $args['vendor_id'],
'compare' => '=',
];
}
// Example: Filter by payout status
if (isset($args['payout_status'])) {
$default_args['meta_query'][] = [
'key' => '_payout_status',
'value' => $args['payout_status'],
'compare' => '=',
];
}
// Merge user-provided args with defaults
$query_args = array_merge_recursive($default_args, $args);
// Ensure meta_query is correctly structured if only one condition is present
if (isset($query_args['meta_query']) && count($query_args['meta_query']) > 1) {
$query_args['meta_query']['relation'] = 'AND';
} elseif (isset($query_args['meta_query']) && count($query_args['meta_query']) === 1) {
// If only one meta query item, it should be an array, not nested further
$query_args['meta_query'] = [$query_args['meta_query']];
}
$commission_query = new WP_Query($query_args);
$records = [];
if ($commission_query->have_posts()) {
while ($commission_query->have_posts()) {
$commission_query->the_post();
$post_id = get_the_ID();
$records[] = [
'id' => $post_id,
'vendor_name' => get_post_meta($post_id, '_vendor_name', true) ?: 'N/A', // Assuming vendor name is stored
'client_name' => get_post_meta($post_id, '_client_name', true) ?: 'N/A', // Assuming client name is stored
'invoice_id' => get_post_meta($post_id, '_invoice_id', true) ?: 'N/A',
'commission_amount' => (float) get_post_meta($post_id, '_commission_amount', true),
'commission_rate' => (float) get_post_meta($post_id, '_commission_rate', true),
'transaction_date' => get_post_meta($post_id, '_transaction_date', true),
'payout_status' => get_post_meta($post_id, '_payout_status', true) ?: 'Pending',
'payout_date' => get_post_meta($post_id, '_payout_date', true) ?: 'N/A',
'notes' => get_post_meta($post_id, '_notes', true) ?: '',
];
}
wp_reset_postdata();
}
return $records;
}
/**
* Generates the PDF report.
*
* @param array $commission_data Commission records.
* @param string $report_title Title for the report.
* @return string The generated PDF content as a string.
* @throws MpdfException If PDF generation fails.
*/
public function generateReport(array $commission_data, string $report_title = "Commission Ledger Report"): string {
if (empty($commission_data)) {
$this->mpdf->WriteHTML('<h1>No Data Available</h1><p>No commission records found for the specified criteria.</p>');
return $this->mpdf->Output('', 'S');
}
$html = '<h1>' . esc_html($report_title) . '</h1>';
$html .= '<p>Report Generated On: ' . date('Y-m-d H:i:s') . '</p>';
// Table Header
$html .= '<table border="1" cellpadding="5" cellspacing="0" style="width: 100%; border-collapse: collapse;">';
$html .= '<thead><tr style="background-color: #f2f2f2;">';
$html .= '<th>Date</th>';
$html .= '<th>Vendor</th>';
$html .= '<th>Client</th>';
$html .= '<th>Invoice ID</th>';
$html .= '<th style="text-align: right;">Amount</th>';
$html .= '<th style="text-align: right;">Rate</th>';
$html .= '<th>Status</th>';
$html .= '<th>Payout Date</th>';
$html .= '</tr></thead><tbody>';
$total_commission = 0;
// Table Rows
foreach ($commission_data as $record) {
$html .= '<tr>';
$html .= '<td>' . esc_html($record['transaction_date']) . '</td>';
$html .= '<td>' . esc_html($record['vendor_name']) . '</td>';
$html .= '<td>' . esc_html($record['client_name']) . '</td>';
$html .= '<td>' . esc_html($record['invoice_id']) . '</td>';
$html .= '<td style="text-align: right;">' . number_format($record['commission_amount'], 2) . '</td>';
$html .= '<td style="text-align: right;">' . number_format($record['commission_rate'], 2) . '%</td>';
$html .= '<td>' . esc_html($record['payout_status']) . '</td>';
$html .= '<td>' . ($record['payout_date'] === 'N/A' ? 'N/A' : esc_html($record['payout_date'])) . '</td>';
$html .= '</tr>';
$total_commission += $record['commission_amount'];
}
// Table Footer (Totals)
$html .= '</tbody></table>';
$html .= '<br />';
$html .= '<table border="1" cellpadding="5" cellspacing="0" style="width: 30%; margin-left: auto; border-collapse: collapse;">';
$html .= '<tfoot><tr style="background-color: #f2f2f2;">';
$html .= '<th style="text-align: left;">Total Commission:</th>';
$html .= '<th style="text-align: right;">' . number_format($total_commission, 2) . '</th>';
$html .= '</tr></tfoot></table>';
try {
$this->mpdf->WriteHTML($html);
// Output as string (binary data)
return $this->mpdf->Output('', 'S');
} catch (MpdfException $e) {
error_log("mPDF generation error: " . $e->getMessage());
throw $e;
}
}
}
Explanation:
- The constructor initializes mPDF with basic settings and defines header/footer content. Error handling is included for initialization failures.
fetchCommissionDatais a placeholder for your data retrieval logic. The example usesWP_Queryto fetch custom post types, demonstrating filtering by date range, vendor ID, and payout status. Adapt this to your specific database schema (e.g., direct SQL queries if using custom tables).generateReporttakes the fetched data, constructs an HTML table, and uses mPDF’sWriteHTMLmethod to render it. It calculates and displays a total commission. The output is returned as a binary string usingOutput('', 'S').
Integrating with WordPress for Automated Triggering
To automate the reporting process, we can hook into WordPress’s action scheduler or use cron jobs. For this example, we’ll outline a method using a custom WordPress action that can be triggered manually or via a scheduled event.
First, ensure the CommissionReporter class is autoloaded. If you’re using Composer within WordPress, this is handled automatically. Otherwise, you might need to include the vendor autoloader manually.
// Include Composer's autoloader if not already done
// require_once __DIR__ . '/vendor/autoload.php'; // Adjust path as necessary
use App\Reporting\CommissionReporter;
/**
* Function to generate and save the commission report.
* Can be triggered via AJAX, WP-Cron, or Action Scheduler.
*/
function generate_and_save_commission_report() {
// Define reporting parameters (e.g., for the previous month)
$end_date = date('Y-m-t'); // End of current month
$start_date = date('Y-m-01', strtotime('-1 month')); // Start of previous month
$report_args = [
'start_date' => $start_date,
'end_date' => $end_date,
// 'vendor_id' => 123, // Optional: filter by specific vendor
// 'payout_status' => 'Paid', // Optional: filter by status
];
$reporter = new CommissionReporter();
$report_title = "Commission Report: " . date('F Y', strtotime($start_date));
try {
$commission_data = $reporter->fetchCommissionData($report_args);
$pdf_content = $reporter->generateReport($commission_data, $report_title);
// Define the upload directory
$upload_dir = wp_upload_dir();
$report_dir = $upload_dir['basedir'] . '/commission-reports/';
// Create directory if it doesn't exist
if (!file_exists($report_dir)) {
wp_mkdir_p($report_dir);
}
// Define filename
$filename = sanitize_title($report_title) . '_' . date('YmdHis') . '.pdf';
$filepath = $report_dir . $filename;
// Save the PDF file
if (file_put_contents($filepath, $pdf_content) !== false) {
// Optionally, log success or store the file path in post meta
error_log("Commission report generated successfully: " . $filepath);
return $filepath; // Return path for further processing
} else {
error_log("Failed to save commission report to: " . $filepath);
return false;
}
} catch (\Mpdf\MpdfException $e) {
error_log("Error generating commission report: " . $e->getMessage());
return false;
} catch (\Exception $e) {
error_log("An unexpected error occurred: " . $e->getMessage());
return false;
}
}
// Example: Hooking into an admin action for manual generation
add_action('admin_post_generate_commission_report', 'generate_and_save_commission_report');
// To trigger manually via URL:
// your-site.com/wp-admin/admin-post.php?action=generate_commission_report
// For scheduled generation, consider using WP-Cron or Action Scheduler.
// Example using WP-Cron (add to functions.php or a plugin file):
/*
if (!wp_next_scheduled('daily_commission_report_cron')) {
wp_schedule_event(time(), 'daily', 'daily_commission_report_cron');
}
add_action('daily_commission_report_cron', 'generate_and_save_commission_report');
*/
Advanced Considerations and Best Practices
When deploying this solution in a production environment, several advanced aspects should be addressed:
- Error Logging: Implement robust logging for mPDF exceptions, file writing errors, and data fetching issues. Use WordPress’s
error_log()or a dedicated logging library. - Security: Sanitize all data before outputting it into HTML for mPDF. Use WordPress functions like
esc_html(),esc_attr(), and ensure database queries are properly parameterized or use WordPress’s prepared statements. Restrict access to the manual trigger URL. - Performance: For very large datasets, consider pagination within the PDF generation or generating reports in batches. Optimize database queries using appropriate indexes.
- Configuration: Externalize settings like report templates, default date ranges, and output directories into WordPress options or a configuration file.
- Templating: For more complex layouts, explore mPDF’s templating capabilities or use a PHP templating engine (like Twig) to generate the HTML before passing it to mPDF.
- File Management: Implement a strategy for managing generated reports (e.g., archiving old reports, setting retention policies) to prevent disk space exhaustion.
- Action Scheduler: For reliable background job processing, especially in high-traffic sites, integrate with the Action Scheduler library. This provides robust queuing, retries, and status tracking for your report generation tasks.
- Internationalization: Ensure mPDF is configured for appropriate character sets and fonts if your data includes non-ASCII characters.
By following these guidelines, you can build a secure, efficient, and automated system for generating compliance reports for your custom vendor commission ledgers.
Setting Up the mPDF Environment for Automated Reporting
To implement automated compliance reporting for custom vendor commission records ledgers, we’ll leverage the mPDF library for PDF generation. This section details the initial setup, including installation and basic configuration, ensuring a robust foundation for our reporting system.
First, ensure you have Composer installed. If not, download and install it from getcomposer.org. Then, navigate to your WordPress project’s root directory in your terminal and run the following command to install mPDF:
composer require mpdf/mpdf
This command will download mPDF and its dependencies into your project’s vendor directory and update your composer.json and composer.lock files. You’ll need to include the Composer autoloader in your PHP scripts to access mPDF classes.
Designing the Commission Ledger Data Structure
A well-defined data structure is crucial for accurate reporting. For this system, we’ll assume a custom database table or a set of post meta fields within WordPress to store commission records. Each record should ideally contain:
record_id(Unique identifier)vendor_id(Foreign key to vendor)client_id(Foreign key to client)invoice_id(Associated invoice)commission_amount(Numeric value)commission_rate(Percentage)transaction_date(Date of the transaction)payout_status(e.g., ‘Pending’, ‘Paid’, ‘Failed’)payout_date(Date of payout, if applicable)notes(Optional textual notes)
If you’re using custom tables, a simplified SQL schema might look like this:
CREATE TABLE wp_commission_ledger (
record_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
vendor_id BIGINT(20) UNSIGNED NOT NULL,
client_id BIGINT(20) UNSIGNED NOT NULL,
invoice_id BIGINT(20) UNSIGNED NULL,
commission_amount DECIMAL(10, 2) NOT NULL,
commission_rate DECIMAL(5, 2) NOT NULL,
transaction_date DATE NOT NULL,
payout_status VARCHAR(50) NOT NULL DEFAULT 'Pending',
payout_date DATETIME NULL,
notes TEXT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (record_id),
INDEX idx_vendor_id (vendor_id),
INDEX idx_transaction_date (transaction_date)
);
For WordPress post meta, you would typically associate commission records with a custom post type (e.g., ‘Commission Record’) or directly with vendor/client posts, using meta keys like _commission_amount, _transaction_date, etc.
Developing the PDF Generation Class
We’ll create a dedicated PHP class to encapsulate the PDF generation logic. This class will handle fetching data, formatting it, and rendering it into a PDF document using mPDF.
namespace App\Reporting;
use Mpdf\Mpdf;
use Mpdf\MpdfException;
use WP_Query; // Assuming WordPress environment
class CommissionReporter {
private $mpdf;
public function __construct() {
try {
$this->mpdf = new Mpdf([
'mode' => 'utf-8',
'format' => 'A4',
'margin_top' => 20,
'margin_bottom' => 20,
'margin_left' => 15,
'margin_right' => 15,
'default_font_size' => 10,
'default_font' => 'helvetica',
]);
$this->mpdf->SetCreator("Your Company Name");
$this->mpdf->SetAuthor("Automated Reporter");
$this->mpdf->SetTitle("Vendor Commission Report");
$this->mpdf->SetSubject("Monthly Commission Ledger");
$this->mpdf->SetKeywords("commission, report, vendor, ledger");
// Optional: Add header and footer
$this->mpdf->SetHTMLHeader('<div style="text-align: right; font-weight: bold;">Page {PAGENO} of {nbpg}</div>');
$this->mpdf->SetHTMLFooter('<div style="text-align: center;">Confidential - © ' . date('Y') . ' Your Company Name</div>');
} catch (MpdfException $e) {
// Log the error appropriately in a production environment
error_log("mPDF initialization error: " . $e->getMessage());
throw $e; // Re-throw to indicate failure
}
}
/**
* Fetches commission records from the database.
*
* @param array $args Query arguments (e.g., date range, vendor ID).
* @return array Array of commission records.
*/
public function fetchCommissionData(array $args = []): array {
// Example using WordPress WP_Query for custom post type 'commission_record'
// Adjust meta_query and tax_query as needed for your specific setup.
$default_args = [
'post_type' => 'commission_record',
'posts_per_page' => -1, // Fetch all
'post_status' => 'publish',
'meta_query' => [],
'orderby' => 'meta_value',
'order' => 'ASC',
];
// Example: Filter by date range
if (isset($args['start_date']) && isset($args['end_date'])) {
$default_args['meta_query'][] = [
'key' => '_transaction_date',
'value' => [$args['start_date'], $args['end_date']],
'type' => 'DATE',
'compare' => 'BETWEEN',
];
// Ensure order by date if filtering by date
$default_args['orderby'] = 'meta_value';
$default_args['order'] = 'ASC';
}
// Example: Filter by vendor ID
if (isset($args['vendor_id'])) {
$default_args['meta_query'][] = [
'key' => '_vendor_id',
'value' => $args['vendor_id'],
'compare' => '=',
];
}
// Example: Filter by payout status
if (isset($args['payout_status'])) {
$default_args['meta_query'][] = [
'key' => '_payout_status',
'value' => $args['payout_status'],
'compare' => '=',
];
}
// Merge user-provided args with defaults
$query_args = array_merge_recursive($default_args, $args);
// Ensure meta_query is correctly structured if only one condition is present
if (isset($query_args['meta_query']) && count($query_args['meta_query']) > 1) {
$query_args['meta_query']['relation'] = 'AND';
} elseif (isset($query_args['meta_query']) && count($query_args['meta_query']) === 1) {
// If only one meta query item, it should be an array, not nested further
$query_args['meta_query'] = [$query_args['meta_query']];
}
$commission_query = new WP_Query($query_args);
$records = [];
if ($commission_query->have_posts()) {
while ($commission_query->have_posts()) {
$commission_query->the_post();
$post_id = get_the_ID();
$records[] = [
'id' => $post_id,
'vendor_name' => get_post_meta($post_id, '_vendor_name', true) ?: 'N/A', // Assuming vendor name is stored
'client_name' => get_post_meta($post_id, '_client_name', true) ?: 'N/A', // Assuming client name is stored
'invoice_id' => get_post_meta($post_id, '_invoice_id', true) ?: 'N/A',
'commission_amount' => (float) get_post_meta($post_id, '_commission_amount', true),
'commission_rate' => (float) get_post_meta($post_id, '_commission_rate', true),
'transaction_date' => get_post_meta($post_id, '_transaction_date', true),
'payout_status' => get_post_meta($post_id, '_payout_status', true) ?: 'Pending',
'payout_date' => get_post_meta($post_id, '_payout_date', true) ?: 'N/A',
'notes' => get_post_meta($post_id, '_notes', true) ?: '',
];
}
wp_reset_postdata();
}
return $records;
}
/**
* Generates the PDF report.
*
* @param array $commission_data Commission records.
* @param string $report_title Title for the report.
* @return string The generated PDF content as a string.
* @throws MpdfException If PDF generation fails.
*/
public function generateReport(array $commission_data, string $report_title = "Commission Ledger Report"): string {
if (empty($commission_data)) {
$this->mpdf->WriteHTML('<h1>No Data Available</h1><p>No commission records found for the specified criteria.</p>');
return $this->mpdf->Output('', 'S');
}
$html = '<h1>' . esc_html($report_title) . '</h1>';
$html .= '<p>Report Generated On: ' . date('Y-m-d H:i:s') . '</p>';
// Table Header
$html .= '<table border="1" cellpadding="5" cellspacing="0" style="width: 100%; border-collapse: collapse;">';
$html .= '<thead><tr style="background-color: #f2f2f2;">';
$html .= '<th>Date</th>';
$html .= '<th>Vendor</th>';
$html .= '<th>Client</th>';
$html .= '<th>Invoice ID</th>';
$html .= '<th style="text-align: right;">Amount</th>';
$html .= '<th style="text-align: right;">Rate</th>';
$html .= '<th>Status</th>';
$html .= '<th>Payout Date</th>';
$html .= '</tr></thead><tbody>';
$total_commission = 0;
// Table Rows
foreach ($commission_data as $record) {
$html .= '<tr>';
$html .= '<td>' . esc_html($record['transaction_date']) . '</td>';
$html .= '<td>' . esc_html($record['vendor_name']) . '</td>';
$html .= '<td>' . esc_html($record['client_name']) . '</td>';
$html .= '<td>' . esc_html($record['invoice_id']) . '</td>';
$html .= '<td style="text-align: right;">' . number_format($record['commission_amount'], 2) . '</td>';
$html .= '<td style="text-align: right;">' . number_format($record['commission_rate'], 2) . '%</td>';
$html .= '<td>' . esc_html($record['payout_status']) . '</td>';
$html .= '<td>' . ($record['payout_date'] === 'N/A' ? 'N/A' : esc_html($record['payout_date'])) . '</td>';
$html .= '</tr>';
$total_commission += $record['commission_amount'];
}
// Table Footer (Totals)
$html .= '</tbody></table>';
$html .= '<br />';
$html .= '<table border="1" cellpadding="5" cellspacing="0" style="width: 30%; margin-left: auto; border-collapse: collapse;">';
$html .= '<tfoot><tr style="background-color: #f2f2f2;">';
$html .= '<th style="text-align: left;">Total Commission:</th>';
$html .= '<th style="text-align: right;">' . number_format($total_commission, 2) . '</th>';
$html .= '</tr></tfoot></table>';
try {
$this->mpdf->WriteHTML($html);
// Output as string (binary data)
return $this->mpdf->Output('', 'S');
} catch (MpdfException $e) {
error_log("mPDF generation error: " . $e->getMessage());
throw $e;
}
}
}
Explanation:
- The constructor initializes mPDF with basic settings and defines header/footer content. Error handling is included for initialization failures.
fetchCommissionDatais a placeholder for your data retrieval logic. The example usesWP_Queryto fetch custom post types, demonstrating filtering by date range, vendor ID, and payout status. Adapt this to your specific database schema (e.g., direct SQL queries if using custom tables).generateReporttakes the fetched data, constructs an HTML table, and uses mPDF’sWriteHTMLmethod to render it. It calculates and displays a total commission. The output is returned as a binary string usingOutput('', 'S').
Integrating with WordPress for Automated Triggering
To automate the reporting process, we can hook into WordPress’s action scheduler or use cron jobs. For this example, we’ll outline a method using a custom WordPress action that can be triggered manually or via a scheduled event.
First, ensure the CommissionReporter class is autoloaded. If you’re using Composer within WordPress, this is handled automatically. Otherwise, you might need to include the vendor autoloader manually.
// Include Composer's autoloader if not already done
// require_once __DIR__ . '/vendor/autoload.php'; // Adjust path as necessary
use App\Reporting\CommissionReporter;
/**
* Function to generate and save the commission report.
* Can be triggered via AJAX, WP-Cron, or Action Scheduler.
*/
function generate_and_save_commission_report() {
// Define reporting parameters (e.g., for the previous month)
$end_date = date('Y-m-t'); // End of current month
$start_date = date('Y-m-01', strtotime('-1 month')); // Start of previous month
$report_args = [
'start_date' => $start_date,
'end_date' => $end_date,
// 'vendor_id' => 123, // Optional: filter by specific vendor
// 'payout_status' => 'Paid', // Optional: filter by status
];
$reporter = new CommissionReporter();
$report_title = "Commission Report: " . date('F Y', strtotime($start_date));
try {
$commission_data = $reporter->fetchCommissionData($report_args);
$pdf_content = $reporter->generateReport($commission_data, $report_title);
// Define the upload directory
$upload_dir = wp_upload_dir();
$report_dir = $upload_dir['basedir'] . '/commission-reports/';
// Create directory if it doesn't exist
if (!file_exists($report_dir)) {
wp_mkdir_p($report_dir);
}
// Define filename
$filename = sanitize_title($report_title) . '_' . date('YmdHis') . '.pdf';
$filepath = $report_dir . $filename;
// Save the PDF file
if (file_put_contents($filepath, $pdf_content) !== false) {
// Optionally, log success or store the file path in post meta
error_log("Commission report generated successfully: " . $filepath);
return $filepath; // Return path for further processing
} else {
error_log("Failed to save commission report to: " . $filepath);
return false;
}
} catch (\Mpdf\MpdfException $e) {
error_log("Error generating commission report: " . $e->getMessage());
return false;
} catch (\Exception $e) {
error_log("An unexpected error occurred: " . $e->getMessage());
return false;
}
}
// Example: Hooking into an admin action for manual generation
add_action('admin_post_generate_commission_report', 'generate_and_save_commission_report');
// To trigger manually via URL:
// your-site.com/wp-admin/admin-post.php?action=generate_commission_report
// For scheduled generation, consider using WP-Cron or Action Scheduler.
// Example using WP-Cron (add to functions.php or a plugin file):
/*
if (!wp_next_scheduled('daily_commission_report_cron')) {
wp_schedule_event(time(), 'daily', 'daily_commission_report_cron');
}
add_action('daily_commission_report_cron', 'generate_and_save_commission_report');
*/
Advanced Considerations and Best Practices
When deploying this solution in a production environment, several advanced aspects should be addressed:
- Error Logging: Implement robust logging for mPDF exceptions, file writing errors, and data fetching issues. Use WordPress’s
error_log()or a dedicated logging library. - Security: Sanitize all data before outputting it into HTML for mPDF. Use WordPress functions like
esc_html(),esc_attr(), and ensure database queries are properly parameterized or use WordPress’s prepared statements. Restrict access to the manual trigger URL. - Performance: For very large datasets, consider pagination within the PDF generation or generating reports in batches. Optimize database queries using appropriate indexes.
- Configuration: Externalize settings like report templates, default date ranges, and output directories into WordPress options or a configuration file.
- Templating: For more complex layouts, explore mPDF’s templating capabilities or use a PHP templating engine (like Twig) to generate the HTML before passing it to mPDF.
- File Management: Implement a strategy for managing generated reports (e.g., archiving old reports, setting retention policies) to prevent disk space exhaustion.
- Action Scheduler: For reliable background job processing, especially in high-traffic sites, integrate with the Action Scheduler library. This provides robust queuing, retries, and status tracking for your report generation tasks.
- Internationalization: Ensure mPDF is configured for appropriate character sets and fonts if your data includes non-ASCII characters.
By following these guidelines, you can build a secure, efficient, and automated system for generating compliance reports for your custom vendor commission ledgers.