2025-11-21 10:49:41 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
use Illuminate\Database\Migrations\Migration;
|
|
|
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
|
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
|
|
|
|
|
|
return new class extends Migration
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Run the migrations.
|
|
|
|
|
*/
|
|
|
|
|
public function up(): void
|
|
|
|
|
{
|
|
|
|
|
// Products table
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.products', 'products'))) {
|
|
|
|
|
Schema::create(config('shop.tables.products', 'products'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
2025-11-22 08:55:58 +00:00
|
|
|
$table->string('name')->nullable();
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->string('slug')->unique();
|
|
|
|
|
$table->string('sku')->nullable()->unique();
|
2025-11-21 14:55:15 +00:00
|
|
|
$table->text('short_description')->nullable();
|
|
|
|
|
$table->longText('description')->nullable();
|
2025-12-03 12:59:01 +00:00
|
|
|
$table->string('type')->default('simple'); // simple, variable, grouped, external, booking
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->string('stripe_product_id')->nullable();
|
|
|
|
|
$table->timestamp('sale_start')->nullable();
|
|
|
|
|
$table->timestamp('sale_end')->nullable();
|
|
|
|
|
$table->boolean('manage_stock')->default(false);
|
|
|
|
|
$table->integer('stock_quantity')->default(0);
|
|
|
|
|
$table->integer('low_stock_threshold')->nullable();
|
|
|
|
|
$table->boolean('in_stock')->default(true);
|
|
|
|
|
$table->string('stock_status')->default('instock'); // instock, outofstock, onbackorder
|
|
|
|
|
$table->decimal('weight', 10, 2)->nullable();
|
|
|
|
|
$table->decimal('length', 10, 2)->nullable();
|
|
|
|
|
$table->decimal('width', 10, 2)->nullable();
|
|
|
|
|
$table->decimal('height', 10, 2)->nullable();
|
|
|
|
|
$table->boolean('virtual')->default(false);
|
|
|
|
|
$table->boolean('downloadable')->default(false);
|
|
|
|
|
$table->uuid('parent_id')->nullable();
|
|
|
|
|
$table->boolean('featured')->default(false);
|
2025-11-22 08:55:58 +00:00
|
|
|
$table->boolean('is_visible')->default(true);
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->string('status')->default('draft'); // draft, published, archived
|
|
|
|
|
$table->timestamp('published_at')->nullable();
|
|
|
|
|
$table->integer('sort_order')->default(0);
|
2025-11-21 14:13:52 +00:00
|
|
|
$table->json('meta')->nullable();
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->string('tax_class')->nullable();
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
$table->softDeletes();
|
|
|
|
|
|
|
|
|
|
$table->index(['slug', 'status']);
|
2025-11-22 08:55:58 +00:00
|
|
|
$table->index(['featured', 'is_visible', 'status']);
|
2025-12-18 11:21:29 +00:00
|
|
|
$table->index(['type', 'status', 'is_visible']);
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->index('parent_id');
|
|
|
|
|
$table->foreign('parent_id')->references('id')->on(config('shop.tables.products', 'products'))->onDelete('cascade');
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// Add new fields to existing products table
|
|
|
|
|
Schema::table(config('shop.tables.products', 'products'), function (Blueprint $table) {
|
2025-11-21 14:55:15 +00:00
|
|
|
if (!Schema::hasColumn(config('shop.tables.products', 'products'), 'name')) {
|
|
|
|
|
$table->string('name')->after('id');
|
|
|
|
|
}
|
|
|
|
|
if (!Schema::hasColumn(config('shop.tables.products', 'products'), 'short_description')) {
|
|
|
|
|
$table->text('short_description')->nullable()->after('sku');
|
|
|
|
|
}
|
|
|
|
|
if (!Schema::hasColumn(config('shop.tables.products', 'products'), 'description')) {
|
|
|
|
|
$table->longText('description')->nullable()->after('short_description');
|
|
|
|
|
}
|
2025-11-21 10:49:41 +00:00
|
|
|
if (!Schema::hasColumn(config('shop.tables.products', 'products'), 'low_stock_threshold')) {
|
|
|
|
|
$table->integer('low_stock_threshold')->nullable()->after('stock_quantity');
|
|
|
|
|
}
|
|
|
|
|
if (!Schema::hasColumn(config('shop.tables.products', 'products'), 'published_at')) {
|
|
|
|
|
$table->timestamp('published_at')->nullable()->after('status');
|
|
|
|
|
}
|
|
|
|
|
if (!Schema::hasColumn(config('shop.tables.products', 'products'), 'sort_order')) {
|
|
|
|
|
$table->integer('sort_order')->default(0)->after('published_at');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Product categories table
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.product_categories', 'product_categories'))) {
|
|
|
|
|
Schema::create(config('shop.tables.product_categories', 'product_categories'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
2025-11-22 17:09:45 +00:00
|
|
|
$table->string('name')->nullable();
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->string('slug')->unique();
|
2025-11-21 15:03:24 +00:00
|
|
|
$table->text('description')->nullable();
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->uuid('parent_id')->nullable();
|
|
|
|
|
$table->integer('sort_order')->default(0);
|
2025-11-22 08:55:58 +00:00
|
|
|
$table->boolean('is_visible')->default(true);
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->json('meta')->nullable();
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
$table->softDeletes();
|
|
|
|
|
|
2025-11-22 08:55:58 +00:00
|
|
|
$table->index(['parent_id', 'is_visible']);
|
|
|
|
|
$table->index(['slug', 'is_visible']);
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->foreign('parent_id')->references('id')->on(config('shop.tables.product_categories', 'product_categories'))->onDelete('cascade');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Product category pivot table
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.product_category_product', 'product_category_product'))) {
|
|
|
|
|
Schema::create(config('shop.tables.product_category_product', 'product_category_product'), function (Blueprint $table) {
|
2025-12-02 08:15:53 +00:00
|
|
|
$table->uuid('product_id');
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->uuid('product_category_id');
|
|
|
|
|
$table->integer('sort_order')->default(0);
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
|
|
|
|
|
$table->primary(['product_id', 'product_category_id'], 'product_category_product_primary');
|
|
|
|
|
$table->foreign('product_id')->references('id')->on(config('shop.tables.products', 'products'))->onDelete('cascade');
|
|
|
|
|
$table->foreign('product_category_id')->references('id')->on(config('shop.tables.product_categories', 'product_categories'))->onDelete('cascade');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Product prices table (multi-currency support)
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.product_prices', 'product_prices'))) {
|
|
|
|
|
Schema::create(config('shop.tables.product_prices', 'product_prices'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
2025-11-23 14:07:12 +00:00
|
|
|
$table->uuidMorphs('purchasable');
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->string('stripe_price_id')->nullable();
|
2025-11-21 15:30:50 +00:00
|
|
|
$table->string('name')->nullable();
|
|
|
|
|
$table->string('type')->default('one_time'); // one_time, recurring
|
2025-11-23 14:07:12 +00:00
|
|
|
$table->string('currency', 3)->default('EUR');
|
|
|
|
|
$table->integer('unit_amount')->default(0); // Store as smallest currency unit (cents)
|
|
|
|
|
$table->integer('sale_unit_amount')->nullable();
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->boolean('is_default')->default(false);
|
2025-11-21 15:30:50 +00:00
|
|
|
$table->boolean('active')->default(true);
|
|
|
|
|
$table->string('billing_scheme')->nullable(); // per_unit, tiered
|
|
|
|
|
$table->string('interval')->nullable(); // day, week, month, year
|
|
|
|
|
$table->integer('interval_count')->nullable();
|
|
|
|
|
$table->integer('trial_period_days')->nullable();
|
|
|
|
|
$table->json('meta')->nullable();
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->timestamps();
|
|
|
|
|
|
2025-11-23 14:07:12 +00:00
|
|
|
$table->index('currency');
|
2025-12-18 11:39:59 +00:00
|
|
|
$table->index(['purchasable_type', 'purchasable_id', 'is_default', 'active'], 'pp_purchasable_default_active_idx');
|
2025-11-21 10:49:41 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Product attributes table
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.product_attributes', 'product_attributes'))) {
|
|
|
|
|
Schema::create(config('shop.tables.product_attributes', 'product_attributes'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
|
|
|
|
$table->uuid('product_id');
|
|
|
|
|
$table->string('key');
|
|
|
|
|
$table->text('value')->nullable();
|
|
|
|
|
$table->string('type')->default('text'); // text, select, color, image
|
|
|
|
|
$table->integer('sort_order')->default(0);
|
|
|
|
|
$table->json('meta')->nullable();
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
|
|
|
|
|
$table->index(['product_id', 'key']);
|
|
|
|
|
$table->foreign('product_id')->references('id')->on(config('shop.tables.products', 'products'))->onDelete('cascade');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-04 10:06:09 +00:00
|
|
|
// Product stocks table (claims)
|
2025-11-21 10:49:41 +00:00
|
|
|
if (!Schema::hasTable(config('shop.tables.product_stocks', 'product_stocks'))) {
|
|
|
|
|
Schema::create(config('shop.tables.product_stocks', 'product_stocks'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
|
|
|
|
$table->uuid('product_id');
|
|
|
|
|
$table->integer('quantity');
|
2025-12-04 10:06:09 +00:00
|
|
|
$table->string('type')->default('claimed'); // claimed, adjustment, sale, return
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->string('status')->default('pending'); // pending, completed, cancelled, expired
|
|
|
|
|
$table->string('reference_type')->nullable();
|
|
|
|
|
$table->string('reference_id')->nullable();
|
2025-12-04 10:06:09 +00:00
|
|
|
$table->timestamp('claimed_from')->nullable();
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->timestamp('expires_at')->nullable();
|
|
|
|
|
$table->text('note')->nullable();
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
|
|
|
|
|
$table->index(['product_id', 'status']);
|
|
|
|
|
$table->index(['reference_type', 'reference_id']);
|
2025-12-04 10:06:09 +00:00
|
|
|
$table->index(['claimed_from', 'expires_at']);
|
2025-12-18 11:55:11 +00:00
|
|
|
$table->index(['product_id', 'type', 'status', 'claimed_from', 'expires_at'], 'ps_product_type_status_dates_idx');
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->foreign('product_id')->references('id')->on(config('shop.tables.products', 'products'))->onDelete('cascade');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Product stock logs table
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.product_stock_logs', 'product_stock_logs'))) {
|
|
|
|
|
Schema::create(config('shop.tables.product_stock_logs', 'product_stock_logs'), function (Blueprint $table) {
|
2025-11-22 14:13:30 +00:00
|
|
|
$table->id();
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->uuid('product_id');
|
|
|
|
|
$table->integer('quantity_change');
|
|
|
|
|
$table->integer('quantity_after');
|
|
|
|
|
$table->string('type'); // increase, decrease, adjustment
|
|
|
|
|
$table->string('reference_type')->nullable();
|
|
|
|
|
$table->string('reference_id')->nullable();
|
|
|
|
|
$table->text('note')->nullable();
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
|
|
|
|
|
$table->index(['product_id', 'created_at']);
|
|
|
|
|
$table->foreign('product_id')->references('id')->on(config('shop.tables.products', 'products'))->onDelete('cascade');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Product relations table (related, upsell, cross-sell)
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.product_relations', 'product_relations'))) {
|
|
|
|
|
Schema::create(config('shop.tables.product_relations', 'product_relations'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('product_id');
|
|
|
|
|
$table->uuid('related_product_id');
|
|
|
|
|
$table->string('type')->default('related'); // related, upsell, cross-sell
|
|
|
|
|
$table->integer('sort_order')->default(0);
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
|
|
|
|
|
$table->primary(['product_id', 'related_product_id', 'type'], 'product_relations_primary');
|
|
|
|
|
$table->foreign('product_id')->references('id')->on(config('shop.tables.products', 'products'))->onDelete('cascade');
|
|
|
|
|
$table->foreign('related_product_id')->references('id')->on(config('shop.tables.products', 'products'))->onDelete('cascade');
|
2025-11-22 17:09:45 +00:00
|
|
|
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->index(['product_id', 'type']);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Product actions table
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.product_actions', 'product_actions'))) {
|
|
|
|
|
Schema::create(config('shop.tables.product_actions', 'product_actions'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
2025-12-02 08:15:53 +00:00
|
|
|
$table->uuid('product_id')->nullable();
|
|
|
|
|
$table->json('events')->nullable(); // e.g. ["purchased","paid","refunded","viewed"]
|
2025-11-29 19:09:19 +00:00
|
|
|
$table->string('class'); // e.g. \App\...
|
|
|
|
|
$table->string('method')->nullable(); // null means __invoke via constructor params
|
|
|
|
|
$table->boolean('defer')->default(true); // queued when true, sync otherwise
|
2025-11-22 17:09:45 +00:00
|
|
|
$table->json('parameters')->nullable();
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->boolean('active')->default(true);
|
|
|
|
|
$table->integer('sort_order')->default(0);
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
|
2025-11-29 19:09:19 +00:00
|
|
|
$table->index(['product_id', 'active']);
|
|
|
|
|
$table->foreign('product_id')
|
|
|
|
|
->references('id')
|
|
|
|
|
->on(config('shop.tables.products', 'products'))
|
|
|
|
|
->nullOnDelete();
|
2025-11-21 10:49:41 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Product purchases table
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.product_purchases', 'product_purchases'))) {
|
|
|
|
|
Schema::create(config('shop.tables.product_purchases', 'product_purchases'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
|
|
|
|
$table->string('status')->default('pending');
|
2025-11-22 17:09:45 +00:00
|
|
|
$table->foreignUuid('cart_id')->nullable();
|
|
|
|
|
$table->foreignUuid('price_id')->nullable();
|
|
|
|
|
$table->nullableUuidMorphs('purchasable');
|
|
|
|
|
$table->nullableUuidMorphs('purchaser');
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->integer('quantity')->default(1);
|
2025-12-19 12:32:00 +00:00
|
|
|
$table->unsignedBigInteger('amount')->nullable(); // Stored in cents
|
|
|
|
|
$table->unsignedBigInteger('amount_paid')->default(0); // Stored in cents
|
2025-11-22 17:09:45 +00:00
|
|
|
$table->string('charge_id')->nullable();
|
2025-12-03 12:59:01 +00:00
|
|
|
$table->timestamp('from')->nullable();
|
|
|
|
|
$table->timestamp('until')->nullable();
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->json('meta')->nullable();
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Carts table
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.carts', 'carts'))) {
|
|
|
|
|
Schema::create(config('shop.tables.carts', 'carts'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
|
|
|
|
$table->string('session_id')->nullable()->unique();
|
2025-12-17 07:29:20 +00:00
|
|
|
$table->nullableUuidMorphs('customer');
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->string('currency', 3)->default('USD');
|
|
|
|
|
$table->string('status')->default('active'); // active, abandoned, converted, expired
|
|
|
|
|
$table->timestamp('last_activity_at')->nullable();
|
|
|
|
|
$table->timestamp('expires_at')->nullable();
|
|
|
|
|
$table->timestamp('converted_at')->nullable();
|
2025-12-19 08:53:44 +00:00
|
|
|
$table->timestamp('from')->nullable(); // Default start date for booking items
|
|
|
|
|
$table->timestamp('until')->nullable(); // Default end date for booking items
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->json('meta')->nullable();
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
$table->softDeletes();
|
|
|
|
|
|
|
|
|
|
$table->index(['session_id', 'status']);
|
|
|
|
|
$table->index(['customer_type', 'customer_id', 'status']);
|
2025-12-18 11:21:29 +00:00
|
|
|
$table->index(['status', 'last_activity_at']);
|
2025-11-21 10:49:41 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cart items table
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.cart_items', 'cart_items'))) {
|
|
|
|
|
Schema::create(config('shop.tables.cart_items', 'cart_items'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
|
|
|
|
$table->uuid('cart_id');
|
2025-11-23 14:07:12 +00:00
|
|
|
$table->uuidMorphs('purchasable');
|
2025-12-30 08:29:43 +00:00
|
|
|
$table->foreignUuid('product_id')->nullable()->constrained(config('shop.tables.products', 'products'))->nullOnDelete();
|
2025-11-28 09:24:07 +00:00
|
|
|
$table->foreignUuid('purchase_id')->nullable()->constrained(config('shop.tables.product_purchases', 'product_purchases'))->nullOnDelete();
|
2025-12-17 11:26:26 +00:00
|
|
|
$table->foreignUuid('price_id')->nullable()->constrained(config('shop.tables.product_prices', 'product_prices'))->nullOnDelete();
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->integer('quantity')->default(1);
|
2026-01-05 08:07:09 +00:00
|
|
|
$table->string('currency', 3)->nullable(); // Currency from the selected price
|
2025-12-20 11:19:34 +00:00
|
|
|
$table->integer('price')->nullable(); // Stored in cents, null = unavailable
|
2025-12-18 11:21:29 +00:00
|
|
|
$table->integer('regular_price')->nullable(); // Stored in cents
|
|
|
|
|
$table->integer('unit_amount')->nullable(); // Base unit price for 1 quantity, 1 day (in cents)
|
2025-12-20 11:19:34 +00:00
|
|
|
$table->integer('subtotal')->nullable(); // Stored in cents, null = unavailable
|
2025-11-23 14:07:12 +00:00
|
|
|
$table->json('parameters')->nullable();
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->json('meta')->nullable();
|
2025-12-15 10:32:31 +00:00
|
|
|
$table->timestamp('from')->nullable();
|
|
|
|
|
$table->timestamp('until')->nullable();
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->timestamps();
|
|
|
|
|
|
2025-12-02 08:15:53 +00:00
|
|
|
$table->index(['cart_id', 'purchasable_id']);
|
2025-12-18 11:55:11 +00:00
|
|
|
$table->index(['cart_id', 'purchasable_type', 'purchasable_id'], 'ci_cart_purchasable_idx');
|
2025-12-18 11:21:29 +00:00
|
|
|
$table->index(['from', 'until']);
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->foreign('cart_id')->references('id')->on(config('shop.tables.carts', 'carts'))->onDelete('cascade');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cart discounts table
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.cart_discounts', 'cart_discounts'))) {
|
|
|
|
|
Schema::create(config('shop.tables.cart_discounts', 'cart_discounts'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
|
|
|
|
$table->uuid('cart_id');
|
|
|
|
|
$table->string('code')->nullable();
|
|
|
|
|
$table->string('type')->default('percentage'); // percentage, fixed, shipping
|
2025-12-19 12:32:00 +00:00
|
|
|
$table->unsignedBigInteger('amount'); // Stored in cents
|
|
|
|
|
$table->unsignedBigInteger('discount_amount'); // Stored in cents
|
2025-11-21 10:49:41 +00:00
|
|
|
$table->json('meta')->nullable();
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
|
|
|
|
|
$table->index('cart_id');
|
|
|
|
|
$table->foreign('cart_id')->references('id')->on(config('shop.tables.carts', 'carts'))->onDelete('cascade');
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-11-26 10:09:52 +00:00
|
|
|
|
|
|
|
|
// Payment provider identities table
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.payment_provider_identities', 'payment_provider_identities'))) {
|
|
|
|
|
Schema::create(config('shop.tables.payment_provider_identities', 'payment_provider_identities'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
2025-12-17 07:29:20 +00:00
|
|
|
$table->nullableUuidMorphs('customer');
|
2025-11-26 10:09:52 +00:00
|
|
|
$table->string('provider_name'); // stripe, paypal, etc.
|
|
|
|
|
$table->string('customer_identification_id'); // The provider's customer ID
|
|
|
|
|
$table->json('meta')->nullable();
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
|
2025-12-18 11:55:11 +00:00
|
|
|
$table->index(['customer_type', 'customer_id', 'provider_name'], 'ppi_cust_provider_idx');
|
|
|
|
|
$table->unique(['customer_type', 'customer_id', 'provider_name'], 'ppi_cust_provider_unq');
|
2025-11-26 10:09:52 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Payment methods table
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.payment_methods', 'payment_methods'))) {
|
|
|
|
|
Schema::create(config('shop.tables.payment_methods', 'payment_methods'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
|
|
|
|
$table->uuid('payment_provider_identity_id');
|
|
|
|
|
$table->string('provider_payment_method_id'); // The provider's payment method ID
|
|
|
|
|
$table->string('type')->nullable(); // card, bank_account, etc.
|
|
|
|
|
$table->string('name')->nullable(); // Custom name given by user
|
|
|
|
|
$table->string('last_digits')->nullable(); // Last 4 digits of card/account
|
2025-11-29 11:05:02 +00:00
|
|
|
$table->string('last_alphanumeric')->nullable(); // Last characters for non-numeric identifiers (e.g., crypto/wallet)
|
2025-11-26 10:09:52 +00:00
|
|
|
$table->string('brand')->nullable(); // visa, mastercard, etc.
|
|
|
|
|
$table->integer('exp_month')->nullable();
|
|
|
|
|
$table->integer('exp_year')->nullable();
|
2025-11-29 11:05:02 +00:00
|
|
|
$table->timestamp('expires_at')->nullable(); // General expiration timestamp for non-card methods
|
2025-11-26 10:09:52 +00:00
|
|
|
$table->boolean('is_default')->default(false);
|
|
|
|
|
$table->boolean('is_active')->default(true);
|
|
|
|
|
$table->json('meta')->nullable();
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
|
2025-12-18 11:55:11 +00:00
|
|
|
$table->index(['payment_provider_identity_id', 'is_active'], 'pm_provider_identity_active_idx');
|
|
|
|
|
$table->foreign('payment_provider_identity_id', 'pm_provider_identity_fk')
|
2025-11-26 10:09:52 +00:00
|
|
|
->references('id')
|
|
|
|
|
->on(config('shop.tables.payment_provider_identities', 'payment_provider_identities'))
|
|
|
|
|
->onDelete('cascade');
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-11-29 19:09:19 +00:00
|
|
|
|
|
|
|
|
// ProductActionRuns table
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.product_action_runs', 'product_action_runs'))) {
|
|
|
|
|
Schema::create(config('shop.tables.product_action_runs', 'product_action_runs'), function (Blueprint $table) {
|
|
|
|
|
$table->id();
|
|
|
|
|
$table->morphs('action');
|
2025-12-02 08:15:53 +00:00
|
|
|
$table->uuid('product_purchase_id')->nullable();
|
2025-11-29 19:09:19 +00:00
|
|
|
$table->boolean('success')->default(false);
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
$table->foreign('product_purchase_id')->references('id')->on(config('shop.tables.product_purchases', 'product_purchases'))->onDelete('set null');
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-12-29 08:59:02 +00:00
|
|
|
|
|
|
|
|
// Orders table - represents converted/purchased carts
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.orders', 'orders'))) {
|
|
|
|
|
Schema::create(config('shop.tables.orders', 'orders'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
|
|
|
|
$table->string('order_number')->unique();
|
|
|
|
|
$table->uuid('cart_id')->nullable();
|
|
|
|
|
$table->nullableUuidMorphs('customer');
|
|
|
|
|
$table->string('status')->default('pending');
|
|
|
|
|
$table->string('currency', 3)->default('USD');
|
|
|
|
|
|
|
|
|
|
// Financial fields (all stored in smallest currency unit - cents)
|
|
|
|
|
$table->unsignedBigInteger('amount_subtotal')->default(0);
|
|
|
|
|
$table->unsignedBigInteger('amount_discount')->default(0);
|
|
|
|
|
$table->unsignedBigInteger('amount_shipping')->default(0);
|
|
|
|
|
$table->unsignedBigInteger('amount_tax')->default(0);
|
|
|
|
|
$table->unsignedBigInteger('amount_total')->default(0);
|
|
|
|
|
$table->unsignedBigInteger('amount_paid')->default(0);
|
|
|
|
|
$table->unsignedBigInteger('amount_refunded')->default(0);
|
|
|
|
|
|
|
|
|
|
// Payment information
|
|
|
|
|
$table->string('payment_method')->nullable();
|
|
|
|
|
$table->string('payment_provider')->nullable();
|
|
|
|
|
$table->string('payment_reference')->nullable();
|
|
|
|
|
|
|
|
|
|
// Addresses (stored as JSON)
|
|
|
|
|
$table->json('billing_address')->nullable();
|
|
|
|
|
$table->json('shipping_address')->nullable();
|
|
|
|
|
|
|
|
|
|
// Notes
|
|
|
|
|
$table->text('customer_note')->nullable();
|
|
|
|
|
$table->text('internal_note')->nullable();
|
|
|
|
|
|
|
|
|
|
// Tracking metadata
|
|
|
|
|
$table->string('ip_address')->nullable();
|
|
|
|
|
$table->text('user_agent')->nullable();
|
|
|
|
|
|
|
|
|
|
// Important timestamps
|
|
|
|
|
$table->timestamp('completed_at')->nullable();
|
|
|
|
|
$table->timestamp('paid_at')->nullable();
|
|
|
|
|
$table->timestamp('shipped_at')->nullable();
|
|
|
|
|
$table->timestamp('delivered_at')->nullable();
|
|
|
|
|
$table->timestamp('cancelled_at')->nullable();
|
|
|
|
|
$table->timestamp('refunded_at')->nullable();
|
|
|
|
|
|
|
|
|
|
// Extensible metadata
|
|
|
|
|
$table->json('meta')->nullable();
|
|
|
|
|
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
$table->softDeletes();
|
|
|
|
|
|
|
|
|
|
// Indexes
|
|
|
|
|
$table->index(['customer_type', 'customer_id', 'status'], 'orders_customer_status_idx');
|
|
|
|
|
$table->index(['status', 'created_at']);
|
|
|
|
|
$table->index(['order_number']);
|
|
|
|
|
$table->index('cart_id');
|
|
|
|
|
|
|
|
|
|
// Foreign key to carts
|
|
|
|
|
$table->foreign('cart_id')
|
|
|
|
|
->references('id')
|
|
|
|
|
->on(config('shop.tables.carts', 'carts'))
|
|
|
|
|
->nullOnDelete();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Order notes table - activity log for orders (like WooCommerce order notes)
|
|
|
|
|
if (!Schema::hasTable(config('shop.tables.order_notes', 'order_notes'))) {
|
|
|
|
|
Schema::create(config('shop.tables.order_notes', 'order_notes'), function (Blueprint $table) {
|
|
|
|
|
$table->uuid('id')->primary();
|
|
|
|
|
$table->uuid('order_id');
|
|
|
|
|
$table->nullableUuidMorphs('author'); // Who created the note
|
|
|
|
|
$table->text('content');
|
|
|
|
|
$table->string('type')->default('note'); // note, status_change, payment, refund, shipping, customer, system
|
|
|
|
|
$table->boolean('is_customer_note')->default(false); // Whether visible to customer
|
|
|
|
|
$table->json('meta')->nullable();
|
|
|
|
|
$table->timestamps();
|
|
|
|
|
|
|
|
|
|
// Indexes
|
|
|
|
|
$table->index(['order_id', 'type']);
|
|
|
|
|
$table->index(['order_id', 'is_customer_note']);
|
|
|
|
|
$table->index(['order_id', 'created_at']);
|
|
|
|
|
|
|
|
|
|
// Foreign key to orders
|
|
|
|
|
$table->foreign('order_id')
|
|
|
|
|
->references('id')
|
|
|
|
|
->on(config('shop.tables.orders', 'orders'))
|
|
|
|
|
->onDelete('cascade');
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-11-21 10:49:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Reverse the migrations.
|
|
|
|
|
*/
|
|
|
|
|
public function down(): void
|
|
|
|
|
{
|
2025-12-29 08:59:02 +00:00
|
|
|
Schema::dropIfExists(config('shop.tables.order_notes', 'order_notes'));
|
|
|
|
|
Schema::dropIfExists(config('shop.tables.orders', 'orders'));
|
2025-11-26 10:09:52 +00:00
|
|
|
Schema::dropIfExists(config('shop.tables.payment_methods', 'payment_methods'));
|
|
|
|
|
Schema::dropIfExists(config('shop.tables.payment_provider_identities', 'payment_provider_identities'));
|
2025-11-21 10:49:41 +00:00
|
|
|
Schema::dropIfExists(config('shop.tables.cart_discounts', 'cart_discounts'));
|
|
|
|
|
Schema::dropIfExists(config('shop.tables.cart_items', 'cart_items'));
|
|
|
|
|
Schema::dropIfExists(config('shop.tables.carts', 'carts'));
|
2025-11-29 19:09:19 +00:00
|
|
|
Schema::dropIfExists(config('shop.tables.product_action_runs', 'product_action_runs'));
|
2025-11-21 10:49:41 +00:00
|
|
|
Schema::dropIfExists(config('shop.tables.product_purchases', 'product_purchases'));
|
|
|
|
|
Schema::dropIfExists(config('shop.tables.product_actions', 'product_actions'));
|
|
|
|
|
Schema::dropIfExists(config('shop.tables.product_category_product', 'product_category_product'));
|
|
|
|
|
Schema::dropIfExists(config('shop.tables.product_relations', 'product_relations'));
|
|
|
|
|
Schema::dropIfExists(config('shop.tables.product_stock_logs', 'product_stock_logs'));
|
|
|
|
|
Schema::dropIfExists(config('shop.tables.product_stocks', 'product_stocks'));
|
|
|
|
|
Schema::dropIfExists(config('shop.tables.product_attributes', 'product_attributes'));
|
|
|
|
|
Schema::dropIfExists(config('shop.tables.product_prices', 'product_prices'));
|
2025-11-29 19:09:19 +00:00
|
|
|
Schema::dropIfExists(config('shop.tables.product_category_product', 'product_category_product'));
|
2025-11-21 10:49:41 +00:00
|
|
|
Schema::dropIfExists(config('shop.tables.product_categories', 'product_categories'));
|
|
|
|
|
Schema::dropIfExists(config('shop.tables.products', 'products'));
|
|
|
|
|
}
|
|
|
|
|
};
|