Initial commit

This commit is contained in:
2025-11-20 23:21:05 +08:00
commit 13bc6db529
378 changed files with 54527 additions and 0 deletions

View File

@@ -0,0 +1,306 @@
<?php
namespace App\Http\Controllers;
use App\Models\BankReconciliation;
use App\Models\CashierLedgerEntry;
use App\Support\AuditLogger;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class BankReconciliationController extends Controller
{
/**
* Display a listing of bank reconciliations
*/
public function index(Request $request)
{
$query = BankReconciliation::query()
->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,
]);
}
}