[FEAT] 重構機台日誌 UI 與增加多語系支援,並整合 IoT API 核心架構
- 機台日誌:對齊 Luxury UI 規範,實作整合式佈局與分頁組件。 - 多語系:完成機台日誌繁、英、日三語系翻譯與動態處理。 - UI 規範:更新 SKILL.md 定義「標準列表 Bible」。 - 後端:完善 TenantScoped 隔離邏輯,修復儀表板死循環與 User Model 缺失。 - IoT:擴展機台、會員 Model 並建立交易、商品、狀態等核心表結構。 - 基礎設施:設置台北時區與 Docker 環境變數同步。
This commit is contained in:
@@ -15,6 +15,7 @@ return new class extends Migration
|
||||
$table->foreignId('company_id')->nullable()->after('id')
|
||||
->constrained('companies')->nullOnDelete();
|
||||
$table->tinyInteger('status')->default(1)->after('role'); // 1:啟用, 0:停用
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,7 +26,7 @@ return new class extends Migration
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropConstrainedForeignId('company_id');
|
||||
$table->dropColumn('status');
|
||||
$table->dropColumn(['status', 'deleted_at']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?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->foreignId('company_id')->nullable()->after('id')
|
||||
->constrained('companies')->onDelete('cascade');
|
||||
|
||||
// 移除舊有的唯一鍵
|
||||
$table->dropUnique('roles_name_guard_name_unique');
|
||||
// 新增複合唯一鍵 (涵蓋租戶)
|
||||
$table->unique(['name', 'guard_name', 'company_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->dropUnique(['name', 'guard_name', 'company_id']);
|
||||
$table->unique(['name', 'guard_name']);
|
||||
$table->dropConstrainedForeignId('company_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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::table('machines', function (Blueprint $table) {
|
||||
$table->string('serial_no')->unique()->after('name')->comment('機台序號');
|
||||
$table->string('model')->nullable()->after('serial_no')->comment('型號');
|
||||
$table->tinyInteger('current_page')->default(0)->after('status')->comment('當前頁面狀態');
|
||||
$table->string('door_status')->nullable()->after('current_page')->comment('門禁狀態');
|
||||
$table->string('api_token')->nullable()->after('firmware_version')->comment('API Token');
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('machines', function (Blueprint $table) {
|
||||
$table->dropColumn(['serial_no', 'model', 'current_page', 'door_status', 'api_token', 'deleted_at']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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('members', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('members', 'company_id')) {
|
||||
$table->foreignId('company_id')->nullable()->constrained()->onDelete('cascade');
|
||||
}
|
||||
if (!Schema::hasColumn('members', 'barcode')) {
|
||||
$table->string('barcode')->nullable()->index();
|
||||
}
|
||||
if (!Schema::hasColumn('members', 'points')) {
|
||||
$table->integer('points')->default(0);
|
||||
}
|
||||
if (!Schema::hasColumn('members', 'wallet_balance')) {
|
||||
$table->decimal('wallet_balance', 10, 2)->default(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('members', function (Blueprint $table) {
|
||||
$table->dropColumn(['company_id', 'barcode', 'points', 'wallet_balance']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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_categories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('company_id')->nullable()->constrained('companies')->onDelete('cascade');
|
||||
$table->string('name');
|
||||
$table->string('name_dictionary_key')->nullable()->comment('多語系 Key');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::create('products', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('company_id')->nullable()->constrained('companies')->onDelete('cascade');
|
||||
$table->foreignId('category_id')->nullable()->constrained('product_categories')->onDelete('set null');
|
||||
$table->string('name');
|
||||
$table->string('name_dictionary_key')->nullable()->comment('多語系 Key');
|
||||
$table->string('sku')->nullable()->comment('商品編號');
|
||||
$table->decimal('price', 10, 2)->default(0)->comment('售價');
|
||||
$table->decimal('cost', 10, 2)->nullable()->comment('成本');
|
||||
$table->string('image')->nullable()->comment('圖片 URL');
|
||||
$table->string('barcode')->nullable()->comment('條碼');
|
||||
$table->boolean('is_timer_product')->default(false)->comment('是否為計時型商品');
|
||||
$table->boolean('is_active')->default(true)->comment('是否上架');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['company_id', 'sku']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('products');
|
||||
Schema::dropIfExists('product_categories');
|
||||
}
|
||||
};
|
||||
@@ -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('machine_slots', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('machine_id')->constrained('machines')->onDelete('cascade');
|
||||
$table->string('slot_no')->comment('貨道編號 (B017 tid, B710 cid)');
|
||||
$table->foreignId('product_id')->nullable()->constrained('products')->onDelete('set null');
|
||||
$table->integer('stock')->default(0)->comment('當前庫存');
|
||||
$table->integer('max_stock')->default(0)->comment('最大容量');
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['machine_id', 'slot_no']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('machine_slots');
|
||||
}
|
||||
};
|
||||
@@ -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('translations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('group', 50)->comment('分組 (product, category, system)');
|
||||
$table->string('key', 100)->comment('字典鍵值');
|
||||
$table->string('locale', 10)->comment('語系代碼');
|
||||
$table->text('value')->comment('翻譯內容');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['group', 'key', 'locale']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('translations');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
<?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('payment_types', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->smallInteger('code')->unique()->comment('金流代碼');
|
||||
$table->string('name')->comment('中文名稱');
|
||||
$table->string('category')->nullable()->comment('大分類');
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('orders', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('company_id')->nullable()->constrained('companies')->onDelete('cascade');
|
||||
$table->string('flow_id')->nullable()->unique()->comment('Cloud 金流 ID (B600 response)');
|
||||
$table->string('order_no')->nullable()->index()->comment('APP 訂單號 (B600 req9)');
|
||||
$table->foreignId('machine_id')->constrained('machines')->onDelete('cascade');
|
||||
$table->foreignId('member_id')->nullable()->constrained('members')->onDelete('set null');
|
||||
$table->smallInteger('payment_type')->comment('金流類型碼');
|
||||
$table->decimal('total_amount', 10, 2)->comment('消費金額');
|
||||
$table->decimal('discount_amount', 10, 2)->default(0)->comment('折扣金額');
|
||||
$table->decimal('pay_amount', 10, 2)->comment('實付金額');
|
||||
$table->decimal('change_amount', 10, 2)->default(0)->comment('找零');
|
||||
$table->integer('points_used')->default(0)->comment('使用點數');
|
||||
$table->decimal('original_amount', 10, 2)->nullable()->comment('折扣前金額');
|
||||
$table->tinyInteger('payment_status')->comment('0:失敗/1:成功');
|
||||
$table->text('payment_request')->nullable()->comment('金流送出 data');
|
||||
$table->text('payment_response')->nullable()->comment('金流回傳 data');
|
||||
$table->datetime('payment_at')->nullable()->comment('支付時間');
|
||||
$table->string('member_barcode')->nullable()->comment('會員條碼');
|
||||
$table->string('invoice_info')->nullable()->comment('發票歸戶');
|
||||
$table->datetime('machine_time')->nullable()->comment('機台時間');
|
||||
$table->string('status')->default('completed')->comment('訂單狀態');
|
||||
$table->json('metadata')->nullable()->comment('其他資訊');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['company_id', 'machine_id', 'created_at']);
|
||||
});
|
||||
|
||||
Schema::create('order_items', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('order_id')->constrained('orders')->onDelete('cascade');
|
||||
$table->foreignId('product_id')->constrained('products')->onDelete('cascade');
|
||||
$table->string('product_name')->nullable()->comment('商品名稱 (備份)');
|
||||
$table->string('sku')->nullable()->comment('商品編號 (備份)');
|
||||
$table->integer('quantity');
|
||||
$table->decimal('price', 10, 2);
|
||||
$table->decimal('subtotal', 10, 2);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('invoices', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('company_id')->nullable()->constrained('companies')->onDelete('cascade');
|
||||
$table->foreignId('order_id')->nullable()->constrained('orders')->onDelete('set null');
|
||||
$table->string('flow_id')->index()->comment('金流 ID');
|
||||
$table->foreignId('machine_id')->constrained('machines')->onDelete('cascade');
|
||||
$table->string('rtn_code')->nullable();
|
||||
$table->string('rtn_msg')->nullable();
|
||||
$table->string('invoice_no')->nullable();
|
||||
$table->decimal('amount', 10, 2)->default(0);
|
||||
$table->string('carrier_id')->nullable();
|
||||
$table->date('invoice_date')->nullable();
|
||||
$table->string('random_number')->nullable();
|
||||
$table->string('love_code')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::create('dispense_records', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('company_id')->nullable()->constrained('companies')->onDelete('cascade');
|
||||
$table->foreignId('order_id')->nullable()->constrained('orders')->onDelete('set null');
|
||||
$table->string('flow_id')->nullable()->index()->comment('金流 ID');
|
||||
$table->foreignId('machine_id')->constrained('machines')->onDelete('cascade');
|
||||
$table->string('product_id')->comment('機台端傳入之商品 ID');
|
||||
$table->string('slot_no')->comment('貨道編號');
|
||||
$table->decimal('amount', 10, 2);
|
||||
$table->integer('remaining_stock')->nullable();
|
||||
$table->tinyInteger('dispense_status')->comment('0:成功/1:失敗');
|
||||
$table->string('member_barcode')->nullable();
|
||||
$table->datetime('machine_time')->nullable();
|
||||
$table->integer('points_used')->default(0);
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['company_id', 'machine_id', 'flow_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('dispense_records');
|
||||
Schema::dropIfExists('invoices');
|
||||
Schema::dropIfExists('order_items');
|
||||
Schema::dropIfExists('orders');
|
||||
Schema::dropIfExists('payment_types');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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('remote_commands', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('machine_id')->constrained('machines')->onDelete('cascade');
|
||||
$table->string('command_type', 50)->comment('reboot, lock, stock_update, dispense...');
|
||||
$table->enum('status', ['pending', 'sent', 'success', 'failed'])->default('pending');
|
||||
$table->json('payload')->nullable()->comment('指令參數');
|
||||
$table->integer('ttl')->default(60)->comment('失效秒數');
|
||||
$table->timestamp('executed_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['machine_id', 'status']);
|
||||
});
|
||||
|
||||
Schema::create('coin_inventories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('machine_id')->constrained('machines')->onDelete('cascade');
|
||||
$table->integer('value_1')->default(0);
|
||||
$table->integer('value_5')->default(0);
|
||||
$table->integer('value_10')->default(0);
|
||||
$table->integer('value_50')->default(0);
|
||||
$table->integer('value_100')->default(0);
|
||||
$table->integer('value_500')->default(0);
|
||||
$table->integer('value_1000')->default(0);
|
||||
$table->string('operator')->nullable()->comment('操作人 (0=消費者)');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('timer_statuses', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('machine_id')->constrained('machines')->onDelete('cascade');
|
||||
$table->string('slot_no')->comment('貨道 ID (B710 cid)');
|
||||
$table->foreignId('product_id')->nullable()->constrained('products')->onDelete('set null');
|
||||
$table->tinyInteger('status')->default(0)->comment('0:未啟用/1:使用中/2:異常');
|
||||
$table->integer('remaining_seconds')->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['machine_id', 'slot_no']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('timer_statuses');
|
||||
Schema::dropIfExists('coin_inventories');
|
||||
Schema::dropIfExists('remote_commands');
|
||||
}
|
||||
};
|
||||
@@ -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('machines', function (Blueprint $table) {
|
||||
$table->string('current_page')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('machines', function (Blueprint $table) {
|
||||
$table->tinyInteger('current_page')->default(0)->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user