with(['financeDocument', 'recordedByCashier']) ->orderByDesc('entry_date') ->orderByDesc('id'); // Filter by entry type if ($request->filled('entry_type')) { $query->where('entry_type', $request->entry_type); } // Filter by payment method if ($request->filled('payment_method')) { $query->where('payment_method', $request->payment_method); } // Filter by bank account if ($request->filled('bank_account')) { $query->where('bank_account', $request->bank_account); } // Filter by date range if ($request->filled('date_from')) { $query->where('entry_date', '>=', $request->date_from); } if ($request->filled('date_to')) { $query->where('entry_date', '<=', $request->date_to); } $entries = $query->paginate(20); // Get latest balance for each bank account $balances = DB::table('cashier_ledger_entries') ->select('bank_account', DB::raw('MAX(id) as latest_id')) ->groupBy('bank_account') ->get() ->mapWithKeys(function ($item) { $latest = CashierLedgerEntry::find($item->latest_id); return [$item->bank_account => $latest->balance_after ?? 0]; }); return view('admin.cashier-ledger.index', [ 'entries' => $entries, 'balances' => $balances, ]); } /** * Show the form for creating a new ledger entry */ public function create(Request $request) { // Check authorization $this->authorize('record_cashier_ledger'); // Get finance document if specified $financeDocument = null; if ($request->filled('finance_document_id')) { $financeDocument = FinanceDocument::with('paymentOrder')->findOrFail($request->finance_document_id); } return view('admin.cashier-ledger.create', [ 'financeDocument' => $financeDocument, ]); } /** * Store a newly created ledger entry */ public function store(Request $request) { // Check authorization $this->authorize('record_cashier_ledger'); $validated = $request->validate([ 'finance_document_id' => ['nullable', 'exists:finance_documents,id'], 'entry_date' => ['required', 'date'], 'entry_type' => ['required', 'in:receipt,payment'], 'payment_method' => ['required', 'in:bank_transfer,check,cash'], 'bank_account' => ['nullable', 'string', 'max:100'], 'amount' => ['required', 'numeric', 'min:0.01'], 'receipt_number' => ['nullable', 'string', 'max:50'], 'transaction_reference' => ['nullable', 'string', 'max:100'], 'notes' => ['nullable', 'string'], ]); DB::beginTransaction(); try { // Get latest balance for the bank account $bankAccount = $validated['bank_account'] ?? 'default'; $balanceBefore = CashierLedgerEntry::getLatestBalance($bankAccount); // Create new entry $entry = new CashierLedgerEntry([ 'finance_document_id' => $validated['finance_document_id'] ?? null, 'entry_date' => $validated['entry_date'], 'entry_type' => $validated['entry_type'], 'payment_method' => $validated['payment_method'], 'bank_account' => $bankAccount, 'amount' => $validated['amount'], 'balance_before' => $balanceBefore, 'receipt_number' => $validated['receipt_number'] ?? null, 'transaction_reference' => $validated['transaction_reference'] ?? null, 'recorded_by_cashier_id' => $request->user()->id, 'recorded_at' => now(), 'notes' => $validated['notes'] ?? null, ]); // Calculate balance after $entry->balance_after = $entry->calculateBalanceAfter($balanceBefore); $entry->save(); // Update finance document if linked if ($validated['finance_document_id']) { $financeDocument = FinanceDocument::find($validated['finance_document_id']); $financeDocument->update([ 'cashier_ledger_entry_id' => $entry->id, ]); } AuditLogger::log('cashier_ledger_entry.created', $entry, $validated); DB::commit(); return redirect() ->route('admin.cashier-ledger.show', $entry) ->with('status', '現金簿記錄已建立。'); } catch (\Exception $e) { DB::rollBack(); return redirect() ->back() ->withInput() ->with('error', '建立現金簿記錄時發生錯誤:' . $e->getMessage()); } } /** * Display the specified ledger entry */ public function show(CashierLedgerEntry $cashierLedgerEntry) { $cashierLedgerEntry->load([ 'financeDocument.member', 'financeDocument.paymentOrder', 'recordedByCashier' ]); return view('admin.cashier-ledger.show', [ 'entry' => $cashierLedgerEntry, ]); } /** * Show ledger balance report */ public function balanceReport(Request $request) { // Check authorization $this->authorize('view_cashier_ledger'); // Get all bank accounts with their latest balances $accounts = DB::table('cashier_ledger_entries') ->select('bank_account') ->distinct() ->get() ->map(function ($account) { $latest = CashierLedgerEntry::where('bank_account', $account->bank_account) ->orderBy('entry_date', 'desc') ->orderBy('id', 'desc') ->first(); return [ 'bank_account' => $account->bank_account, 'balance' => $latest->balance_after ?? 0, 'last_updated' => $latest->entry_date ?? null, ]; }); // Get transaction summary for current month $startOfMonth = now()->startOfMonth(); $endOfMonth = now()->endOfMonth(); $monthlySummary = [ 'receipts' => CashierLedgerEntry::where('entry_type', CashierLedgerEntry::ENTRY_TYPE_RECEIPT) ->whereBetween('entry_date', [$startOfMonth, $endOfMonth]) ->sum('amount'), 'payments' => CashierLedgerEntry::where('entry_type', CashierLedgerEntry::ENTRY_TYPE_PAYMENT) ->whereBetween('entry_date', [$startOfMonth, $endOfMonth]) ->sum('amount'), ]; return view('admin.cashier-ledger.balance-report', [ 'accounts' => $accounts, 'monthlySummary' => $monthlySummary, ]); } /** * Export ledger entries to CSV */ public function export(Request $request) { // Check authorization $this->authorize('view_cashier_ledger'); $query = CashierLedgerEntry::query() ->with(['financeDocument', 'recordedByCashier']) ->orderBy('entry_date') ->orderBy('id'); // Apply filters if ($request->filled('date_from')) { $query->where('entry_date', '>=', $request->date_from); } if ($request->filled('date_to')) { $query->where('entry_date', '<=', $request->date_to); } if ($request->filled('bank_account')) { $query->where('bank_account', $request->bank_account); } $entries = $query->get(); $filename = 'cashier_ledger_' . now()->format('Ymd_His') . '.csv'; $headers = [ 'Content-Type' => 'text/csv; charset=UTF-8', 'Content-Disposition' => "attachment; filename=\"{$filename}\"", ]; $callback = function() use ($entries) { $file = fopen('php://output', 'w'); // Add BOM for UTF-8 fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF)); // Header row fputcsv($file, [ '記帳日期', '類型', '付款方式', '銀行帳戶', '金額', '交易前餘額', '交易後餘額', '收據編號', '交易參考號', '記錄人', '備註', ]); // Data rows foreach ($entries as $entry) { fputcsv($file, [ $entry->entry_date->format('Y-m-d'), $entry->getEntryTypeText(), $entry->getPaymentMethodText(), $entry->bank_account ?? '', $entry->amount, $entry->balance_before, $entry->balance_after, $entry->receipt_number ?? '', $entry->transaction_reference ?? '', $entry->recordedByCashier->name ?? '', $entry->notes ?? '', ]); } fclose($file); }; return response()->stream($callback, 200, $headers); } }