star-cloud/app/Http/Controllers/Admin/StaffCardController.php
sky121113 99debbf259 [FIX] 員工卡編輯彈窗未回填所屬公司,並防止儲存時洗掉 company_id
1. 修正系統管理員開啟「編輯員工識別卡」時,所屬公司下拉顯示為「選擇公司」空值的問題:Preline 的 option value 為字串,原本直接以數字型別的 company_id 呼叫 setValue,嚴格比對失配導致無法回填,現統一轉為字串 (對齊 ads / welcome-gifts / pickup-codes 既有寫法)。
2. 強化 StaffCardController@update 的寫入安全 (RBAC 1.3):非系統管理員一律強制綁定自身 company_id;系統管理員若未帶入公司則保留原本歸屬,避免在上述空值狀態按下儲存時把 company_id 洗成 null,造成機台刷卡「查無此員工卡」的孤兒卡。
3. 影響範圍:資料設定 → 員工識別管理之新增/編輯彈窗 (resources/views/admin/data-config/staff-cards/index.blade.php) 與 App\Http\Controllers\Admin\StaffCardController;僅前端回填與後端寫入保護,不更動資料表結構與既有正常卡片資料。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:18:13 +08:00

189 lines
7.1 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\StaffCard;
use App\Models\StaffCardLog;
use App\Services\StaffCardImportService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class StaffCardController extends Controller
{
public function index(Request $request)
{
$tab = $request->get('tab', 'staff_list');
$isSystemAdmin = auth()->user()->isSystemAdmin();
$companies = [];
if ($isSystemAdmin) {
$companies = \App\Models\System\Company::active()->orderBy('name')->get();
}
$cardsQuery = StaffCard::with('lastMachine')
->when($request->search, function ($q) use ($request) {
$q->where(function ($sq) use ($request) {
$sq->where('name', 'like', "%{$request->search}%")
->orWhere('employee_id', 'like', "%{$request->search}%")
->orWhere('card_uid', 'like', "%{$request->search}%");
});
})
->when($isSystemAdmin && $request->company_id, function ($q) use ($request) {
$q->where('company_id', $request->company_id);
});
$logsQuery = StaffCardLog::with(['staffCard', 'machine', 'order'])
->when($request->log_search, function ($q) use ($request) {
$q->whereHas('staffCard', function ($sq) use ($request) {
$sq->where('name', 'like', "%{$request->log_search}%")
->orWhere('employee_id', 'like', "%{$request->log_search}%")
->orWhere('card_uid', 'like', "%{$request->log_search}%");
});
})
->when($isSystemAdmin && $request->log_company_id, function ($q) use ($request) {
$q->where('company_id', $request->log_company_id);
});
$staffPerPage = $request->input('staff_per_page', 10);
$usagePerPage = $request->input('usage_per_page', 10);
if ($request->ajax() && $request->has('_ajax')) {
if ($tab === 'staff_list') {
$cards = $cardsQuery->latest()->paginate($staffPerPage, ['*'], 'staff_page');
return response()->json([
'success' => true,
'html' => view('admin.data-config.staff-cards.partials.tab-staff-list', compact('cards', 'companies'))->render()
]);
} else {
$logs = $logsQuery->latest()->paginate($usagePerPage, ['*'], 'usage_page');
return response()->json([
'success' => true,
'html' => view('admin.data-config.staff-cards.partials.tab-usage-logs', compact('logs', 'companies'))->render()
]);
}
}
$cards = $cardsQuery->latest()->paginate($staffPerPage, ['*'], 'staff_page');
$logs = $logsQuery->latest()->paginate($usagePerPage, ['*'], 'usage_page');
return view('admin.data-config.staff-cards.index', compact('cards', 'logs', 'tab', 'companies'));
}
public function store(Request $request)
{
$validated = $request->validate([
'company_id' => 'nullable|exists:companies,id',
'employee_id' => 'required|string|max:50',
'name' => 'required|string|max:100',
'card_uid' => 'required|string|max:100',
'status' => 'nullable|string|in:active,inactive',
'notes' => 'nullable|string',
]);
$validated['status'] = $request->get('status', 'active');
$card = StaffCard::create($validated);
if ($request->ajax()) {
return response()->json([
'success' => true,
'message' => __('Staff card created successfully'),
'data' => $card
]);
}
return redirect()->route('admin.data-config.staff-cards.index')->with('success', __('Staff card created successfully'));
}
public function update(Request $request, StaffCard $staffCard)
{
$validated = $request->validate([
'company_id' => 'nullable|exists:companies,id',
'employee_id' => 'required|string|max:50',
'name' => 'required|string|max:100',
'card_uid' => 'required|string|max:100',
'status' => 'nullable|string|in:active,inactive',
'notes' => 'nullable|string',
]);
$validated['status'] = $request->get('status', $staffCard->status);
// 寫入安全company_id 不得由前端任意決定或被洗成 null
// - 非系統管理員:強制綁定自身公司 (租戶隔離)
// - 系統管理員:若未帶入公司,保留原本歸屬,避免變成無公司歸屬的孤兒卡 (機台將查無此卡)
if (!auth()->user()->isSystemAdmin()) {
$validated['company_id'] = auth()->user()->company_id;
} elseif (empty($validated['company_id'])) {
$validated['company_id'] = $staffCard->company_id;
}
$staffCard->update($validated);
if ($request->ajax()) {
return response()->json([
'success' => true,
'message' => __('Staff card updated successfully'),
]);
}
return redirect()->route('admin.data-config.staff-cards.index')->with('success', __('Staff card updated successfully'));
}
public function destroy(StaffCard $staffCard)
{
$staffCard->delete();
if (request()->ajax()) {
return response()->json([
'success' => true,
'message' => __('Staff card deleted successfully'),
]);
}
return redirect()->route('admin.data-config.staff-cards.index')->with('success', __('Staff card deleted successfully'));
}
public function toggleStatus(StaffCard $staffCard)
{
$staffCard->status = $staffCard->status === 'active' ? 'inactive' : 'active';
$staffCard->save();
return response()->json([
'success' => true,
'message' => __('Status updated successfully'),
]);
}
public function downloadTemplate(StaffCardImportService $importService)
{
return $importService->downloadTemplate();
}
public function import(Request $request, StaffCardImportService $importService)
{
$request->validate([
'file' => 'required|file|mimes:xlsx,xls,csv|max:5120',
'company_id' => 'nullable|exists:companies,id',
]);
$companyId = $request->get('company_id') ?: auth()->user()->company_id;
if (auth()->user()->isSystemAdmin() && !$companyId) {
return response()->json([
'success' => false,
'message' => __('Please select a company'),
], 422);
}
$results = $importService->import($request->file('file')->getRealPath(), $companyId);
return response()->json([
'success' => true,
'message' => __(':count staff cards imported successfully', ['count' => $results['success_count']]),
'results' => $results,
]);
}
}