273 lines
9.0 KiB
PHP
273 lines
9.0 KiB
PHP
<?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->all();
|
|
|
|
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'] ?? now()->format('Y-m')) . '-01',
|
|
'bank_statement_balance' => $validated['bank_statement_balance'] ?? 0,
|
|
'bank_statement_date' => $validated['bank_statement_date'] ?? now()->format('Y-m-d'),
|
|
'bank_statement_file_path' => $statementPath,
|
|
'system_book_balance' => $validated['system_book_balance'] ?? 0,
|
|
'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,
|
|
]);
|
|
|
|
// Ensure required numeric fields
|
|
$adjusted = (float) $reconciliation->calculateAdjustedBalance();
|
|
$reconciliation->adjusted_balance = $adjusted ?: 0;
|
|
$reconciliation->discrepancy_amount = (float) $reconciliation->calculateDiscrepancy();
|
|
$reconciliation->reconciliation_status = BankReconciliation::STATUS_PENDING;
|
|
|
|
$reconciliation->save();
|
|
|
|
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(),
|
|
]);
|
|
|
|
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,
|
|
]);
|
|
|
|
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,
|
|
]);
|
|
}
|
|
}
|