feat: 實作 Multi-tenancy 多租戶架構 (stancl/tenancy)
- 安裝並設定 stancl/tenancy 套件 - 分離 Central / Tenant migrations - 建立 Tenant Model 與資料遷移指令 - 建立房東後台 CRUD (Landlord Dashboard) - 新增租戶管理頁面 (列表、新增、編輯、詳情) - 新增域名管理功能 - 更新部署手冊
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
<?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
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('sessions', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->foreignId('user_id')->nullable()->index();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->longText('payload');
|
||||
$table->integer('last_activity')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
Schema::dropIfExists('sessions');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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
|
||||
{
|
||||
Schema::create('cache', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->mediumText('value');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
|
||||
Schema::create('cache_locks', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->string('owner');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cache');
|
||||
Schema::dropIfExists('cache_locks');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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
|
||||
{
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('queue')->index();
|
||||
$table->longText('payload');
|
||||
$table->unsignedTinyInteger('attempts');
|
||||
$table->unsignedInteger('reserved_at')->nullable();
|
||||
$table->unsignedInteger('available_at');
|
||||
$table->unsignedInteger('created_at');
|
||||
});
|
||||
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
$table->integer('failed_jobs');
|
||||
$table->longText('failed_job_ids');
|
||||
$table->mediumText('options')->nullable();
|
||||
$table->integer('cancelled_at')->nullable();
|
||||
$table->integer('created_at');
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('jobs');
|
||||
Schema::dropIfExists('job_batches');
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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
|
||||
{
|
||||
Schema::create('categories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->unique();
|
||||
$table->string('description')->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('categories');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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
|
||||
{
|
||||
Schema::create('products', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('code')->unique()->comment('商品編號');
|
||||
$table->string('name')->comment('商品名稱');
|
||||
$table->foreignId('category_id')->constrained('categories')->onDelete('restrict')->comment('商品分類');
|
||||
$table->string('brand')->nullable()->comment('品牌');
|
||||
$table->text('specification')->nullable()->comment('規格描述');
|
||||
|
||||
// Unit conversion
|
||||
$table->string('base_unit')->comment('基本庫存單位 (e.g. g, ml)');
|
||||
$table->string('large_unit')->nullable()->comment('大單位 (e.g. 桶, 箱)');
|
||||
$table->decimal('conversion_rate', 10, 4)->default(1)->comment('換算率 (1 大單位 = ? 基本單位)');
|
||||
$table->string('purchase_unit')->nullable()->comment('採購單位');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('products');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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
|
||||
{
|
||||
Schema::create('vendors', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('code')->unique()->comment('廠商編號');
|
||||
$table->string('name')->comment('廠商名稱');
|
||||
$table->string('short_name')->nullable()->comment('廠商簡稱');
|
||||
$table->string('tax_id')->nullable()->comment('統一編號');
|
||||
$table->string('owner')->nullable()->comment('負責人');
|
||||
$table->string('contact_name')->nullable()->comment('聯絡人');
|
||||
$table->string('tel')->nullable()->comment('市話');
|
||||
$table->string('phone')->nullable()->comment('連絡電話');
|
||||
$table->string('email')->nullable()->comment('電子郵件');
|
||||
$table->text('address')->nullable()->comment('地址');
|
||||
$table->text('remark')->nullable()->comment('備註');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('vendors');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('vendors', function (Blueprint $table) {
|
||||
$table->string('code', 50)->change();
|
||||
$table->string('name', 100)->change();
|
||||
$table->string('short_name', 50)->nullable()->change();
|
||||
$table->string('tax_id', 20)->nullable()->change();
|
||||
$table->string('owner', 50)->nullable()->change();
|
||||
$table->string('contact_name', 50)->nullable()->change();
|
||||
$table->string('tel', 20)->nullable()->change();
|
||||
$table->string('phone', 20)->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->string('code', 50)->change();
|
||||
$table->string('name', 100)->change();
|
||||
$table->string('brand', 50)->nullable()->change();
|
||||
$table->string('base_unit', 20)->change();
|
||||
$table->string('large_unit', 20)->nullable()->change();
|
||||
$table->string('purchase_unit', 20)->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('categories', function (Blueprint $table) {
|
||||
$table->string('name', 50)->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('vendors', function (Blueprint $table) {
|
||||
$table->string('code', 255)->change();
|
||||
$table->string('name', 255)->change();
|
||||
$table->string('short_name', 255)->nullable()->change();
|
||||
$table->string('tax_id', 255)->nullable()->change();
|
||||
$table->string('owner', 255)->nullable()->change();
|
||||
$table->string('contact_name', 255)->nullable()->change();
|
||||
$table->string('tel', 255)->nullable()->change();
|
||||
$table->string('phone', 255)->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->string('code', 255)->change();
|
||||
$table->string('name', 255)->change();
|
||||
$table->string('brand', 255)->nullable()->change();
|
||||
$table->string('base_unit', 255)->change();
|
||||
$table->string('large_unit', 255)->nullable()->change();
|
||||
$table->string('purchase_unit', 255)->nullable()->change();
|
||||
});
|
||||
|
||||
Schema::table('categories', function (Blueprint $table) {
|
||||
$table->string('name', 255)->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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
|
||||
{
|
||||
// 倉庫基本資料
|
||||
Schema::create('warehouses', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('code', 50)->unique()->comment('倉庫編號');
|
||||
$table->string('name', 50)->comment('倉庫名稱');
|
||||
$table->string('address')->nullable()->comment('地址');
|
||||
$table->text('description')->nullable()->comment('描述');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// 商品庫存詳情
|
||||
Schema::create('inventories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('warehouse_id')->constrained()->onDelete('cascade');
|
||||
$table->foreignId('product_id')->constrained()->onDelete('restrict');
|
||||
$table->decimal('quantity', 10, 2)->default(0)->comment('當前庫存數量');
|
||||
$table->decimal('safety_stock', 10, 2)->default(0)->nullable()->comment('安全存量');
|
||||
$table->string('location', 50)->nullable()->comment('儲位/貨架號');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['warehouse_id', 'product_id'], 'warehouse_product_unique');
|
||||
});
|
||||
|
||||
// 庫存異動紀錄
|
||||
Schema::create('inventory_transactions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('inventory_id')->constrained()->onDelete('cascade');
|
||||
$table->string('type', 20)->comment('異動類型: purchase_in, sales_out, adjustment, transfer_in, transfer_out');
|
||||
$table->decimal('quantity', 10, 2)->comment('異動數量 (+/-)');
|
||||
$table->decimal('balance_before', 10, 2)->comment('異動前餘額');
|
||||
$table->decimal('balance_after', 10, 2)->comment('異動後餘額');
|
||||
$table->string('reason')->nullable()->comment('異動原因/備註');
|
||||
$table->nullableMorphs('reference'); // reference_type, reference_id
|
||||
$table->foreignId('user_id')->nullable()->constrained()->onDelete('set null')->comment('操作人員');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('inventory_transactions');
|
||||
Schema::dropIfExists('inventories');
|
||||
Schema::dropIfExists('warehouses');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('inventory_transactions', function (Blueprint $table) {
|
||||
$table->string('type', 20)->comment('異動類型: 採購進貨, 銷售出庫, 盤點調整, 撥補入庫, 撥補出庫, 手動入庫')->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('inventory_transactions', function (Blueprint $table) {
|
||||
$table->string('type', 20)->comment('異動類型: purchase_in, sales_out, adjustment, transfer_in, transfer_out')->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('inventory_transactions', function (Blueprint $table) {
|
||||
$table->dateTime('actual_time')->nullable()->after('reason')->comment('實際異動發生時間');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('inventory_transactions', function (Blueprint $table) {
|
||||
$table->dropColumn('actual_time');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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
|
||||
{
|
||||
Schema::create('product_vendor', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('vendor_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('product_id')->constrained()->cascadeOnDelete();
|
||||
$table->decimal('last_price', 10, 2)->nullable()->comment('最近一次進價');
|
||||
$table->timestamps();
|
||||
|
||||
// 確保同一個廠商對同一個商品只有一筆記錄
|
||||
$table->unique(['vendor_id', 'product_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('product_vendor');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('inventories', function (Blueprint $table) {
|
||||
// Change default value of safety_stock to NULL
|
||||
$table->decimal('safety_stock', 10, 2)->nullable()->default(null)->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('inventories', function (Blueprint $table) {
|
||||
// Revert default value to 0
|
||||
$table->decimal('safety_stock', 10, 2)->nullable()->default(0)->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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
|
||||
{
|
||||
Schema::create('purchase_orders', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('code')->unique()->comment('採購單號');
|
||||
$table->foreignId('vendor_id')->constrained()->onDelete('restrict');
|
||||
$table->foreignId('warehouse_id')->nullable()->constrained()->onDelete('set null');
|
||||
$table->foreignId('user_id')->constrained()->onDelete('restrict');
|
||||
$table->string('status')->default('draft')->comment('草稿, 待審核, 處理中, 運送中, 待確認, 已完成');
|
||||
$table->date('expected_delivery_date')->nullable();
|
||||
$table->decimal('total_amount', 12, 2)->default(0);
|
||||
$table->decimal('tax_amount', 12, 2)->default(0);
|
||||
$table->decimal('grand_total', 12, 2)->default(0);
|
||||
$table->text('remark')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('purchase_orders');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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
|
||||
{
|
||||
Schema::create('purchase_order_items', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('purchase_order_id')->constrained()->onDelete('cascade');
|
||||
$table->foreignId('product_id')->constrained()->onDelete('restrict');
|
||||
$table->decimal('quantity', 10, 2);
|
||||
$table->decimal('unit_price', 10, 2);
|
||||
$table->decimal('subtotal', 12, 2);
|
||||
$table->decimal('received_quantity', 10, 2)->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('purchase_order_items');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->decimal('conversion_rate', 10, 4)->nullable()->default(null)->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->decimal('conversion_rate', 10, 4)->nullable(false)->default(1)->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('username')->unique()->after('name');
|
||||
$table->string('email')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('username');
|
||||
$table->string('email')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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
|
||||
{
|
||||
Schema::create('units', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->unique()->comment('單位名稱');
|
||||
$table->string('code')->nullable()->comment('單位代碼 (如: kg)');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('units');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
// Drop old string columns
|
||||
$table->dropColumn(['base_unit', 'large_unit', 'purchase_unit']);
|
||||
|
||||
// Add new foreign key columns
|
||||
$table->foreignId('base_unit_id')->nullable()->after('specification')->constrained('units')->nullOnDelete()->comment('基本庫存單位ID');
|
||||
$table->foreignId('large_unit_id')->nullable()->after('base_unit_id')->constrained('units')->nullOnDelete()->comment('大單位ID');
|
||||
$table->foreignId('purchase_unit_id')->nullable()->after('conversion_rate')->constrained('units')->nullOnDelete()->comment('採購單位ID');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
// Remove foreign keys
|
||||
$table->dropForeign(['base_unit_id']);
|
||||
$table->dropForeign(['large_unit_id']);
|
||||
$table->dropForeign(['purchase_unit_id']);
|
||||
$table->dropColumn(['base_unit_id', 'large_unit_id', 'purchase_unit_id']);
|
||||
|
||||
// Add back string columns (nullable since data is lost)
|
||||
$table->string('base_unit')->nullable()->comment('基本庫存單位 (e.g. g, ml)');
|
||||
$table->string('large_unit')->nullable()->comment('大單位 (e.g. 桶, 箱)');
|
||||
$table->string('purchase_unit')->nullable()->comment('採購單位');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('purchase_order_items', function (Blueprint $table) {
|
||||
$table->string('unit')->nullable()->after('quantity')->comment('採購單位');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('purchase_order_items', function (Blueprint $table) {
|
||||
$table->dropColumn('unit');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('purchase_order_items', function (Blueprint $table) {
|
||||
$table->foreignId('unit_id')->nullable()->after('quantity')->comment('選擇的單位ID')->constrained('units')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('purchase_order_items', function (Blueprint $table) {
|
||||
$table->dropForeign(['unit_id']);
|
||||
$table->dropColumn('unit_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('purchase_orders', function (Blueprint $table) {
|
||||
$table->string('invoice_number', 11)->nullable()->comment('發票號碼 (格式: AB-12345678)');
|
||||
$table->date('invoice_date')->nullable()->comment('發票日期');
|
||||
$table->decimal('invoice_amount', 12, 2)->nullable()->comment('發票金額');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('purchase_orders', function (Blueprint $table) {
|
||||
$table->dropColumn(['invoice_number', 'invoice_date', 'invoice_amount']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
<?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
|
||||
{
|
||||
$teams = config('permission.teams');
|
||||
$tableNames = config('permission.table_names');
|
||||
$columnNames = config('permission.column_names');
|
||||
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
|
||||
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
|
||||
|
||||
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), Exception::class, 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
|
||||
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
||||
// $table->engine('InnoDB');
|
||||
$table->bigIncrements('id'); // permission id
|
||||
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['name', 'guard_name']);
|
||||
});
|
||||
|
||||
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
|
||||
// $table->engine('InnoDB');
|
||||
$table->bigIncrements('id'); // role id
|
||||
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
|
||||
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
||||
}
|
||||
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||
$table->timestamps();
|
||||
if ($teams || config('permission.testing')) {
|
||||
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
||||
} else {
|
||||
$table->unique(['name', 'guard_name']);
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->onDelete('cascade');
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->onDelete('cascade');
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||
});
|
||||
|
||||
app('cache')
|
||||
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||
->forget(config('permission.cache.key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$tableNames = config('permission.table_names');
|
||||
|
||||
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
|
||||
|
||||
Schema::drop($tableNames['role_has_permissions']);
|
||||
Schema::drop($tableNames['model_has_roles']);
|
||||
Schema::drop($tableNames['model_has_permissions']);
|
||||
Schema::drop($tableNames['roles']);
|
||||
Schema::drop($tableNames['permissions']);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->string('display_name')->nullable()->after('name');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->dropColumn('display_name');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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
|
||||
{
|
||||
\Spatie\Permission\Models\Permission::create(['name' => 'inventory.delete', 'guard_name' => 'web']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
\Spatie\Permission\Models\Permission::where('name', 'inventory.delete')->delete();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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
|
||||
{
|
||||
\Spatie\Permission\Models\Permission::create(['name' => 'inventory.safety_stock', 'guard_name' => 'web']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
\Spatie\Permission\Models\Permission::where('name', 'inventory.safety_stock')->delete();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
* 確保 username 為 'admin' 的使用者被指派為 super-admin 角色
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// 取得 super-admin 角色
|
||||
$role = DB::table('roles')->where('name', 'super-admin')->first();
|
||||
if (!$role) {
|
||||
return; // 角色不存在則跳過
|
||||
}
|
||||
|
||||
// 取得 admin 使用者
|
||||
$user = DB::table('users')->where('username', 'admin')->first();
|
||||
if (!$user) {
|
||||
return; // 使用者不存在則跳過
|
||||
}
|
||||
|
||||
// 檢查是否已有此角色
|
||||
$exists = DB::table('model_has_roles')
|
||||
->where('role_id', $role->id)
|
||||
->where('model_type', 'App\\Models\\User')
|
||||
->where('model_id', $user->id)
|
||||
->exists();
|
||||
|
||||
if (!$exists) {
|
||||
// 先移除該使用者的所有現有角色
|
||||
DB::table('model_has_roles')
|
||||
->where('model_type', 'App\\Models\\User')
|
||||
->where('model_id', $user->id)
|
||||
->delete();
|
||||
|
||||
// 指派 super-admin 角色
|
||||
DB::table('model_has_roles')->insert([
|
||||
'role_id' => $role->id,
|
||||
'model_type' => 'App\\Models\\User',
|
||||
'model_id' => $user->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// 此 Migration 不需要復原邏輯
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
* 確保 super-admin 角色擁有所有權限
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// 取得 super-admin 角色
|
||||
$role = DB::table('roles')->where('name', 'super-admin')->first();
|
||||
if (!$role) {
|
||||
return; // 角色不存在則跳過
|
||||
}
|
||||
|
||||
// 取得所有權限
|
||||
$permissions = DB::table('permissions')->pluck('id');
|
||||
if ($permissions->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 清除該角色現有的權限
|
||||
DB::table('role_has_permissions')
|
||||
->where('role_id', $role->id)
|
||||
->delete();
|
||||
|
||||
// 指派所有權限給 super-admin
|
||||
$inserts = $permissions->map(fn ($permissionId) => [
|
||||
'permission_id' => $permissionId,
|
||||
'role_id' => $role->id,
|
||||
])->toArray();
|
||||
|
||||
DB::table('role_has_permissions')->insert($inserts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// 此 Migration 不需要復原邏輯
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
* 使用更寬鬆的條件確保 admin 使用者被設為 super-admin
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// 取得 super-admin 角色
|
||||
$role = DB::table('roles')->where('name', 'super-admin')->first();
|
||||
if (!$role) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 嘗試用多種條件抓取 admin 使用者
|
||||
$user = DB::table('users')
|
||||
->where('username', 'admin')
|
||||
->orWhere('email', 'admin@example.com')
|
||||
->orWhere('username', 'admin01') // 之前對話提到的可能 username
|
||||
->first();
|
||||
|
||||
if (!$user) {
|
||||
// 如果都找不到,嘗試抓 ID = 1 或 2 (通常是建立的第一個使用者)
|
||||
$user = DB::table('users')->orderBy('id')->first();
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 檢查是否已有此角色
|
||||
$exists = DB::table('model_has_roles')
|
||||
->where('role_id', $role->id)
|
||||
->where('model_type', 'App\\Models\\User')
|
||||
->where('model_id', $user->id)
|
||||
->exists();
|
||||
|
||||
if (!$exists) {
|
||||
// 移除舊角色並指派新角色
|
||||
DB::table('model_has_roles')
|
||||
->where('model_type', 'App\\Models\\User')
|
||||
->where('model_id', $user->id)
|
||||
->delete();
|
||||
|
||||
DB::table('model_has_roles')->insert([
|
||||
'role_id' => $role->id,
|
||||
'model_type' => 'App\\Models\\User',
|
||||
'model_id' => $user->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$roles = [
|
||||
'super-admin' => '系統管理員',
|
||||
'admin' => '一般管理員',
|
||||
'warehouse-manager' => '倉庫管理員',
|
||||
'purchaser' => '採購人員',
|
||||
'viewer' => '檢視人員',
|
||||
];
|
||||
|
||||
foreach ($roles as $name => $displayName) {
|
||||
DB::table('roles')
|
||||
->where('name', $name)
|
||||
->update(['display_name' => $displayName]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$roles = [
|
||||
'super-admin',
|
||||
'admin',
|
||||
'warehouse-manager',
|
||||
'purchaser',
|
||||
'viewer',
|
||||
];
|
||||
|
||||
DB::table('roles')
|
||||
->whereIn('name', $roles)
|
||||
->update(['display_name' => null]);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user