# Product Unification Implementation Plan

**Project:** Unifying Sales Products and Catalog Products  
**Approach:** Unified Variant-Based Architecture  
**Timeline:** 10 Weeks  
**Status:** Implementation Ready

---

## Table of Contents

1. [Overview](#1-overview)
2. [Prerequisites](#2-prerequisites)
3. [Phase-by-Phase Implementation](#3-phase-by-phase-implementation)
4. [Database Migrations](#4-database-migrations)
5. [Model Changes](#5-model-changes)
6. [Service Layer Changes](#6-service-layer-changes)
7. [Controller Changes](#7-controller-changes)
8. [API Routes](#8-api-routes)
9. [Testing Requirements](#9-testing-requirements)
10. [Deployment Checklist](#10-deployment-checklist)
11. [Rollback Procedures](#11-rollback-procedures)

---

## 1. Overview

This document provides a detailed, step-by-step implementation plan for unifying Sales Products (`sales_products`) and Catalog Products (`catalog_products`) into a single unified variant-based architecture.

### 1.1 Architecture Decision

**Strategy:** Unified Variant-Based Architecture
- All products use variant-based system
- Simple products (sales-style) have single variant
- Complex products (catalog-style) can have multiple variants
- Backward compatibility maintained during transition

### 1.2 Key Principles

1. **No Data Loss** - All existing data must be preserved
2. **Backward Compatibility** - Old APIs continue to work during transition
3. **Incremental Migration** - Migrate in phases, not all at once
4. **Dual-Write Period** - Write to both old and new systems during transition
5. **Feature Flags** - Use feature flags to control rollout

---

## 2. Prerequisites

### 2.1 Environment Setup

- [ ] Staging environment identical to production
- [ ] Database backup created
- [ ] Git branch created: `feature/product-unification`
- [ ] Feature flag system configured
- [ ] Monitoring and logging enabled

### 2.2 Required Knowledge

- Laravel migrations
- Eloquent ORM relationships
- Database transactions
- API versioning
- Testing frameworks

### 2.3 Tools Required

- Database migration tools
- Data validation scripts
- Performance monitoring tools
- API testing tools (Postman/Insomnia)

---

## 3. Phase-by-Phase Implementation

### Phase 1: Database Schema Creation (Week 1)

#### Step 1.1: Create Unified Products Table

**File:** `database/migrations/YYYY_MM_DD_HHMMSS_create_unified_products_table.php`

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->foreignId('company_id')->constrained('companies')->cascadeOnDelete()->cascadeOnUpdate();
            $table->foreignId('user_id')->nullable()->constrained('users')->cascadeOnDelete()->cascadeOnUpdate();
            
            // Core identification
            $table->json('canonical_name'); // Translatable name
            $table->json('description')->nullable(); // Translatable description
            $table->string('code')->nullable()->index(); // Product code (ITM-xxx format)
            
            // Status and type
            $table->enum('status', ['draft', 'active', 'discontinued', 'pending', 'inactive'])
                  ->default('draft')->index();
            $table->enum('product_type', ['physical', 'digital', 'spare_part', 'service'])
                  ->default('physical');
            
            // Categorization
            $table->foreignId('catalog_item_id')->nullable()
                  ->constrained('catalog_category_items')->nullOnDelete();
            $table->foreignId('category_id')->nullable()
                  ->constrained('categories')->nullOnDelete(); // Sales category
            $table->foreignId('parent_id')->nullable()
                  ->constrained('products')->nullOnDelete(); // Product hierarchy
            
            // Product metadata
            $table->string('brand')->nullable();
            $table->string('manufacturer')->nullable();
            
            // Sales-specific fields
            $table->string('section')->nullable()->index(); // 'accounting', 'procurement'
            $table->boolean('is_simple_product')->default(false)->index(); // Flag for sales-style products
            
            // Accounting accounts
            $table->foreignId('revenue_account_id')->nullable()
                  ->constrained('chart_of_accounts')->nullOnDelete();
            $table->foreignId('expense_account_id')->nullable()
                  ->constrained('chart_of_accounts')->nullOnDelete();
            $table->foreignId('inventory_account_id')->nullable()
                  ->constrained('chart_of_accounts')->nullOnDelete();
            $table->foreignId('cogs_account_id')->nullable()
                  ->constrained('chart_of_accounts')->nullOnDelete();
            $table->foreignId('purchase_account_id')->nullable()
                  ->constrained('chart_of_accounts')->nullOnDelete();
            
            // Tax and cost center
            $table->foreignId('tax_id')->nullable()
                  ->constrained('taxes')->cascadeOnDelete()->cascadeOnUpdate();
            $table->foreignId('purchase_tax_id')->nullable()
                  ->constrained('taxes')->cascadeOnDelete()->cascadeOnUpdate();
            $table->foreignId('cost_center_id')->nullable()
                  ->constrained('cost_centers')->nullOnDelete();
            
            // Timestamps
            $table->softDeletes();
            $table->timestamps();
            
            // Indexes
            $table->index(['company_id', 'status']);
            $table->index(['company_id', 'section']);
            $table->index(['company_id', 'is_simple_product']);
            $table->index(['catalog_item_id']);
            $table->index(['category_id']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};
```

#### Step 1.2: Create Unified Product Variants Table

**File:** `database/migrations/YYYY_MM_DD_HHMMSS_create_unified_product_variants_table.php`

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('product_variants', function (Blueprint $table) {
            $table->id();
            $table->foreignId('product_id')->constrained('products')->cascadeOnDelete();
            $table->foreignId('variant_group_id')->nullable()
                  ->constrained('catalog_product_variant_groups')->nullOnDelete();
            
            // Identification
            $table->string('sku')->nullable()->unique()->index();
            $table->string('barcode')->nullable()->index();
            
            // Sales-specific fields (for simple products)
            $table->boolean('track_stock')->default(false);
            $table->decimal('initial_stock_level', 15, 4)->default(0);
            $table->integer('min_qty')->default(1);
            $table->boolean('is_returnable')->default(false);
            $table->decimal('discount_amount', 15, 4)->nullable();
            $table->string('discount_type')->nullable(); // 'fixed', 'percentage'
            $table->boolean('is_taxable')->default(false);
            $table->string('sale_tax')->nullable();
            $table->string('purchase_tax')->nullable();
            $table->string('barcode_type')->nullable(); // 'barcode', 'qr'
            $table->string('barcode_path')->nullable();
            
            // Catalog-specific fields
            $table->string('origin_country')->nullable();
            $table->enum('quality_grade', ['oem', 'compatible', 'aftermarket', 'refurbished'])->nullable();
            $table->string('manufacturer_location')->nullable();
            $table->string('certification')->nullable();
            $table->string('warranty_terms')->nullable();
            $table->boolean('is_preferred')->default(false);
            
            // Notes
            $table->text('notes')->nullable();
            
            // Timestamps
            $table->softDeletes();
            $table->timestamps();
            
            // Indexes
            $table->index(['product_id']);
            $table->index(['sku']);
            $table->index(['barcode']);
            $table->index(['quality_grade']);
            $table->index(['is_preferred']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('product_variants');
    }
};
```

#### Step 1.3: Create Unified Product Variant Prices Table

**File:** `database/migrations/YYYY_MM_DD_HHMMSS_create_unified_product_variant_prices_table.php`

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('product_variant_prices', function (Blueprint $table) {
            $table->id();
            $table->foreignId('variant_id')->constrained('product_variants')->cascadeOnDelete();
            $table->foreignId('unit_id')->constrained('measurement_units')->restrictOnDelete();
            $table->foreignId('company_id')->constrained('companies')->cascadeOnDelete();
            $table->foreignId('price_type_id')->nullable()
                  ->constrained('catalog_price_types')->nullOnDelete(); // For catalog products
            
            // Sales pricing fields
            $table->decimal('purchasing_price', 15, 4)->nullable();
            $table->decimal('selling_price', 15, 4)->nullable();
            $table->decimal('min_selling_price', 15, 4)->nullable();
            $table->boolean('is_main_price')->default(false); // Sales products
            
            // Catalog pricing fields
            $table->decimal('price', 15, 2)->nullable(); // Generic price
            $table->string('currency', 3)->default('USD');
            $table->decimal('min_quantity', 15, 3)->nullable();
            $table->date('valid_from')->nullable();
            $table->date('valid_to')->nullable();
            
            // Timestamps
            $table->timestamps();
            
            // Indexes
            $table->index(['variant_id', 'unit_id']);
            $table->index(['company_id']);
            $table->index(['price_type_id']);
            $table->index(['currency']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('product_variant_prices');
    }
};
```

#### Step 1.4: Create Migration Tracking Table

**File:** `database/migrations/YYYY_MM_DD_HHMMSS_create_product_migration_logs_table.php`

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('product_migration_logs', function (Blueprint $table) {
            $table->id();
            $table->string('source_table'); // 'sales_products' or 'catalog_products'
            $table->unsignedBigInteger('source_id');
            $table->string('target_table')->default('products');
            $table->unsignedBigInteger('target_id');
            $table->unsignedBigInteger('target_variant_id')->nullable();
            $table->enum('status', ['pending', 'completed', 'failed', 'rolled_back'])->default('pending');
            $table->text('error_message')->nullable();
            $table->json('metadata')->nullable(); // Additional migration data
            $table->timestamps();
            
            $table->index(['source_table', 'source_id']);
            $table->index(['target_table', 'target_id']);
            $table->index(['status']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('product_migration_logs');
    }
};
```

#### Step 1.5: Add Variant ID to Related Tables

**File:** `database/migrations/YYYY_MM_DD_HHMMSS_add_variant_id_to_sales_invoice_products_table.php`

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('sales_invoice_products', function (Blueprint $table) {
            $table->foreignId('variant_id')->nullable()
                  ->after('product_id')
                  ->constrained('product_variants')->nullOnDelete();
            $table->index(['variant_id']);
        });
    }

    public function down(): void
    {
        Schema::table('sales_invoice_products', function (Blueprint $table) {
            $table->dropForeign(['variant_id']);
            $table->dropColumn('variant_id');
        });
    }
};
```

**Repeat for:**
- `purchase_products` table
- `product_stores` table
- Any other tables referencing `sales_products` or `catalog_products`

---

### Phase 2: Model Creation (Week 2)

#### Step 2.1: Create Unified Product Model

**File:** `app/Domains/Products/Models/Product.php`

```php
<?php

namespace App\Domains\Products\Models;

use App\Models\BaseModel;
use App\Domains\Products\Enums\ProductStatus;
use App\Domains\Products\Enums\ProductType;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Translatable\HasTranslations;

class Product extends BaseModel
{
    use HasTranslations, SoftDeletes;

    protected $table = 'products';

    protected static bool $applyCompanyScope = true;

    protected $fillable = [
        'company_id',
        'user_id',
        'canonical_name',
        'description',
        'code',
        'status',
        'product_type',
        'catalog_item_id',
        'category_id',
        'parent_id',
        'brand',
        'manufacturer',
        'section',
        'is_simple_product',
        'revenue_account_id',
        'expense_account_id',
        'inventory_account_id',
        'cogs_account_id',
        'purchase_account_id',
        'tax_id',
        'purchase_tax_id',
        'cost_center_id',
    ];

    protected $casts = [
        'canonical_name' => 'array',
        'description' => 'array',
        'status' => ProductStatus::class,
        'product_type' => ProductType::class,
        'is_simple_product' => 'boolean',
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
        'deleted_at' => 'datetime',
    ];

    public array $translatable = ['canonical_name', 'description'];

    public static array $searchCols = ['canonical_name', 'code', 'brand', 'manufacturer'];
    public static array $filtersCols = ['status', 'product_type', 'section', 'is_simple_product', 'category_id', 'catalog_item_id'];

    // Relationships
    public function variants()
    {
        return $this->hasMany(ProductVariant::class);
    }

    public function defaultVariant()
    {
        return $this->hasOne(ProductVariant::class)->where('is_preferred', true)
            ->orWhere(function ($query) {
                $query->whereNull('is_preferred')->orderBy('id');
            });
    }

    public function catalogItem()
    {
        return $this->belongsTo(\App\Domains\Catalog\Categories\Models\CategoryItem::class, 'catalog_item_id');
    }

    public function category()
    {
        return $this->belongsTo(\App\Domains\Accounting\Entities\Models\Category::class, 'category_id');
    }

    public function parent()
    {
        return $this->belongsTo(Product::class, 'parent_id');
    }

    public function children()
    {
        return $this->hasMany(Product::class, 'parent_id');
    }

    public function images()
    {
        return $this->hasMany(ProductImage::class);
    }

    public function revenueAccount()
    {
        return $this->belongsTo(\App\Domains\Accounting\ChartOfAccount\Models\ChartOfAccount::class, 'revenue_account_id');
    }

    public function expenseAccount()
    {
        return $this->belongsTo(\App\Domains\Accounting\ChartOfAccount\Models\ChartOfAccount::class, 'expense_account_id');
    }

    public function inventoryAccount()
    {
        return $this->belongsTo(\App\Domains\Accounting\ChartOfAccount\Models\ChartOfAccount::class, 'inventory_account_id');
    }

    public function cogsAccount()
    {
        return $this->belongsTo(\App\Domains\Accounting\ChartOfAccount\Models\ChartOfAccount::class, 'cogs_account_id');
    }

    public function purchaseAccount()
    {
        return $this->belongsTo(\App\Domains\Accounting\ChartOfAccount\Models\ChartOfAccount::class, 'purchase_account_id');
    }

    public function tax()
    {
        return $this->belongsTo(\App\Domains\Accounting\Entities\Models\Tax::class, 'tax_id');
    }

    public function purchaseTax()
    {
        return $this->belongsTo(\App\Domains\Accounting\Entities\Models\Tax::class, 'purchase_tax_id');
    }

    public function costCenter()
    {
        return $this->belongsTo(\App\Domains\Accounting\Entities\Models\CostCenter::class, 'cost_center_id');
    }

    // Scopes
    public function scopeSimpleProducts($query)
    {
        return $query->where('is_simple_product', true);
    }

    public function scopeComplexProducts($query)
    {
        return $query->where('is_simple_product', false);
    }

    public function scopeBySection($query, string $section)
    {
        return $query->where('section', $section);
    }

    public function scopeActive($query)
    {
        return $query->where('status', ProductStatus::ACTIVE);
    }

    // Helper methods
    public function getNameAttribute()
    {
        // For backward compatibility with sales products
        if ($this->is_simple_product) {
            return $this->canonical_name['en'] ?? $this->canonical_name[app()->getLocale()] ?? reset($this->canonical_name);
        }
        return $this->canonical_name;
    }

    public function getDescriptionAttribute()
    {
        // For backward compatibility
        if ($this->is_simple_product && is_array($this->attributes['description'] ?? null)) {
            return $this->attributes['description']['en'] ?? $this->attributes['description'][app()->getLocale()] ?? reset($this->attributes['description']);
        }
        return $this->attributes['description'] ?? null;
    }
}
```

#### Step 2.2: Create Unified Product Variant Model

**File:** `app/Domains/Products/Models/ProductVariant.php`

```php
<?php

namespace App\Domains\Products\Models;

use App\Models\BaseModel;
use App\Domains\Products\Enums\QualityGrade;
use Illuminate\Database\Eloquent\SoftDeletes;

class ProductVariant extends BaseModel
{
    use SoftDeletes;

    protected $table = 'product_variants';

    protected $fillable = [
        'product_id',
        'variant_group_id',
        'sku',
        'barcode',
        'track_stock',
        'initial_stock_level',
        'min_qty',
        'is_returnable',
        'discount_amount',
        'discount_type',
        'is_taxable',
        'sale_tax',
        'purchase_tax',
        'barcode_type',
        'barcode_path',
        'origin_country',
        'quality_grade',
        'manufacturer_location',
        'certification',
        'warranty_terms',
        'is_preferred',
        'notes',
    ];

    protected $casts = [
        'track_stock' => 'boolean',
        'initial_stock_level' => 'decimal:4',
        'min_qty' => 'integer',
        'is_returnable' => 'boolean',
        'discount_amount' => 'decimal:4',
        'is_taxable' => 'boolean',
        'quality_grade' => QualityGrade::class,
        'is_preferred' => 'boolean',
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
        'deleted_at' => 'datetime',
    ];

    public static array $searchCols = ['sku', 'barcode'];
    public static array $filtersCols = ['quality_grade', 'is_preferred'];

    // Relationships
    public function product()
    {
        return $this->belongsTo(Product::class);
    }

    public function prices()
    {
        return $this->hasMany(ProductVariantPrice::class, 'variant_id');
    }

    public function mainPrice()
    {
        return $this->hasOne(ProductVariantPrice::class, 'variant_id')
            ->where('is_main_price', true);
    }

    public function units()
    {
        return $this->hasMany(ProductVariantUnit::class, 'variant_id');
    }

    public function images()
    {
        return $this->hasMany(ProductImage::class, 'product_variant_id');
    }

    public function inventory()
    {
        return $this->hasMany(\App\Domains\Catalog\Inventory\Models\Inventory::class, 'variant_id');
    }

    // Scopes
    public function scopePreferred($query)
    {
        return $query->where('is_preferred', true);
    }
}
```

#### Step 2.3: Create Unified Product Variant Price Model

**File:** `app/Domains/Products/Models/ProductVariantPrice.php`

```php
<?php

namespace App\Domains\Products\Models;

use App\Models\BaseModel;

class ProductVariantPrice extends BaseModel
{
    protected $table = 'product_variant_prices';

    protected $fillable = [
        'variant_id',
        'unit_id',
        'company_id',
        'price_type_id',
        'purchasing_price',
        'selling_price',
        'min_selling_price',
        'is_main_price',
        'price',
        'currency',
        'min_quantity',
        'valid_from',
        'valid_to',
    ];

    protected $casts = [
        'purchasing_price' => 'decimal:4',
        'selling_price' => 'decimal:4',
        'min_selling_price' => 'decimal:4',
        'is_main_price' => 'boolean',
        'price' => 'decimal:2',
        'min_quantity' => 'decimal:3',
        'valid_from' => 'date',
        'valid_to' => 'date',
    ];

    // Relationships
    public function variant()
    {
        return $this->belongsTo(ProductVariant::class, 'variant_id');
    }

    public function unit()
    {
        return $this->belongsTo(\App\Domains\Accounting\Entities\Models\MeasurementUnit::class, 'unit_id');
    }

    public function priceType()
    {
        return $this->belongsTo(\App\Domains\Catalog\Pricing\Models\PriceType::class, 'price_type_id');
    }
}
```

#### Step 2.4: Create Enums

**File:** `app/Domains/Products/Enums/ProductStatus.php`

```php
<?php

namespace App\Domains\Products\Enums;

enum ProductStatus: string
{
    case DRAFT = 'draft';
    case ACTIVE = 'active';
    case DISCONTINUED = 'discontinued';
    case PENDING = 'pending';
    case INACTIVE = 'inactive';

    public function label(): string
    {
        return match($this) {
            self::DRAFT => __('Draft'),
            self::ACTIVE => __('Active'),
            self::DISCONTINUED => __('Discontinued'),
            self::PENDING => __('Pending'),
            self::INACTIVE => __('Inactive'),
        };
    }
}
```

**File:** `app/Domains/Products/Enums/ProductType.php`

```php
<?php

namespace App\Domains\Products\Enums;

enum ProductType: string
{
    case PHYSICAL = 'physical';
    case DIGITAL = 'digital';
    case SPARE_PART = 'spare_part';
    case SERVICE = 'service';

    public function label(): string
    {
        return match($this) {
            self::PHYSICAL => __('Physical'),
            self::DIGITAL => __('Digital'),
            self::SPARE_PART => __('Spare Part'),
            self::SERVICE => __('Service'),
        };
    }
}
```

---

### Phase 3: Migration Service (Week 3)

#### Step 3.1: Create Migration Service

**File:** `app/Domains/Products/Services/ProductMigrationService.php`

```php
<?php

namespace App\Domains\Products\Services;

use App\Domains\Products\Models\Product;
use App\Domains\Products\Models\ProductVariant;
use App\Domains\Products\Models\ProductVariantPrice;
use App\Domains\Products\Models\ProductMigrationLog;
use App\Domains\Accounting\Sales\Models\Product as SalesProduct;
use App\Domains\Catalog\Products\Models\Product as CatalogProduct;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ProductMigrationService
{
    /**
     * Migrate all sales products to unified products
     */
    public function migrateSalesProducts(): array
    {
        $results = [
            'total' => 0,
            'success' => 0,
            'failed' => 0,
            'errors' => [],
        ];

        $salesProducts = SalesProduct::with(['prices.unit', 'media', 'category'])->get();

        foreach ($salesProducts as $salesProduct) {
            $results['total']++;
            
            try {
                DB::transaction(function () use ($salesProduct, &$results) {
                    $this->migrateSalesProduct($salesProduct);
                    $results['success']++;
                });
            } catch (\Exception $e) {
                $results['failed']++;
                $results['errors'][] = [
                    'product_id' => $salesProduct->id,
                    'product_name' => $salesProduct->name,
                    'error' => $e->getMessage(),
                ];
                Log::error("Failed to migrate sales product {$salesProduct->id}: " . $e->getMessage());
            }
        }

        return $results;
    }

    /**
     * Migrate a single sales product
     */
    protected function migrateSalesProduct(SalesProduct $salesProduct): Product
    {
        // Check if already migrated
        $existingLog = ProductMigrationLog::where('source_table', 'sales_products')
            ->where('source_id', $salesProduct->id)
            ->where('status', 'completed')
            ->first();

        if ($existingLog) {
            return Product::findOrFail($existingLog->target_id);
        }

        // Create unified product
        $product = Product::create([
            'company_id' => $salesProduct->company_id,
            'user_id' => $salesProduct->user_id,
            'canonical_name' => ['en' => $salesProduct->name], // Convert to translatable
            'description' => $salesProduct->description ? ['en' => $salesProduct->description] : null,
            'code' => $salesProduct->code,
            'status' => $salesProduct->status ? 'active' : 'inactive',
            'product_type' => 'physical',
            'section' => $salesProduct->section ?? 'accounting',
            'is_simple_product' => true,
            'category_id' => $salesProduct->category_id,
            'tax_id' => $salesProduct->tax_id,
            'purchase_tax_id' => $salesProduct->purchase_tax_id,
            'purchase_account_id' => $salesProduct->purchase_account_id,
            'cost_center_id' => $salesProduct->cost_center_id,
        ]);

        // Create single variant
        $variant = ProductVariant::create([
            'product_id' => $product->id,
            'sku' => $salesProduct->code, // Use code as SKU
            'barcode' => $salesProduct->barcode,
            'track_stock' => $salesProduct->track_stock,
            'initial_stock_level' => $salesProduct->initial_stock_level,
            'min_qty' => $salesProduct->min_qty,
            'is_returnable' => $salesProduct->is_returnable ?? false,
            'discount_amount' => $salesProduct->discount_amount,
            'discount_type' => $salesProduct->discount_type,
            'is_taxable' => $salesProduct->is_taxable,
            'sale_tax' => $salesProduct->sale_tax,
            'purchase_tax' => $salesProduct->purchase_tax,
            'barcode_type' => $salesProduct->barcode_type,
            'barcode_path' => $salesProduct->barcode_path,
            'notes' => $salesProduct->notes,
        ]);

        // Migrate prices
        foreach ($salesProduct->prices as $price) {
            ProductVariantPrice::create([
                'variant_id' => $variant->id,
                'unit_id' => $price->unit_id,
                'company_id' => $salesProduct->company_id,
                'purchasing_price' => $price->purchasing_price,
                'selling_price' => $price->selling_price,
                'min_selling_price' => $price->min_selling_price,
                'is_main_price' => $price->is_main_price ?? false,
            ]);
        }

        // Log migration
        ProductMigrationLog::create([
            'source_table' => 'sales_products',
            'source_id' => $salesProduct->id,
            'target_table' => 'products',
            'target_id' => $product->id,
            'target_variant_id' => $variant->id,
            'status' => 'completed',
        ]);

        return $product;
    }

    /**
     * Migrate all catalog products to unified products
     */
    public function migrateCatalogProducts(): array
    {
        $results = [
            'total' => 0,
            'success' => 0,
            'failed' => 0,
            'errors' => [],
        ];

        $catalogProducts = CatalogProduct::with(['variants.prices', 'variants.units'])->get();

        foreach ($catalogProducts as $catalogProduct) {
            $results['total']++;
            
            try {
                DB::transaction(function () use ($catalogProduct, &$results) {
                    $this->migrateCatalogProduct($catalogProduct);
                    $results['success']++;
                });
            } catch (\Exception $e) {
                $results['failed']++;
                $results['errors'][] = [
                    'product_id' => $catalogProduct->id,
                    'error' => $e->getMessage(),
                ];
                Log::error("Failed to migrate catalog product {$catalogProduct->id}: " . $e->getMessage());
            }
        }

        return $results;
    }

    /**
     * Migrate a single catalog product
     */
    protected function migrateCatalogProduct(CatalogProduct $catalogProduct): Product
    {
        // Check if already migrated
        $existingLog = ProductMigrationLog::where('source_table', 'catalog_products')
            ->where('source_id', $catalogProduct->id)
            ->where('status', 'completed')
            ->first();

        if ($existingLog) {
            return Product::findOrFail($existingLog->target_id);
        }

        // Create unified product
        $product = Product::create([
            'company_id' => $catalogProduct->company_id,
            'catalog_item_id' => $catalogProduct->catalog_item_id,
            'parent_id' => $catalogProduct->parent_id,
            'canonical_name' => $catalogProduct->canonical_name,
            'description' => $catalogProduct->description,
            'brand' => $catalogProduct->brand,
            'manufacturer' => $catalogProduct->manufacturer,
            'product_type' => $catalogProduct->product_type->value,
            'status' => $catalogProduct->status->value,
            'is_simple_product' => false,
            'revenue_account_id' => $catalogProduct->revenue_account_id,
            'expense_account_id' => $catalogProduct->expense_account_id,
            'inventory_account_id' => $catalogProduct->inventory_account_id,
            'cogs_account_id' => $catalogProduct->cogs_account_id,
        ]);

        // Migrate variants
        foreach ($catalogProduct->variants as $catalogVariant) {
            $variant = ProductVariant::create([
                'product_id' => $product->id,
                'variant_group_id' => $catalogVariant->variant_group_id,
                'sku' => $catalogVariant->sku,
                'barcode' => $catalogVariant->barcode,
                'origin_country' => $catalogVariant->origin_country,
                'quality_grade' => $catalogVariant->quality_grade?->value,
                'manufacturer_location' => $catalogVariant->manufacturer_location,
                'certification' => $catalogVariant->certification,
                'warranty_terms' => $catalogVariant->warranty_terms,
                'is_preferred' => $catalogVariant->is_preferred,
                'notes' => $catalogVariant->notes,
            ]);

            // Migrate variant prices
            foreach ($catalogVariant->prices as $price) {
                ProductVariantPrice::create([
                    'variant_id' => $variant->id,
                    'unit_id' => $price->unit_id ?? $catalogVariant->units->first()?->unit_id,
                    'company_id' => $catalogProduct->company_id,
                    'price_type_id' => $price->price_type_id,
                    'price' => $price->price,
                    'currency' => $price->currency ?? 'USD',
                    'min_quantity' => $price->min_quantity,
                    'valid_from' => $price->valid_from,
                    'valid_to' => $price->valid_to,
                ]);
            }
        }

        // Log migration
        ProductMigrationLog::create([
            'source_table' => 'catalog_products',
            'source_id' => $catalogProduct->id,
            'target_table' => 'products',
            'target_id' => $product->id,
            'status' => 'completed',
        ]);

        return $product;
    }

    /**
     * Validate migrated data
     */
    public function validateMigration(): array
    {
        $issues = [];

        // Check sales products
        $salesProducts = SalesProduct::count();
        $migratedSales = ProductMigrationLog::where('source_table', 'sales_products')
            ->where('status', 'completed')
            ->count();

        if ($salesProducts !== $migratedSales) {
            $issues[] = "Sales products mismatch: {$salesProducts} total, {$migratedSales} migrated";
        }

        // Check catalog products
        $catalogProducts = CatalogProduct::count();
        $migratedCatalog = ProductMigrationLog::where('source_table', 'catalog_products')
            ->where('status', 'completed')
            ->count();

        if ($catalogProducts !== $migratedCatalog) {
            $issues[] = "Catalog products mismatch: {$catalogProducts} total, {$migratedCatalog} migrated";
        }

        // Check for orphaned variants
        $orphanedVariants = ProductVariant::whereDoesntHave('product')->count();
        if ($orphanedVariants > 0) {
            $issues[] = "Found {$orphanedVariants} orphaned variants";
        }

        return $issues;
    }
}
```

#### Step 3.2: Create Migration Command

**File:** `app/Console/Commands/MigrateProductsCommand.php`

```php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Domains\Products\Services\ProductMigrationService;

class MigrateProductsCommand extends Command
{
    protected $signature = 'products:migrate {--type=all : Migration type: all, sales, catalog} {--validate : Validate migration}';

    protected $description = 'Migrate sales and catalog products to unified products';

    public function handle(ProductMigrationService $service)
    {
        $type = $this->option('type');

        if ($this->option('validate')) {
            $this->info('Validating migration...');
            $issues = $service->validateMigration();
            
            if (empty($issues)) {
                $this->info('✓ Migration validation passed');
            } else {
                $this->error('✗ Migration validation failed:');
                foreach ($issues as $issue) {
                    $this->error("  - {$issue}");
                }
            }
            return;
        }

        if ($type === 'all' || $type === 'sales') {
            $this->info('Migrating sales products...');
            $results = $service->migrateSalesProducts();
            $this->displayResults('Sales Products', $results);
        }

        if ($type === 'all' || $type === 'catalog') {
            $this->info('Migrating catalog products...');
            $results = $service->migrateCatalogProducts();
            $this->displayResults('Catalog Products', $results);
        }

        $this->info('Migration completed!');
    }

    protected function displayResults(string $type, array $results)
    {
        $this->info("{$type} Migration Results:");
        $this->info("  Total: {$results['total']}");
        $this->info("  Success: {$results['success']}");
        $this->info("  Failed: {$results['failed']}");
        
        if (!empty($results['errors'])) {
            $this->error('  Errors:');
            foreach ($results['errors'] as $error) {
                $this->error("    - Product ID {$error['product_id']}: {$error['error']}");
            }
        }
    }
}
```

---

### Phase 4: Dual-Write Service (Week 4)

#### Step 4.1: Create Dual-Write Service

**File:** `app/Domains/Products/Services/DualWriteService.php`

```php
<?php

namespace App\Domains\Products\Services;

use App\Domains\Products\Models\Product;
use App\Domains\Accounting\Sales\Models\Product as SalesProduct;
use App\Domains\Catalog\Products\Models\Product as CatalogProduct;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class DualWriteService
{
    protected bool $enabled;

    public function __construct()
    {
        $this->enabled = config('features.dual_write_products', false);
    }

    /**
     * Write to unified system and optionally to old system
     */
    public function createProduct(array $data, bool $isSimpleProduct = false): Product
    {
        $product = DB::transaction(function () use ($data, $isSimpleProduct) {
            // Always write to unified system
            $product = Product::create($data);

            // Write to old system if dual-write enabled
            if ($this->enabled) {
                if ($isSimpleProduct) {
                    $this->writeToSalesProducts($product, $data);
                } else {
                    $this->writeToCatalogProducts($product, $data);
                }
            }

            return $product;
        });

        return $product;
    }

    /**
     * Update unified product and optionally old system
     */
    public function updateProduct(Product $product, array $data): Product
    {
        DB::transaction(function () use ($product, $data) {
            $product->update($data);

            if ($this->enabled) {
                // Find corresponding old product
                $migrationLog = \App\Domains\Products\Models\ProductMigrationLog::where('target_id', $product->id)
                    ->where('status', 'completed')
                    ->first();

                if ($migrationLog) {
                    if ($migrationLog->source_table === 'sales_products') {
                        $oldProduct = SalesProduct::find($migrationLog->source_id);
                        if ($oldProduct) {
                            $this->updateSalesProduct($oldProduct, $product, $data);
                        }
                    } else {
                        $oldProduct = CatalogProduct::find($migrationLog->source_id);
                        if ($oldProduct) {
                            $this->updateCatalogProduct($oldProduct, $product, $data);
                        }
                    }
                }
            }
        });

        return $product->fresh();
    }

    protected function writeToSalesProducts(Product $product, array $data)
    {
        // Map unified product to sales product format
        $salesData = [
            'company_id' => $product->company_id,
            'user_id' => $product->user_id,
            'name' => $product->canonical_name['en'] ?? reset($product->canonical_name),
            'description' => $product->description['en'] ?? reset($product->description ?? []),
            'code' => $product->code,
            'status' => $product->status === 'active' ? 1 : 0,
            'section' => $product->section ?? 'accounting',
            'category_id' => $product->category_id,
            'tax_id' => $product->tax_id,
            'purchase_tax_id' => $product->purchase_tax_id,
            'purchase_account_id' => $product->purchase_account_id,
            'cost_center_id' => $product->cost_center_id,
        ];

        // Get variant data
        $variant = $product->defaultVariant;
        if ($variant) {
            $salesData['barcode'] = $variant->barcode;
            $salesData['track_stock'] = $variant->track_stock;
            $salesData['initial_stock_level'] = $variant->initial_stock_level;
            $salesData['min_qty'] = $variant->min_qty;
            $salesData['is_returnable'] = $variant->is_returnable;
            $salesData['discount_amount'] = $variant->discount_amount;
            $salesData['discount_type'] = $variant->discount_type;
            $salesData['is_taxable'] = $variant->is_taxable;
            $salesData['sale_tax'] = $variant->sale_tax;
            $salesData['purchase_tax'] = $variant->purchase_tax;
            $salesData['barcode_type'] = $variant->barcode_type;
            $salesData['barcode_path'] = $variant->barcode_path;
            $salesData['notes'] = $variant->notes;
        }

        SalesProduct::create($salesData);
    }

    protected function writeToCatalogProducts(Product $product, array $data)
    {
        $catalogData = [
            'company_id' => $product->company_id,
            'catalog_item_id' => $product->catalog_item_id,
            'parent_id' => $product->parent_id,
            'canonical_name' => $product->canonical_name,
            'description' => $product->description,
            'brand' => $product->brand,
            'manufacturer' => $product->manufacturer,
            'product_type' => $product->product_type,
            'status' => $product->status,
            'revenue_account_id' => $product->revenue_account_id,
            'expense_account_id' => $product->expense_account_id,
            'inventory_account_id' => $product->inventory_account_id,
            'cogs_account_id' => $product->cogs_account_id,
        ];

        CatalogProduct::create($catalogData);
    }

    protected function updateSalesProduct(SalesProduct $oldProduct, Product $product, array $data)
    {
        $updateData = [
            'name' => $product->canonical_name['en'] ?? reset($product->canonical_name),
            'description' => $product->description['en'] ?? reset($product->description ?? []),
            'code' => $product->code,
            'status' => $product->status === 'active' ? 1 : 0,
            'section' => $product->section ?? 'accounting',
        ];

        $oldProduct->update($updateData);
    }

    protected function updateCatalogProduct(CatalogProduct $oldProduct, Product $product, array $data)
    {
        $updateData = [
            'canonical_name' => $product->canonical_name,
            'description' => $product->description,
            'brand' => $product->brand,
            'manufacturer' => $product->manufacturer,
            'status' => $product->status,
        ];

        $oldProduct->update($updateData);
    }
}
```

---

### Phase 5: Unified Service Layer (Week 5)

#### Step 5.1: Create Unified Product Service

**File:** `app/Domains/Products/Services/ProductService.php`

```php
<?php

namespace App\Domains\Products\Services;

use App\Domains\Products\Models\Product;
use App\Domains\Products\Models\ProductVariant;
use App\Domains\Products\Models\ProductVariantPrice;
use App\Domains\Products\Services\DualWriteService;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;

class ProductService
{
    public function __construct(
        protected DualWriteService $dualWriteService
    ) {}

    public function index(array $filters = []): LengthAwarePaginator
    {
        $query = Product::query()
            ->with(['variants.prices.unit', 'defaultVariant.prices.unit', 'category', 'catalogItem']);

        // Apply filters
        if (isset($filters['section'])) {
            $query->where('section', $filters['section']);
        }

        if (isset($filters['is_simple_product'])) {
            $query->where('is_simple_product', $filters['is_simple_product']);
        }

        if (isset($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        return $query->filter()
            ->orderBy('created_at', 'desc')
            ->paginate($filters['per_page'] ?? 15);
    }

    public function find(int $id): Product
    {
        return Product::with([
            'variants.prices.unit',
            'variants.units',
            'defaultVariant.prices.unit',
            'category',
            'catalogItem',
            'revenueAccount',
            'expenseAccount',
            'inventoryAccount',
            'cogsAccount',
            'purchaseAccount',
            'tax',
            'purchaseTax',
            'costCenter',
            'images',
        ])->findOrFail($id);
    }

    public function create(array $data): Product
    {
        return DB::transaction(function () use ($data) {
            // Extract variant data
            $variantsData = $data['variants'] ?? [];
            $isSimpleProduct = $data['is_simple_product'] ?? false;

            // Remove variants from product data
            unset($data['variants']);

            // Create product
            $product = $this->dualWriteService->createProduct($data, $isSimpleProduct);

            // Create variants
            if (!empty($variantsData)) {
                foreach ($variantsData as $variantData) {
                    $this->createVariant($product, $variantData);
                }
            } elseif ($isSimpleProduct) {
                // Create default variant for simple products
                $this->createDefaultVariant($product, $data);
            }

            return $product->load(['variants.prices.unit', 'defaultVariant']);
        });
    }

    public function update(Product $product, array $data): Product
    {
        return DB::transaction(function () use ($product, $data) {
            // Extract variant data
            $variantsData = $data['variants'] ?? [];
            unset($data['variants']);

            // Update product
            $product = $this->dualWriteService->updateProduct($product, $data);

            // Update variants if provided
            if (!empty($variantsData)) {
                $this->updateVariants($product, $variantsData);
            }

            return $product->fresh(['variants.prices.unit', 'defaultVariant']);
        });
    }

    public function delete(Product $product): bool
    {
        return DB::transaction(function () use ($product) {
            // Check for dependencies
            $this->checkDependencies($product);

            return $product->delete();
        });
    }

    protected function createVariant(Product $product, array $data): ProductVariant
    {
        $pricesData = $data['prices'] ?? [];
        unset($data['prices']);

        $variant = $product->variants()->create($data);

        // Create prices
        foreach ($pricesData as $priceData) {
            $variant->prices()->create($priceData);
        }

        return $variant;
    }

    protected function createDefaultVariant(Product $product, array $data): ProductVariant
    {
        $variantData = [
            'sku' => $product->code,
            'track_stock' => $data['track_stock'] ?? false,
            'initial_stock_level' => $data['initial_stock_level'] ?? 0,
            'min_qty' => $data['min_qty'] ?? 1,
            'is_returnable' => $data['is_returnable'] ?? false,
        ];

        $variant = $product->variants()->create($variantData);

        // Create default price if provided
        if (isset($data['prices']) && !empty($data['prices'])) {
            foreach ($data['prices'] as $priceData) {
                $variant->prices()->create($priceData);
            }
        }

        return $variant;
    }

    protected function updateVariants(Product $product, array $variantsData)
    {
        $existingVariants = $product->variants()->get();
        $updatedIds = [];

        foreach ($variantsData as $variantData) {
            if (isset($variantData['id'])) {
                // Update existing variant
                $variant = $existingVariants->find($variantData['id']);
                if ($variant) {
                    $this->updateVariant($variant, $variantData);
                    $updatedIds[] = $variant->id;
                }
            } else {
                // Create new variant
                $variant = $this->createVariant($product, $variantData);
                $updatedIds[] = $variant->id;
            }
        }

        // Delete variants not in update list
        $product->variants()->whereNotIn('id', $updatedIds)->delete();
    }

    protected function updateVariant(ProductVariant $variant, array $data)
    {
        $pricesData = $data['prices'] ?? [];
        unset($data['prices']);

        $variant->update($data);

        // Update prices
        if (!empty($pricesData)) {
            $variant->prices()->delete();
            foreach ($pricesData as $priceData) {
                $variant->prices()->create($priceData);
            }
        }
    }

    protected function checkDependencies(Product $product)
    {
        // Check if product is used in invoices
        $invoiceCount = DB::table('sales_invoice_products')
            ->where('product_id', $product->id)
            ->count();

        if ($invoiceCount > 0) {
            throw new \Exception("Cannot delete product: it is used in {$invoiceCount} invoice(s)");
        }

        // Add other dependency checks as needed
    }
}
```

---

### Phase 6: Controller Updates (Week 6)

#### Step 6.1: Create Unified Product Controller

**File:** `app/Domains/Products/HTTP/Controllers/V1/ProductController.php`

```php
<?php

namespace App\Domains\Products\HTTP\Controllers\V1;

use App\Http\Controllers\Controller;
use App\Domains\Products\Services\ProductService;
use App\Domains\Products\HTTP\Requests\StoreProductRequest;
use App\Domains\Products\HTTP\Requests\UpdateProductRequest;
use App\Domains\Products\HTTP\Resources\ProductResource;
use Illuminate\Http\JsonResponse;

class ProductController extends Controller
{
    public function __construct(
        protected ProductService $productService
    ) {}

    public function index(): JsonResponse
    {
        $filters = request()->only(['section', 'is_simple_product', 'status', 'per_page']);
        $products = $this->productService->index($filters);
        
        return $this->sendPaginatedResponse(ProductResource::collection($products));
    }

    public function show(int $id): JsonResponse
    {
        $product = $this->productService->find($id);
        return $this->sendSuccessResponse(new ProductResource($product));
    }

    public function store(StoreProductRequest $request): JsonResponse
    {
        $product = $this->productService->create($request->validated());
        return $this->sendSuccessResponse(
            new ProductResource($product),
            __('Product created successfully'),
            201
        );
    }

    public function update(UpdateProductRequest $request, int $id): JsonResponse
    {
        $product = $this->productService->find($id);
        $updated = $this->productService->update($product, $request->validated());
        
        return $this->sendSuccessResponse(
            new ProductResource($updated),
            __('Product updated successfully')
        );
    }

    public function destroy(int $id): JsonResponse
    {
        $product = $this->productService->find($id);
        $this->productService->delete($product);
        
        return $this->sendSuccessResponse(
            null,
            __('Product deleted successfully')
        );
    }
}
```

#### Step 6.2: Create Backward Compatible Adapters

**File:** `app/Domains/Products/Adapters/SalesProductAdapter.php`

```php
<?php

namespace App\Domains\Products\Adapters;

use App\Domains\Products\Models\Product;
use App\Domains\Products\Models\ProductMigrationLog;

class SalesProductAdapter
{
    /**
     * Convert unified product to sales product format
     */
    public static function toSalesFormat(Product $product): array
    {
        $variant = $product->defaultVariant;

        return [
            'id' => $product->id,
            'name' => $product->getNameAttribute(),
            'description' => $product->getDescriptionAttribute(),
            'code' => $product->code,
            'status' => $product->status === 'active' ? 1 : 0,
            'section' => $product->section,
            'barcode' => $variant?->barcode,
            'track_stock' => $variant?->track_stock ?? false,
            'initial_stock_level' => $variant?->initial_stock_level ?? 0,
            'min_qty' => $variant?->min_qty ?? 1,
            'is_returnable' => $variant?->is_returnable ?? false,
            'discount_amount' => $variant?->discount_amount,
            'discount_type' => $variant?->discount_type,
            'is_taxable' => $variant?->is_taxable ?? false,
            'sale_tax' => $variant?->sale_tax,
            'purchase_tax' => $variant?->purchase_tax,
            'barcode_type' => $variant?->barcode_type,
            'barcode_path' => $variant?->barcode_path,
            'category_id' => $product->category_id,
            'tax_id' => $product->tax_id,
            'purchase_tax_id' => $product->purchase_tax_id,
            'purchase_account_id' => $product->purchase_account_id,
            'cost_center_id' => $product->cost_center_id,
            'prices' => $variant?->prices->map(function ($price) {
                return [
                    'id' => $price->id,
                    'unit_id' => $price->unit_id,
                    'purchasing_price' => $price->purchasing_price,
                    'selling_price' => $price->selling_price,
                    'min_selling_price' => $price->min_selling_price,
                    'is_main_price' => $price->is_main_price,
                ];
            }) ?? [],
        ];
    }

    /**
     * Find unified product from sales product ID
     */
    public static function findUnifiedProduct(int $salesProductId): ?Product
    {
        $migrationLog = ProductMigrationLog::where('source_table', 'sales_products')
            ->where('source_id', $salesProductId)
            ->where('status', 'completed')
            ->first();

        if ($migrationLog) {
            return Product::find($migrationLog->target_id);
        }

        return null;
    }
}
```

---

### Phase 7: Testing (Week 7-8)

#### Step 7.1: Unit Tests

**File:** `tests/Unit/Products/ProductMigrationTest.php`

```php
<?php

namespace Tests\Unit\Products;

use Tests\TestCase;
use App\Domains\Products\Services\ProductMigrationService;
use App\Domains\Accounting\Sales\Models\Product as SalesProduct;
use App\Domains\Catalog\Products\Models\Product as CatalogProduct;
use App\Domains\Products\Models\Product;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ProductMigrationTest extends TestCase
{
    use RefreshDatabase;

    public function test_migrates_sales_product_successfully()
    {
        $salesProduct = SalesProduct::factory()->create();
        
        $service = new ProductMigrationService();
        $unifiedProduct = $service->migrateSalesProduct($salesProduct);

        $this->assertInstanceOf(Product::class, $unifiedProduct);
        $this->assertEquals($salesProduct->company_id, $unifiedProduct->company_id);
        $this->assertTrue($unifiedProduct->is_simple_product);
        $this->assertCount(1, $unifiedProduct->variants);
    }

    public function test_migrates_catalog_product_successfully()
    {
        $catalogProduct = CatalogProduct::factory()->create();
        
        $service = new ProductMigrationService();
        $unifiedProduct = $service->migrateCatalogProduct($catalogProduct);

        $this->assertInstanceOf(Product::class, $unifiedProduct);
        $this->assertFalse($unifiedProduct->is_simple_product);
    }

    public function test_does_not_duplicate_migration()
    {
        $salesProduct = SalesProduct::factory()->create();
        $service = new ProductMigrationService();

        $first = $service->migrateSalesProduct($salesProduct);
        $second = $service->migrateSalesProduct($salesProduct);

        $this->assertEquals($first->id, $second->id);
    }
}
```

#### Step 7.2: Integration Tests

**File:** `tests/Integration/Products/ProductApiTest.php`

```php
<?php

namespace Tests\Integration\Products;

use Tests\TestCase;
use App\Domains\Products\Models\Product;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ProductApiTest extends TestCase
{
    use RefreshDatabase;

    public function test_can_create_simple_product()
    {
        $response = $this->postJson('/api/v1/products', [
            'canonical_name' => ['en' => 'Test Product'],
            'code' => 'TEST-001',
            'is_simple_product' => true,
            'section' => 'accounting',
            'variants' => [[
                'sku' => 'TEST-001',
                'prices' => [[
                    'unit_id' => 1,
                    'selling_price' => 100,
                    'purchasing_price' => 50,
                ]],
            ]],
        ]);

        $response->assertStatus(201);
        $this->assertDatabaseHas('products', ['code' => 'TEST-001']);
    }

    public function test_can_list_products()
    {
        Product::factory()->count(10)->create();

        $response = $this->getJson('/api/v1/products');

        $response->assertStatus(200);
        $response->assertJsonStructure([
            'data' => [
                '*' => ['id', 'canonical_name', 'code', 'status'],
            ],
        ]);
    }
}
```

---

### Phase 8: Deployment (Week 9)

#### Step 8.1: Pre-Deployment Checklist

- [ ] All migrations tested on staging
- [ ] All unit tests passing
- [ ] All integration tests passing
- [ ] Performance tests completed
- [ ] Data validation scripts ready
- [ ] Rollback plan documented
- [ ] Feature flags configured
- [ ] Monitoring enabled
- [ ] Team briefed

#### Step 8.2: Deployment Steps

1. **Enable Feature Flag**
   ```php
   // config/features.php
   'dual_write_products' => true,
   ```

2. **Run Migrations**
   ```bash
   php artisan migrate
   ```

3. **Run Data Migration**
   ```bash
   php artisan products:migrate --type=all
   ```

4. **Validate Migration**
   ```bash
   php artisan products:migrate --validate
   ```

5. **Switch to Unified System**
   ```php
   // config/features.php
   'use_unified_products' => true,
   'dual_write_products' => false, // Disable after verification
   ```

---

### Phase 9: Cleanup (Week 10)

#### Step 9.1: Remove Old Tables (After 30-day verification)

```php
// Migration: drop_old_product_tables.php
Schema::dropIfExists('sales_products');
Schema::dropIfExists('catalog_products');
```

#### Step 9.2: Remove Old Models and Services

- Archive old models
- Remove old controllers
- Update documentation

---

## 10. Deployment Checklist

### Pre-Deployment
- [ ] Database backup created
- [ ] Staging environment tested
- [ ] All tests passing
- [ ] Migration scripts tested
- [ ] Rollback plan ready
- [ ] Team notified

### Deployment
- [ ] Run migrations
- [ ] Run data migration
- [ ] Validate data
- [ ] Enable feature flags
- [ ] Monitor system
- [ ] Verify API endpoints

### Post-Deployment
- [ ] Monitor for 24 hours
- [ ] Check error logs
- [ ] Verify data integrity
- [ ] Performance monitoring
- [ ] User feedback collection

---

## 11. Rollback Procedures

### If Migration Fails

1. **Stop Dual-Write**
   ```php
   // config/features.php
   'dual_write_products' => false,
   ```

2. **Restore from Backup**
   ```bash
   mysql -u user -p database < backup.sql
   ```

3. **Revert Code**
   ```bash
   git revert <commit-hash>
   ```

### If Performance Degrades

1. Analyze slow queries
2. Add missing indexes
3. Optimize queries
4. Consider caching

---

## Summary

This implementation plan provides a detailed, step-by-step guide for unifying Sales Products and Catalog Products. Follow each phase sequentially, test thoroughly, and maintain backward compatibility throughout the process.

**Key Success Factors:**
- Comprehensive testing at each phase
- Data validation after migration
- Feature flags for gradual rollout
- Monitoring and logging
- Clear rollback procedures

**Estimated Timeline:** 10 weeks  
**Risk Level:** Medium-High  
**Complexity:** High
