'銷售&金流紀錄', 'description' => '銷售交易與金流明細查詢', 'features' => [ '銷售記錄查詢', '金流對帳', '發票管理', '退款處理', ] ]); } // 取貨碼設定 public function pickupCodes(Request $request) { $tab = $request->input('tab', 'list'); $isAjax = $request->ajax(); $data = [ 'title' => '取貨碼設定', 'description' => '產生與管理商品取貨驗證碼', 'tab' => $tab, ]; // 1. 取貨碼列表 (list) if (!$isAjax || $tab === 'list') { $query = PickupCode::with(['machine.slots.product', 'creator'])->latest(); if ($request->search) { $query->where(function ($q) use ($request) { $q->where('code', 'like', "%{$request->search}%") ->orWhereHas('machine', function ($mq) use ($request) { $mq->where('name', 'like', "%{$request->search}%") ->orWhere('serial_no', 'like', "%{$request->search}%"); }); }); } if ($request->status && trim($request->status) !== '') { $query->where('status', trim($request->status)); } $per_page = $request->input('per_page', 15); $data['pickupCodes'] = $query->paginate($per_page, ['*'], 'list_page')->withQueryString(); // 供新增彈窗使用的機台清單 $data['machines'] = Machine::all(); } // 2. 操作紀錄 (logs) if (!$isAjax || $tab === 'logs') { $logQuery = SystemOperationLog::with('user:id,name') ->where('module', 'pickup_code'); if ($request->filled('search_log')) { $search = $request->input('search_log'); $logQuery->where(function ($q) use ($search) { $q->where('target_id', 'like', "%{$search}%") ->orWhere('new_values', 'like', "%{$search}%") ->orWhere('old_values', 'like', "%{$search}%"); }); } // 新增:類型篩選 if ($request->filled('action')) { $logQuery->where('action', $request->action); } // 新增:日期區間篩選 if ($request->filled('start_date') && $request->filled('end_date')) { try { $start = \Carbon\Carbon::parse($request->start_date)->startOfMinute(); $end = \Carbon\Carbon::parse($request->end_date)->endOfMinute(); $logQuery->whereBetween('created_at', [$start, $end]); } catch (\Exception $e) { } } $data['logs'] = $logQuery->latest() ->paginate($request->input('per_page', 15), ['*'], 'log_page') ->withQueryString(); // 定義可用動作 $data['actions'] = [ 'create' => __('create'), 'update' => __('update'), 'cancel' => __('cancel'), 'used' => __('used'), 'consume' => __('consume'), 'consume_failed' => __('consume_failed'), 'verify_success' => __('verify_success'), ]; } if ($isAjax) { return response()->json([ 'success' => true, 'tab' => $tab, 'html' => view('admin.sales.pickup-codes.partials.tab-' . $tab, $data)->render() ]); } return view('admin.sales.pickup-codes.index', $data); } /** * 產生取貨碼 */ public function storePickupCode(Request $request) { $validated = $request->validate([ 'machine_id' => 'required|exists:machines,id', 'slot_no' => 'required|string', 'expires_hours' => 'nullable|integer|min:1|max:720', // 最長一個月 'custom_code' => 'nullable|string|min:4|max:12', ]); $machine = Machine::findOrFail($validated['machine_id']); $expiresAt = now()->addHours((int) ($request->expires_hours ?? 24)); $pickupCode = PickupCode::create([ 'machine_id' => $validated['machine_id'], 'slot_no' => $validated['slot_no'], 'code' => $request->custom_code ?? PickupCode::generateUniqueCode($validated['machine_id']), 'expires_at' => $expiresAt, 'status' => 'active', 'company_id' => $machine->company_id, 'created_by' => Auth::id(), ]); SystemOperationLog::create([ 'company_id' => $machine->company_id, 'user_id' => Auth::id(), 'module' => 'pickup_code', 'action' => 'create', 'target_id' => $pickupCode->id, 'target_type' => PickupCode::class, 'new_values' => $pickupCode->toArray(), ]); return back()->with('success', __('Pickup code generated: :code', ['code' => $pickupCode->code])); } /** * 更新取貨碼 (僅限修改時間) */ public function updatePickupCode(Request $request, PickupCode $pickupCode) { $validated = $request->validate([ 'expires_at' => 'required|date|after:now', ]); $oldValues = $pickupCode->toArray(); $pickupCode->update([ 'expires_at' => $validated['expires_at'], ]); SystemOperationLog::create([ 'company_id' => $pickupCode->company_id, 'user_id' => Auth::id(), 'module' => 'pickup_code', 'action' => 'update', 'target_id' => $pickupCode->id, 'target_type' => PickupCode::class, 'old_values' => $oldValues, 'new_values' => $pickupCode->toArray(), ]); return back()->with('success', __('Pickup code updated.')); } /** * 刪除/取消取貨碼 */ public function destroyPickupCode(PickupCode $pickupCode) { $oldValues = $pickupCode->toArray(); $pickupCode->update(['status' => 'cancelled']); SystemOperationLog::create([ 'company_id' => $pickupCode->company_id, 'user_id' => Auth::id(), 'module' => 'pickup_code', 'action' => 'cancel', 'target_id' => $pickupCode->id, 'target_type' => PickupCode::class, 'old_values' => $oldValues, 'new_values' => $pickupCode->toArray(), ]); return back()->with('success', __('Pickup code cancelled.')); } // 購買單 public function orders() { return view('admin.placeholder', [ 'title' => '購買單', 'description' => '購買訂單管理', ]); } // 促銷時段設定 public function promotions() { return view('admin.placeholder', [ 'title' => '促銷時段設定', 'description' => '促銷活動時間設定', ]); } // 通行碼設定 public function passCodes(Request $request) { $tab = $request->input('tab', 'list'); $isAjax = $request->ajax(); $data = [ 'title' => '通行碼設定', 'description' => '特殊通行權限碼管理 (測試/補貨用)', 'tab' => $tab, ]; // 1. 通行碼列表 (list) if (!$isAjax || $tab === 'list') { $query = PassCode::with(['machine', 'creator'])->latest(); if ($request->search) { $query->where(function ($q) use ($request) { $q->where('code', 'like', "%{$request->search}%") ->orWhere('name', 'like', "%{$request->search}%") ->orWhereHas('machine', function ($mq) use ($request) { $mq->where('name', 'like', "%{$request->search}%") ->orWhere('serial_no', 'like', "%{$request->search}%"); }); }); } if ($request->status && trim($request->status) !== '') { $status = trim($request->status); if ($status === 'active') { $query->where('status', 'active') ->where(function ($q) { $q->whereNull('expires_at') ->orWhere('expires_at', '>', now()); }); } elseif ($status === 'expired') { $query->where('status', 'active') ->whereNotNull('expires_at') ->where('expires_at', '<=', now()); } else { $query->where('status', $status); } } $data['passCodes'] = $query->paginate($request->input('per_page', 15), ['*'], 'list_page')->withQueryString(); $data['machines'] = Machine::all(); } // 2. 操作紀錄 (logs) if (!$isAjax || $tab === 'logs') { $logQuery = SystemOperationLog::with('user:id,name') ->where('module', 'pass_code'); if ($request->filled('search_log')) { $search = $request->input('search_log'); $logQuery->where(function ($q) use ($search) { $q->where('target_id', 'like', "%{$search}%") ->orWhere('new_values', 'like', "%{$search}%") ->orWhere('old_values', 'like', "%{$search}%"); }); } // 新增:類型篩選 if ($request->filled('action')) { $logQuery->where('action', $request->action); } // 新增:日期區間篩選 if ($request->filled('start_date') && $request->filled('end_date')) { try { $start = \Carbon\Carbon::parse($request->start_date)->startOfMinute(); $end = \Carbon\Carbon::parse($request->end_date)->endOfMinute(); $logQuery->whereBetween('created_at', [$start, $end]); } catch (\Exception $e) { } } $data['logs'] = $logQuery->latest() ->paginate($request->input('per_page', 15), ['*'], 'log_page') ->withQueryString(); // 定義可用動作 $data['actions'] = [ 'create' => __('create'), 'update' => __('update'), 'cancel' => __('cancel'), 'used' => __('used'), 'consume' => __('consume'), 'verify_success' => __('verify_success'), ]; } if ($isAjax) { return response()->json([ 'success' => true, 'tab' => $tab, 'html' => view('admin.sales.pass-codes.partials.tab-' . $tab, $data)->render() ]); } return view('admin.sales.pass-codes.index', $data); } /** * 產生通行碼 */ public function storePassCode(Request $request) { $validated = $request->validate([ 'machine_id' => 'required|exists:machines,id', 'name' => 'required|string|max:50', 'expires_days' => 'nullable|integer|min:0', 'custom_code' => 'required|string|min:4|max:12', ]); $machine = Machine::findOrFail($validated['machine_id']); $expiresAt = $request->expires_days ? now()->addDays((int) $request->expires_days) : null; $passCode = PassCode::create([ 'machine_id' => $validated['machine_id'], 'name' => $validated['name'] ?? 'Manual Generate', 'code' => $validated['custom_code'] ?? PassCode::generateUniqueCode($validated['machine_id']), 'expires_at' => $expiresAt, 'status' => 'active', 'company_id' => $machine->company_id, 'created_by' => Auth::id(), ]); SystemOperationLog::create([ 'company_id' => $machine->company_id, 'user_id' => Auth::id(), 'module' => 'pass_code', 'action' => 'create', 'target_id' => $passCode->id, 'target_type' => PassCode::class, 'new_values' => $passCode->toArray(), ]); return back()->with('success', __('Pass code created: :code', ['code' => $passCode->code])); } /** * 更新通行碼 */ public function updatePassCode(Request $request, PassCode $passCode) { $validated = $request->validate([ 'name' => 'nullable|string|max:50', 'expires_at' => 'nullable|date', 'status' => 'nullable|in:active,disabled', ]); // 確保 expires_at 為空字串時轉為 null if (isset($validated['expires_at']) && empty($validated['expires_at'])) { $validated['expires_at'] = null; } $oldValues = $passCode->toArray(); $passCode->update(array_filter($validated, function ($value, $key) use ($request) { return $request->has($key); }, ARRAY_FILTER_USE_BOTH)); SystemOperationLog::create([ 'company_id' => $passCode->company_id, 'user_id' => Auth::id(), 'module' => 'pass_code', 'action' => 'update', 'target_id' => $passCode->id, 'target_type' => PassCode::class, 'old_values' => $oldValues, 'new_values' => $passCode->toArray(), ]); return back()->with('success', __('Pass code updated.')); } /** * 刪除通行碼 (改為停用) */ public function destroyPassCode(PassCode $passCode) { $oldValues = $passCode->toArray(); $passCode->update(['status' => 'disabled']); SystemOperationLog::create([ 'company_id' => $passCode->company_id, 'user_id' => Auth::id(), 'module' => 'pass_code', 'action' => 'cancel', 'target_id' => $passCode->id, 'target_type' => PassCode::class, 'old_values' => $oldValues, 'new_values' => $passCode->toArray(), ]); return back()->with('success', __('Pass code cancelled.')); } // 來店禮設定 public function storeGifts() { return view('admin.placeholder', [ 'title' => '來店禮設定', 'description' => '來店優惠活動設定', ]); } }