293 lines
9.7 KiB
PHP
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);
|
|
}
|
|
}
|