Files
usher-manage-stack/app/Http/Controllers/CashierLedgerController.php
2025-11-20 23:21:05 +08:00

293 lines
9.7 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\CashierLedgerEntry;
use App\Models\FinanceDocument;
use App\Models\PaymentOrder;
use App\Support\AuditLogger;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class CashierLedgerController extends Controller
{
/**
* Display a listing of cashier ledger entries
*/
public function index(Request $request)
{
$query = CashierLedgerEntry::query()
->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);
}
}