[FEAT] 重構機台日誌 UI 與增加多語系支援,並整合 IoT API 核心架構

- 機台日誌:對齊 Luxury UI 規範,實作整合式佈局與分頁組件。
- 多語系:完成機台日誌繁、英、日三語系翻譯與動態處理。
- UI 規範:更新 SKILL.md 定義「標準列表 Bible」。
- 後端:完善 TenantScoped 隔離邏輯,修復儀表板死循環與 User Model 缺失。
- IoT:擴展機台、會員 Model 並建立交易、商品、狀態等核心表結構。
- 基礎設施:設置台北時區與 Docker 環境變數同步。
This commit is contained in:
2026-03-16 17:29:15 +08:00
parent 1851e91c86
commit 3ce88ed342
54 changed files with 2015 additions and 227 deletions

View File

@@ -0,0 +1,123 @@
<?php
namespace App\Http\Controllers\Api\V1\App;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Machine\Machine;
use App\Jobs\Machine\ProcessHeartbeat;
use App\Jobs\Machine\ProcessTimerStatus;
use App\Jobs\Machine\ProcessCoinInventory;
use Illuminate\Support\Facades\Validator;
class MachineController extends Controller
{
/**
* B010: Machine Heartbeat & Status Update (Asynchronous)
*/
public function heartbeat(Request $request)
{
$machine = $request->get('machine');
$data = $request->all();
// 異步處理狀態更新
ProcessHeartbeat::dispatch($machine->serial_no, $data);
return response()->json([
'success' => true,
'code' => 200,
'message' => 'OK',
'status' => '49' // 某些硬體可能需要的成功碼
], 202); // 202 Accepted
}
/**
* B017: Get Slot Info & Stock (Synchronous)
*/
public function getSlots(Request $request)
{
$machine = $request->get('machine');
$slots = $machine->slots()->with('product')->get();
return response()->json([
'success' => true,
'code' => 200,
'data' => $slots->map(function ($slot) {
return [
'slot_no' => $slot->slot_no,
'product_id' => $slot->product_id,
'stock' => $slot->stock,
'capacity' => $slot->capacity,
'price' => $slot->price,
'status' => $slot->status,
];
})
]);
}
/**
* B710: Sync Timer status (Asynchronous)
*/
public function syncTimer(Request $request)
{
$machine = $request->get('machine');
$data = $request->all();
ProcessTimerStatus::dispatch($machine->serial_no, $data);
return response()->json(['success' => true], 202);
}
/**
* B220: Sync Coin Inventory (Asynchronous)
*/
public function syncCoinInventory(Request $request)
{
$machine = $request->get('machine');
$data = $request->all();
ProcessCoinInventory::dispatch($machine->serial_no, $data);
return response()->json(['success' => true], 202);
}
/**
* B650: Verify Member Code/Barcode (Synchronous)
*/
public function verifyMember(Request $request)
{
$validator = Validator::make($request->all(), [
'code' => 'required|string',
]);
if ($validator->fails()) {
return response()->json(['success' => false, 'message' => 'Invalid code'], 400);
}
$code = $request->input('code');
// 搜尋會員 (barcode 或特定驗證碼)
$member = \App\Models\Member\Member::where('barcode', $code)
->orWhere('id', $code) // 暫時支援 ID
->first();
if (!$member) {
return response()->json([
'success' => false,
'code' => 404,
'message' => 'Member not found'
], 404);
}
return response()->json([
'success' => true,
'code' => 200,
'data' => [
'member_id' => $member->id,
'name' => $member->name,
'points' => $member->points,
'wallet_balance' => $member->wallet_balance ?? 0,
]
]);
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Http\Controllers\Api\V1\App;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Jobs\Transaction\ProcessTransaction;
use App\Jobs\Transaction\ProcessInvoice;
use App\Jobs\Transaction\ProcessDispenseRecord;
class TransactionController extends Controller
{
/**
* B600: Record Transaction (Asynchronous)
*/
public function store(Request $request)
{
$machine = $request->get('machine');
$data = $request->all();
$data['serial_no'] = $machine->serial_no;
ProcessTransaction::dispatch($data);
return response()->json([
'success' => true,
'code' => 200,
'message' => 'Accepted'
], 202);
}
/**
* B601: Record Invoice (Asynchronous)
*/
public function recordInvoice(Request $request)
{
$machine = $request->get('machine');
$data = $request->all();
$data['serial_no'] = $machine->serial_no;
ProcessInvoice::dispatch($data);
return response()->json([
'success' => true,
'code' => 200,
'message' => 'Accepted'
], 202);
}
/**
* B602: Record Dispense Result (Asynchronous)
*/
public function recordDispense(Request $request)
{
$machine = $request->get('machine');
$data = $request->all();
$data['serial_no'] = $machine->serial_no;
ProcessDispenseRecord::dispatch($data);
return response()->json([
'success' => true,
'code' => 200,
'message' => 'Accepted'
], 202);
}
}