# easy-invoice/2.3.4/includes/Migration/Src/MetaMigration.php

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.3.4. 720 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.3.4/code/includes/Migration/Src/MetaMigration.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.3.4/raw/includes/Migration/Src/MetaMigration.php
- Modified: 2026-02-15T10:35:10+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/easy-invoice/2.3.4/code/includes/Migration/Src/MetaMigration.php#L10-L20`.

```php
<?php
/**
 * Meta Migration for Easy Invoice
 *
 * @package     EasyInvoice
 * @subpackage  Migration
 * @since       2.0.0
 */

namespace EasyInvoice\Migration\Src;

/**
 * Meta migration class.
 *
 * Handles migration of meta data from old structure to new structure.
 *
 * @since 2.0.0
 */
class MetaMigration extends AbstractMigration {

    /**
     * Migration description.
     *
     * @since 2.0.0
     * @var string
     */
    protected $description = 'Migrate meta data to new structure';

    /**
     * Meta key mappings.
     *
     * @since 2.0.0
     * @var array
     */
    private $meta_mappings = [
        'easy-invoice' => [
            // Basic info
            'invoice_number' => '_easy_invoice_number',
            'created_date' => '_easy_invoice_date',
            'due_date' => '_easy_invoice_due_date',
            'status' => '_easy_invoice_status',
            'invoice_status' => '_easy_invoice_status', // Old plugin specific status field
            'currency' => '_easy_invoice_currency_code',
            'description' => '_easy_invoice_description',
            'notes' => '_easy_invoice_notes',
            'terms_and_conditions' => '_easy_invoice_terms',

            // Tax and discount
            'tax_type' => '_easy_invoice_prices_include_tax',
            'tax_rate' => '_easy_invoice_tax_rate',
            'discount' => '_easy_invoice_discount_value',
            'discount_type' => '_easy_invoice_discount_type',
            'discount_calculation_method' => '_easy_invoice_discount_calculation_method',

            // Items
            'easy_invoice_line_items' => '_easy_invoice_items',

            // Client info (these are used to find/create WordPress user)
            'client_name' => '_easy_invoice_client_name',
            'client_email' => '_easy_invoice_client_email',
            'client_company' => '_easy_invoice_client_company',
            'client_address' => '_easy_invoice_client_address',
            'client_phone' => '_easy_invoice_client_phone',
            'client_vat' => '_easy_invoice_client_vat',

            // Template settings
            'template' => '_easy_invoice_template'
        ],
        'easy-invoice-quotes' => [
            // Basic info
            'quote_number' => '_easy_invoice_quote_number',
            'created_date' => '_easy_invoice_quote_date',
            'valid_until' => '_easy_invoice_quote_expiry_date',
            'status' => '_easy_invoice_quote_status',
            'currency' => '_easy_invoice_quote_currency',
            'description' => '_easy_invoice_quote_description',
            'notes' => '_easy_invoice_quote_notes',

            // Tax and discount
            'tax_type' => '_easy_invoice_quote_prices_include_tax',
            'tax_rate' => '_easy_invoice_quote_tax_rate',
            'discount' => '_easy_invoice_quote_discount_value',
            'discount_type' => '_easy_invoice_quote_discount_type',
            'discount_calculation_method' => '_easy_invoice_quote_discount_calculation_method',

            // Items
            'easy_invoice_quote_line_items' => '_easy_invoice_quote_items',

            // Client info (these are used to find/create WordPress user)
            'client_name' => '_easy_invoice_quote_client_name',
            'client_email' => '_easy_invoice_quote_client_email',
            'client_company' => '_easy_invoice_quote_client_company',
            'client_address' => '_easy_invoice_quote_client_address',
            'client_phone' => '_easy_invoice_quote_client_phone',
            'client_vat' => '_easy_invoice_quote_client_vat',

            // Template settings
            'template' => '_easy_invoice_quote_template'
        ],
        'easy-invoice-payment' => [
            // Basic info
            'payment_number' => '_easy_payment_number',
            'payment_date' => '_easy_payment_date',
            'status' => '_easy_payment_status',
            'payment_method' => '_easy_payment_method',
            'transaction_id' => '_easy_payment_transaction_id',
            'notes' => '_easy_payment_notes',

            // Invoice info
            'invoice_id' => '_easy_payment_invoice_id',
            'invoice_number' => '_easy_payment_invoice_number',

            // Amount info
            'amount' => '_easy_payment_amount',
            'currency' => '_easy_payment_currency_code'
        ]
    ];

// ... (keep the rest of the existing code until transform_items_meta method)

    /**
     * Transform items meta.
     *
     * @since 2.0.0
     * @param mixed $items Items array
     * @return array
     */
    /**
     * Migrate post meta from old post to new post.
     *
     * @since 2.0.0
     * @param int $old_post_id Old post ID
     * @param int $new_post_id New post ID
     * @param string $post_type New post type
     * @return array
     */
    public function migrate_post_meta(int $old_post_id, int $new_post_id, string $post_type): array {
        try {
            global $wpdb;

            // Get all meta for the old post directly from database to ensure we get everything
            $meta_values = $wpdb->get_results($wpdb->prepare(
                "SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d",
                $old_post_id
            ), ARRAY_A);

            if (empty($meta_values)) {
                return [
                    'success' => true,
                    'message' => 'No meta to migrate',
                    'migrated_count' => 0
                ];
            }

            // Convert to format similar to get_post_custom
            $meta_data = [];
            foreach ($meta_values as $row) {
                $meta_data[$row['meta_key']][] = $row['meta_value'];
            }

            // Get the old post type
            $old_post_type = get_post_type($old_post_id);
            if (empty($old_post_type)) {
                $old_post_type = $wpdb->get_var($wpdb->prepare(
                    "SELECT post_type FROM {$wpdb->posts} WHERE ID = %d",
                    $old_post_id
                ));
            }

            if (empty($old_post_type)) {
                return [
                    'success' => false,
                    'message' => 'Could not determine old post type'
                ];
            }

            $migrated_count = 0;
            $errors = [];

            // Handle items first if they exist
            $possible_item_keys = [];
            $new_items_key = '';

            if ($post_type === 'easy_invoice') {
                $possible_item_keys = [
                    'easy_invoice_line_items',
                    '_easy_invoice_line_items',
                    'invoice_items',
                    '_invoice_items',
                    'line_items',
                    '_line_items'
                ];
                $new_items_key = '_easy_invoice_items';
            } elseif ($post_type === 'easy_invoice_quote') {
                $possible_item_keys = [
                    'easy_invoice_quote_line_items',
                    '_easy_invoice_quote_line_items',
                    'quote_items',
                    '_quote_items',
                    'line_items',
                    '_line_items'
                ];
                $new_items_key = '_easy_invoice_quote_items';
            }

            // Try to find items using the correct old plugin meta keys
            $items = null;
            if ($post_type === 'easy_invoice') {
                // For invoices, look for the correct old meta key
                if (isset($meta_data['easy_invoice_line_items'])) {
                    $items = $meta_data['easy_invoice_line_items'][0];
                    $this->log(sprintf('Found invoice items using key: easy_invoice_line_items for post %d', $old_post_id));
                }
            } elseif ($post_type === 'easy_invoice_quote') {
                // For quotes, look for the correct old meta key
                if (isset($meta_data['easy_invoice_quote_line_items'])) {
                    $items = $meta_data['easy_invoice_quote_line_items'][0];
                    $this->log(sprintf('Found quote items using key: easy_invoice_quote_line_items for post %d', $old_post_id));
                }
            }

            if ($items !== null) {
                $items = $this->transform_items_meta($items);
                if (!empty($items)) {
                    delete_post_meta($new_post_id, $new_items_key);
                    update_post_meta($new_post_id, $new_items_key, $items);
                    $migrated_count++;
                    $this->log(sprintf('Migrated %d items for post %d to %d', count($items), $old_post_id, $new_post_id));
                }
            }

            // Log available meta keys for debugging
            $this->log(sprintf('Available meta keys for post %d (type: %s): %s',
                $old_post_id,
                $old_post_type,
                implode(', ', array_keys($meta_data))
            ));

            // Get meta mappings for this post type
            $mappings = $this->meta_mappings[$old_post_type] ?? [];
            if (empty($mappings)) {
                return [
                    'success' => false,
                    'message' => sprintf('No meta mappings found for post type: %s', $old_post_type)
                ];
            }

            // Migrate each meta value
            foreach ($meta_data as $old_key => $values) {
                // Skip items keys as we've already handled them
                if (in_array($old_key, $possible_item_keys)) {
                    continue;
                }

                // Find the new key in mappings
                $new_key = $mappings[$old_key] ?? '';
                if (empty($new_key)) {
                    // Try without underscore prefix
                    if (strpos($old_key, '_') === 0) {
                        $new_key = $mappings[substr($old_key, 1)] ?? '';
                    }
                    // Try with underscore prefix
                    if (empty($new_key) && strpos($old_key, '_') !== 0) {
                        $new_key = $mappings['_' . $old_key] ?? '';
                    }
                    if (empty($new_key)) {
                        continue; // Skip if no mapping found
                    }
                }

                // Get the first value since WordPress stores meta as arrays
                $value = $values[0] ?? null;
                if ($value === null) {
                    continue;
                }

                // Delete any existing meta with the new key
                delete_post_meta($new_post_id, $new_key);

                // Skip invoice_number as it will be generated
                if ($old_key === 'invoice_number') {
                    continue;
                }

                // Transform and update the meta value
                $value = maybe_unserialize($value);

                // Special handling for tax_type
                if ($old_key === 'tax_type') {
                    $value = $value === 'inclusive' ? 'yes' : 'no';
                }

                // Special handling for status fields
                if ($old_key === 'status') {
                    $value = $this->transform_status_value($value, $post_type);
                    $this->log(sprintf('Transformed status from %s to %s for post type %s', $meta_data[$old_key][0], $value, $post_type));
                }

                // Special handling for dates
                if (in_array($old_key, ['created_date', 'due_date', 'valid_until'])) {
                    // Convert from "August 11, 2025" to "2025-08-11"
                    if ($timestamp = strtotime($value)) {
                        $value = date('Y-m-d', $timestamp);
                        $this->log(sprintf('Converted date %s to %s for key %s', $meta_data[$old_key][0], $value, $old_key));
                    } else {
                        $this->log(sprintf('Failed to parse date: %s for key %s', $value, $old_key), 'warning');
                    }
                }

                // Update meta
                $result = update_post_meta($new_post_id, $new_key, $value);
                if ($result) {
                    $migrated_count++;
                    $this->log(sprintf('Migrated meta %s to %s for post %d', $old_key, $new_key, $new_post_id));
            } else {
                    $errors[] = sprintf('Failed to migrate meta: %s', $old_key);
                    $this->log(sprintf('Failed to migrate meta %s to %s for post %d', $old_key, $new_key, $new_post_id), 'error');
                }
            }

            // After migrating all meta, handle client ID assignment
            if ($post_type === 'easy_invoice' || $post_type === 'easy_invoice_quote') {
                // Get client email from meta
                $client_email = '';
                foreach ($meta_data as $key => $values) {
                    if (in_array($key, ['client_email', '_client_email', 'easy_invoice_client_email', 'easy_invoice_quote_client_email'])) {
                        $client_email = $values[0] ?? '';
                        break;
                    }
                }

                if (!empty($client_email)) {
                    // Try to find existing user by email first
                    $user = get_user_by('email', $client_email);

                    if ($user) {
                        $user_id = $user->ID;
                    } else {
                        // Use ClientMigration to get/create user if not found
                        $client_migration = new ClientMigration();

                        // Get client name from meta with all possible keys
                        $client_name = '';
                        $name_keys = [
                            'client_name',
                            '_client_name',
                            'easy_invoice_client_name',
                            'easy_invoice_quote_client_name',
                            'name',
                            '_name',
                            'customer_name',
                            '_customer_name'
                        ];
                        foreach ($name_keys as $key) {
                            if (isset($meta_data[$key][0]) && !empty($meta_data[$key][0])) {
                                $client_name = $meta_data[$key][0];
                                break;
                            }
                        }

                        // Create client data array
                        $client_data = [
                            'email' => $client_email,
                            'name' => $client_name,
                            'company' => $meta_data['client_company'][0] ?? $meta_data['_client_company'][0] ?? $meta_data['company'][0] ?? $meta_data['_company'][0] ?? '',
                            'address' => $meta_data['client_address'][0] ?? $meta_data['_client_address'][0] ?? $meta_data['address'][0] ?? $meta_data['_address'][0] ?? '',
                            'phone' => $meta_data['client_phone'][0] ?? $meta_data['_client_phone'][0] ?? $meta_data['phone'][0] ?? $meta_data['_phone'][0] ?? '',
                            'vat' => $meta_data['client_vat'][0] ?? $meta_data['_client_vat'][0] ?? $meta_data['vat'][0] ?? $meta_data['_vat'][0] ?? ''
                        ];

                        // Get user ID from ClientMigration
                        $user_id = $client_migration->get_or_create_user($client_data);
                    }

                    if ($user_id > 0) {
                        // Set client ID in post meta
                        $client_id_meta_key = $post_type === 'easy_invoice' ? '_easy_invoice_client_id' : '_easy_invoice_quote_client_id';
                        update_post_meta($new_post_id, $client_id_meta_key, $user_id);
                        $this->log(sprintf('Assigned client ID %d to %s %d', $user_id, $post_type, $new_post_id));
                    }
                }
            }

            // Generate new invoice/quote number
            if ($post_type === 'easy_invoice') {
                if (class_exists('\\EasyInvoice\\Services\\InvoiceNumberService')) {
                    $invoice_service = new \EasyInvoice\Services\InvoiceNumberService();
                    $new_invoice_number = $invoice_service->generateUniqueNumber();
                    update_post_meta($new_post_id, '_easy_invoice_number', $new_invoice_number);
                    $this->log(sprintf('Generated new invoice number: %s for post %d', $new_invoice_number, $new_post_id));
                    $migrated_count++;
                }
            } elseif ($post_type === 'easy_invoice_quote') {
                if (class_exists('\\EasyInvoice\\Services\\QuoteNumberService')) {
                    $quote_service = new \EasyInvoice\Services\QuoteNumberService();
                    $new_quote_number = $quote_service->generateUniqueNumber();
                    update_post_meta($new_post_id, '_easy_invoice_quote_number', $new_quote_number);
                    $this->log(sprintf('Generated new quote number: %s for post %d', $new_quote_number, $new_post_id));
                    $migrated_count++;
                }
            }

            if (empty($errors)) {
            return [
                'success' => true,
                    'message' => sprintf('Successfully migrated %d meta entries', $migrated_count),
                    'migrated_count' => $migrated_count
            ];
        } else {
                return [
                    'success' => false,
                    'message' => 'Meta migration completed with errors: ' . implode(', ', $errors),
                    'migrated_count' => $migrated_count
                ];
            }

        } catch (\Exception $e) {
            $this->log(sprintf('Failed to migrate meta for post %d: %s', $old_post_id, $e->getMessage()), 'error');
            return [
                'success' => false,
                'message' => $e->getMessage()
            ];
        }
    }

    private function transform_items_meta($items): array {
        if (!is_array($items)) {
            $items = maybe_unserialize($items);
        }

        if (!is_array($items)) {
            return [];
        }

        $transformed = [];
        foreach ($items as $item) {
            if (!is_array($item)) {
                $item = maybe_unserialize($item);
            }

            if (!is_array($item)) {
                continue;
            }

            // The old format has these keys:
            // quantity, item_title, adjust, rate, description, taxable

            $title = $item['item_title'] ?? '';
            $description = $item['description'] ?? '';

            // Handle quantity (always a string in old format)
            $quantity = isset($item['quantity']) ? floatval($item['quantity']) : 1;
            // Allow quantity 0 - don't force minimum of 1

            // Handle price (called 'rate' in old format)
            $price = isset($item['rate']) ? floatval($item['rate']) : 0;

            // Handle adjustment (called 'adjust' in old format)
            // The old system stored adjust as a percentage value (e.g., 10 for 10%)
            $adjust_percentage = 0;
            if (isset($item['adjust']) && !empty($item['adjust'])) {
                $adjust_percentage = floatval($item['adjust']);
                // Ensure it's a valid percentage
                if ($adjust_percentage < -100) {
                    $adjust_percentage = -100; // Cap at -100%
                }
            }

            // Skip if no title
            if (empty($title)) {
                continue;
            }

            // Calculate total based on old system logic
            // Old system: total = quantity * rate * (1 + adjust_percentage/100)
            $total = $quantity * $price;
            if ($adjust_percentage != 0) {
                $total = $total * (1 + $adjust_percentage / 100);
            }

            // Handle taxable field - this is critical for tax calculations
            // The old plugin logic: isset($line_item['taxable']) && (boolean)$line_item['taxable']
            // This means: if field doesn't exist OR is falsy → NOT taxable
            $taxable = '0'; // Default to NOT taxable (following old plugin logic)
            $original_taxable = $item['taxable'] ?? 'NOT_SET';

            if (isset($item['taxable'])) {
                // The old system used (boolean)$item['taxable']
                // Convert various formats to '1' or '0'
                $tax_value = $item['taxable'];

                // Log the original taxable value and type for debugging
                $this->log(sprintf('Processing taxable field: value=%s, type=%s', var_export($tax_value, true), gettype($tax_value)));

                if (is_bool($tax_value)) {
                    $taxable = $tax_value ? '1' : '0';
                    $this->log(sprintf('Boolean taxable: %s -> %s', var_export($tax_value, true), $taxable));
                } elseif (is_string($tax_value)) {
                    $tax_value = strtolower(trim($tax_value));
                    if (in_array($tax_value, ['0', 'false', 'no', 'n', 'off'], true)) {
                        $taxable = '0';
                        $this->log(sprintf('String taxable (false): %s -> %s', $tax_value, $taxable));
                    } else {
                        $taxable = '1'; // Only set to taxable if explicitly truthy
                        $this->log(sprintf('String taxable (true): %s -> %s', $tax_value, $taxable));
                    }
                } elseif (is_numeric($tax_value)) {
                    $taxable = $tax_value > 0 ? '1' : '0';
                    $this->log(sprintf('Numeric taxable: %s -> %s', $tax_value, $taxable));
                } else {
                    $this->log(sprintf('Unknown taxable type: %s, value: %s', gettype($tax_value), var_export($tax_value, true)));
                }
            } elseif (isset($item['tax_exempt'])) {
                // Some versions used tax_exempt instead
                $exempt_value = $item['tax_exempt'];
                $this->log(sprintf('Using tax_exempt field: %s', var_export($exempt_value, true)));

                if (is_bool($exempt_value)) {
                    $taxable = $exempt_value ? '0' : '1'; // tax_exempt = true means NOT taxable
                    $this->log(sprintf('Boolean tax_exempt: %s -> taxable: %s', var_export($exempt_value, true), $taxable));
                } elseif (is_string($exempt_value)) {
                    $exempt_value = strtolower(trim($exempt_value));
                    if (in_array($exempt_value, ['1', 'true', 'yes', 'y', 'on'], true)) {
                        $taxable = '0'; // tax_exempt = true means NOT taxable
                        $this->log(sprintf('String tax_exempt (true): %s -> taxable: %s', $exempt_value, $taxable));
                    } else {
                        $this->log(sprintf('String tax_exempt (false): %s -> taxable: %s', $exempt_value, $taxable));
                    }
                }
            } elseif (isset($item['is_taxable'])) {
                // Some versions used is_taxable
                $is_taxable = $item['is_taxable'];
                $this->log(sprintf('Using is_taxable field: %s', var_export($is_taxable, true)));

                if (is_bool($is_taxable)) {
                    $taxable = $is_taxable ? '1' : '0';
                    $this->log(sprintf('Boolean is_taxable: %s -> taxable: %s', var_export($is_taxable, true), $taxable));
                } elseif (is_string($is_taxable)) {
                    $is_taxable = strtolower(trim($is_taxable));
                    if (in_array($is_taxable, ['0', 'false', 'no', 'n', 'off'], true)) {
                        $taxable = '0';
                        $this->log(sprintf('String is_taxable (false): %s -> taxable: %s', $is_taxable, $taxable));
                    } else {
                        $this->log(sprintf('String is_taxable (true): %s -> taxable: %s', $is_taxable, $taxable));
                    }
                }
            } else {
                $this->log(sprintf('No taxable field found, using default: %s', $taxable));
            }

            // Log the transformation for debugging
            $this->log(sprintf(
                'Transformed item: title="%s", quantity=%f, price=%f, adjust_percentage=%f, taxable=%s, total=%f (original taxable: %s, original adjust: %s)',
                $title, $quantity, $price, $adjust_percentage, $taxable, $total,
                var_export($item['taxable'] ?? 'NOT_SET', true),
                var_export($item['adjust'] ?? 'NOT_SET', true)
            ));

            $transformed[] = [
                'title' => $title,
                'description' => $description,
                'quantity' => $quantity,
                'price' => $price,
                'adjust_percentage' => $adjust_percentage,
                'total' => $total,
                'taxable' => $taxable,
                'id' => 0 // Required by new format
            ];
        }

        return $transformed;
    }

    /**
     * Check if migration is needed.
     *
     * @since 2.0.0
     * @return bool
     */
    public function is_needed(): bool {
        global $wpdb;

        // Check if any old post types have meta that needs migration
        $old_post_types = ['easy-invoice', 'easy-invoice-quotes', 'easy-invoice-payment'];

        foreach ($old_post_types as $post_type) {
            $count = $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->postmeta} pm
                JOIN {$wpdb->posts} p ON p.ID = pm.post_id
                WHERE p.post_type = %s AND p.post_status NOT IN ('auto-draft', 'trash', 'inherit')",
                $post_type
            ));

            if ($count > 0) {
                $this->log(sprintf('Found %d meta entries for post type %s that need migration', $count, $post_type));
                return true;
            }
        }

        return false;
    }

    /**
     * Run the migration.
     *
     * @since 2.0.0
     * @return array
     */
    public function migrate(): array {
        try {
            global $wpdb;

            $migrated_count = 0;
            $errors = [];

            // Get all posts from old post types
            $old_post_types = ['easy-invoice', 'easy-invoice-quotes', 'easy-invoice-payment'];
            $new_post_types = [
                'easy-invoice' => 'easy_invoice',
                'easy-invoice-quotes' => 'easy_invoice_quote',
                'easy-invoice-payment' => 'easy_payment'
            ];

            foreach ($old_post_types as $old_post_type) {
                $posts = $wpdb->get_results($wpdb->prepare(
                    "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status NOT IN ('auto-draft', 'trash', 'inherit')",
                    $old_post_type
                ));

                if (empty($posts)) {
                    continue;
                }

                foreach ($posts as $post) {
                    // Get the new post ID from the mapping
                    $new_post_id = get_option('easy_invoice_migration_post_mapping_' . $post->ID, 0);
                    if ($new_post_id <= 0) {
                        $errors[] = sprintf('No mapping found for old post ID: %d', $post->ID);
                        continue;
                    }

                    // Get the new post type
                    $new_post_type = $new_post_types[$old_post_type] ?? '';
                    if (empty($new_post_type)) {
                        $errors[] = sprintf('No post type mapping found for: %s', $old_post_type);
                        continue;
                    }

                    // Migrate meta
                    $result = $this->migrate_post_meta($post->ID, $new_post_id, $new_post_type);
                    if ($result['success']) {
                        $migrated_count += $result['migrated_count'];
                    } else {
                        $errors[] = $result['message'];
                    }
                }
            }

            if (empty($errors)) {
                return [
                    'success' => true,
                    'message' => sprintf('Successfully migrated %d meta entries', $migrated_count),
                    'migrated_count' => $migrated_count
                ];
            } else {
                return [
                    'success' => false,
                    'message' => 'Meta migration completed with errors: ' . implode(', ', $errors),
                    'migrated_count' => $migrated_count
                ];
            }

        } catch (\Exception $e) {
            $this->log('Meta migration failed: ' . $e->getMessage(), 'error');
            return [
                'success' => false,
                'message' => $e->getMessage()
            ];
        }
    }

    /**
     * Transform status value from old plugin format to new plugin format.
     *
     * @since 2.0.0
     * @param string $old_status Old status value
     * @param string $post_type Post type (easy_invoice or easy_invoice_quote)
     * @return string New status value
     */
    private function transform_status_value(string $old_status, string $post_type): string {
        $old_status = strtolower(trim($old_status));

        if ($post_type === 'easy_invoice') {
            // Invoice status mapping
            $status_mapping = [
                'available' => 'unpaid',    // Old 'available' becomes 'unpaid' in new plugin
                'draft' => 'draft',        // 'draft' stays the same
                'paid' => 'paid',          // 'paid' stays the same
                'cancelled' => 'cancelled' // 'cancelled' stays the same
            ];
        } elseif ($post_type === 'easy_invoice_quote') {
            // Quote status mapping
            $status_mapping = [
                'available' => 'available', // 'available' stays the same
                'draft' => 'draft',        // 'draft' stays the same
                'sent' => 'sent',          // 'sent' stays the same
                'declined' => 'declined',  // 'declined' stays the same
                'cancelled' => 'cancelled' // 'cancelled' stays the same
            ];
        } else {
            // Unknown post type, return original status
            return $old_status;
        }

        // Return mapped status or original if no mapping found
        $this->log(sprintf('Status transformation: %s -> %s for post type %s', $old_status, $status_mapping[$old_status] ?? $old_status, $post_type));
        return $status_mapping[$old_status] ?? $old_status;
    }
}

```
