Initial commit
This commit is contained in:
292
app/Http/Controllers/CashierLedgerController.php
Normal file
292
app/Http/Controllers/CashierLedgerController.php
Normal file
@@ -0,0 +1,292 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user