with([ 'preparedByCashier', 'reviewedByAccountant', 'approvedByManager' ]) ->orderByDesc('reconciliation_month'); // Filter by status if ($request->filled('reconciliation_status')) { $query->where('reconciliation_status', $request->reconciliation_status); } // Filter by month if ($request->filled('month')) { $query->whereYear('reconciliation_month', '=', substr($request->month, 0, 4)) ->whereMonth('reconciliation_month', '=', substr($request->month, 5, 2)); } $reconciliations = $query->paginate(15); return view('admin.bank-reconciliations.index', [ 'reconciliations' => $reconciliations, ]); } /** * Show the form for creating a new bank reconciliation */ public function create(Request $request) { // Check authorization $this->authorize('prepare_bank_reconciliation'); // Default to current month $month = $request->input('month', now()->format('Y-m')); // Get system book balance from cashier ledger $systemBalance = CashierLedgerEntry::getLatestBalance(); return view('admin.bank-reconciliations.create', [ 'month' => $month, 'systemBalance' => $systemBalance, ]); } /** * Store a newly created bank reconciliation */ public function store(Request $request) { // Check authorization $this->authorize('prepare_bank_reconciliation'); $validated = $request->validate([ 'reconciliation_month' => ['required', 'date_format:Y-m'], 'bank_statement_balance' => ['required', 'numeric'], 'bank_statement_date' => ['required', 'date'], 'bank_statement_file' => ['nullable', 'file', 'max:10240'], 'system_book_balance' => ['required', 'numeric'], 'outstanding_checks' => ['nullable', 'array'], 'outstanding_checks.*.amount' => ['required', 'numeric', 'min:0'], 'outstanding_checks.*.check_number' => ['nullable', 'string'], 'outstanding_checks.*.description' => ['nullable', 'string'], 'deposits_in_transit' => ['nullable', 'array'], 'deposits_in_transit.*.amount' => ['required', 'numeric', 'min:0'], 'deposits_in_transit.*.date' => ['nullable', 'date'], 'deposits_in_transit.*.description' => ['nullable', 'string'], 'bank_charges' => ['nullable', 'array'], 'bank_charges.*.amount' => ['required', 'numeric', 'min:0'], 'bank_charges.*.description' => ['nullable', 'string'], 'notes' => ['nullable', 'string'], ]); DB::beginTransaction(); try { // Handle bank statement file upload $statementPath = null; if ($request->hasFile('bank_statement_file')) { $statementPath = $request->file('bank_statement_file')->store('bank-statements', 'local'); } // Create reconciliation record $reconciliation = new BankReconciliation([ 'reconciliation_month' => $validated['reconciliation_month'] . '-01', 'bank_statement_balance' => $validated['bank_statement_balance'], 'bank_statement_date' => $validated['bank_statement_date'], 'bank_statement_file_path' => $statementPath, 'system_book_balance' => $validated['system_book_balance'], 'outstanding_checks' => $validated['outstanding_checks'] ?? [], 'deposits_in_transit' => $validated['deposits_in_transit'] ?? [], 'bank_charges' => $validated['bank_charges'] ?? [], 'prepared_by_cashier_id' => $request->user()->id, 'prepared_at' => now(), 'notes' => $validated['notes'] ?? null, ]); // Calculate adjusted balance $reconciliation->adjusted_balance = $reconciliation->calculateAdjustedBalance(); // Calculate discrepancy $reconciliation->discrepancy_amount = $reconciliation->calculateDiscrepancy(); // Set status based on discrepancy if ($reconciliation->hasDiscrepancy()) { $reconciliation->reconciliation_status = BankReconciliation::STATUS_DISCREPANCY; } else { $reconciliation->reconciliation_status = BankReconciliation::STATUS_PENDING; } $reconciliation->save(); AuditLogger::log('bank_reconciliation.created', $reconciliation, $validated); DB::commit(); $message = '銀行調節表已建立。'; if ($reconciliation->hasDiscrepancy()) { $message .= ' 發現差異金額:NT$ ' . number_format($reconciliation->discrepancy_amount, 2); } return redirect() ->route('admin.bank-reconciliations.show', $reconciliation) ->with('status', $message); } catch (\Exception $e) { DB::rollBack(); return redirect() ->back() ->withInput() ->with('error', '建立銀行調節表時發生錯誤:' . $e->getMessage()); } } /** * Display the specified bank reconciliation */ public function show(BankReconciliation $bankReconciliation) { $bankReconciliation->load([ 'preparedByCashier', 'reviewedByAccountant', 'approvedByManager' ]); // Get outstanding items summary $summary = $bankReconciliation->getOutstandingItemsSummary(); return view('admin.bank-reconciliations.show', [ 'reconciliation' => $bankReconciliation, 'summary' => $summary, ]); } /** * Accountant reviews the bank reconciliation */ public function review(Request $request, BankReconciliation $bankReconciliation) { // Check authorization $this->authorize('review_bank_reconciliation'); // Check if can be reviewed if (!$bankReconciliation->canBeReviewed()) { return redirect() ->route('admin.bank-reconciliations.show', $bankReconciliation) ->with('error', '此銀行調節表無法覆核。'); } $validated = $request->validate([ 'review_notes' => ['nullable', 'string'], ]); DB::beginTransaction(); try { $bankReconciliation->update([ 'reviewed_by_accountant_id' => $request->user()->id, 'reviewed_at' => now(), ]); AuditLogger::log('bank_reconciliation.reviewed', $bankReconciliation, $validated); DB::commit(); return redirect() ->route('admin.bank-reconciliations.show', $bankReconciliation) ->with('status', '銀行調節表已完成會計覆核。'); } catch (\Exception $e) { DB::rollBack(); return redirect() ->back() ->with('error', '覆核銀行調節表時發生錯誤:' . $e->getMessage()); } } /** * Manager approves the bank reconciliation */ public function approve(Request $request, BankReconciliation $bankReconciliation) { // Check authorization $this->authorize('approve_bank_reconciliation'); // Check if can be approved if (!$bankReconciliation->canBeApproved()) { return redirect() ->route('admin.bank-reconciliations.show', $bankReconciliation) ->with('error', '此銀行調節表無法核准。'); } DB::beginTransaction(); try { // Determine final status $finalStatus = $bankReconciliation->hasDiscrepancy() ? BankReconciliation::STATUS_DISCREPANCY : BankReconciliation::STATUS_COMPLETED; $bankReconciliation->update([ 'approved_by_manager_id' => $request->user()->id, 'approved_at' => now(), 'reconciliation_status' => $finalStatus, ]); AuditLogger::log('bank_reconciliation.approved', $bankReconciliation, [ 'approved_by' => $request->user()->name, 'final_status' => $finalStatus, ]); DB::commit(); $message = '銀行調節表已核准。'; if ($finalStatus === BankReconciliation::STATUS_DISCREPANCY) { $message .= ' 請注意:仍有差異需要處理。'; } return redirect() ->route('admin.bank-reconciliations.show', $bankReconciliation) ->with('status', $message); } catch (\Exception $e) { DB::rollBack(); return redirect() ->back() ->with('error', '核准銀行調節表時發生錯誤:' . $e->getMessage()); } } /** * Download bank statement file */ public function downloadStatement(BankReconciliation $bankReconciliation) { if (!$bankReconciliation->bank_statement_file_path) { abort(404, '找不到銀行對帳單檔案'); } if (!Storage::disk('local')->exists($bankReconciliation->bank_statement_file_path)) { abort(404, '銀行對帳單檔案不存在'); } return Storage::disk('local')->download($bankReconciliation->bank_statement_file_path); } /** * Export reconciliation to PDF */ public function exportPdf(BankReconciliation $bankReconciliation) { // Check authorization $this->authorize('view_cashier_ledger'); $bankReconciliation->load([ 'preparedByCashier', 'reviewedByAccountant', 'approvedByManager' ]); $summary = $bankReconciliation->getOutstandingItemsSummary(); // Generate PDF (you would need to implement PDF generation library like DomPDF or TCPDF) // For now, return a view that can be printed return view('admin.bank-reconciliations.pdf', [ 'reconciliation' => $bankReconciliation, 'summary' => $summary, ]); } }