with([ 'financeDocument', 'createdByAccountant', 'verifiedByCashier', 'executedByCashier' ]) ->orderByDesc('created_at'); // Filter by status if ($request->filled('status')) { $query->where('status', $request->status); } // Filter by verification status if ($request->filled('verification_status')) { $query->where('verification_status', $request->verification_status); } // Filter by execution status if ($request->filled('execution_status')) { $query->where('execution_status', $request->execution_status); } $paymentOrders = $query->paginate(15); return view('admin.payment-orders.index', [ 'paymentOrders' => $paymentOrders, ]); } /** * Show the form for creating a new payment order (accountant only) */ public function create(FinanceDocument $financeDocument) { // Check authorization $this->authorize('create_payment_order'); // Check if document is ready for payment order creation if (!$financeDocument->canCreatePaymentOrder()) { return redirect() ->route('admin.finance.show', $financeDocument) ->with('error', '此財務申請單尚未完成審核流程,無法製作付款單。'); } // Check if payment order already exists if ($financeDocument->paymentOrder !== null) { return redirect() ->route('admin.payment-orders.show', $financeDocument->paymentOrder) ->with('error', '此財務申請單已有付款單。'); } $financeDocument->load(['member', 'submittedBy']); return view('admin.payment-orders.create', [ 'financeDocument' => $financeDocument, ]); } /** * Store a newly created payment order (accountant creates) */ public function store(Request $request) { // Check authorization $this->authorize('create_payment_order'); $financeDocument = FinanceDocument::findOrFail($request->input('finance_document_id')); // Check if document is ready if (!$financeDocument->canCreatePaymentOrder()) { if (app()->environment('testing')) { \Log::info('payment_order.store.blocked', [ 'finance_document_id' => $financeDocument->id, 'status' => $financeDocument->status, 'amount_tier' => $financeDocument->amount_tier, 'payment_order_created_at' => $financeDocument->payment_order_created_at, ]); } return redirect() ->route('admin.finance.show', $financeDocument) ->with('error', '此財務申請單尚未完成審核流程,無法製作付款單。'); } $validated = $request->validate([ 'finance_document_id' => ['required', 'exists:finance_documents,id'], 'payee_name' => ['required', 'string', 'max:100'], 'payee_bank_code' => ['nullable', 'string', 'max:10'], 'payee_account_number' => ['nullable', 'string', 'max:30'], 'payee_bank_name' => ['nullable', 'string', 'max:100'], 'payment_amount' => ['required', 'numeric', 'min:0'], 'payment_method' => ['required', 'in:bank_transfer,check,cash'], 'notes' => ['nullable', 'string'], ]); DB::beginTransaction(); try { // Generate payment order number $paymentOrderNumber = PaymentOrder::generatePaymentOrderNumber(); // Create payment order $paymentOrder = PaymentOrder::create([ 'finance_document_id' => $financeDocument->id, 'payee_name' => $validated['payee_name'], 'payee_bank_code' => $validated['payee_bank_code'] ?? null, 'payee_account_number' => $validated['payee_account_number'] ?? null, 'payee_bank_name' => $validated['payee_bank_name'] ?? null, 'payment_amount' => $validated['payment_amount'], 'payment_method' => $validated['payment_method'], 'created_by_accountant_id' => $request->user()->id, 'payment_order_number' => $paymentOrderNumber, 'notes' => $validated['notes'] ?? null, 'status' => PaymentOrder::STATUS_PENDING_VERIFICATION, 'verification_status' => PaymentOrder::VERIFICATION_PENDING, 'execution_status' => PaymentOrder::EXECUTION_PENDING, ]); // Update finance document $financeDocument->update([ 'payment_order_created_by_accountant_id' => $request->user()->id, 'payment_order_created_at' => now(), 'payment_method' => $validated['payment_method'], 'payee_name' => $validated['payee_name'], 'payee_account_number' => $validated['payee_account_number'] ?? null, 'payee_bank_name' => $validated['payee_bank_name'] ?? null, ]); AuditLogger::log('payment_order.created', $paymentOrder, $validated); DB::commit(); return redirect() ->route('admin.payment-orders.show', $paymentOrder) ->with('status', "付款單 {$paymentOrderNumber} 已建立,等待出納覆核。"); } catch (\Exception $e) { if (app()->environment('testing')) { \Log::error('payment_order.store.failed', [ 'finance_document_id' => $financeDocument->id ?? null, 'error' => $e->getMessage(), ]); } DB::rollBack(); return redirect() ->back() ->withInput() ->with('error', '建立付款單時發生錯誤:' . $e->getMessage()); } } /** * Display the specified payment order */ public function show(PaymentOrder $paymentOrder) { $paymentOrder->load([ 'financeDocument.member', 'financeDocument.submittedBy', 'createdByAccountant', 'verifiedByCashier', 'executedByCashier' ]); return view('admin.payment-orders.show', [ 'paymentOrder' => $paymentOrder, ]); } /** * Cashier verifies the payment order */ public function verify(Request $request, PaymentOrder $paymentOrder) { // Check authorization $this->authorize('verify_payment_order'); // Check if can be verified if (!$paymentOrder->canBeVerifiedByCashier()) { return redirect() ->route('admin.payment-orders.show', $paymentOrder) ->with('error', '此付款單無法覆核。'); } $validated = $request->validate([ 'action' => ['required', 'in:approve,reject'], 'verification_notes' => ['nullable', 'string'], ]); DB::beginTransaction(); try { if ($validated['action'] === 'approve') { // Approve $paymentOrder->update([ 'verified_by_cashier_id' => $request->user()->id, 'verified_at' => now(), 'verification_status' => PaymentOrder::VERIFICATION_APPROVED, 'verification_notes' => $validated['verification_notes'] ?? null, 'status' => PaymentOrder::STATUS_VERIFIED, ]); // Update finance document $paymentOrder->financeDocument->update([ 'payment_verified_by_cashier_id' => $request->user()->id, 'payment_verified_at' => now(), ]); AuditLogger::log('payment_order.verified_approved', $paymentOrder, $validated); $message = '付款單已覆核通過,可以執行付款。'; } else { // Reject $paymentOrder->update([ 'verified_by_cashier_id' => $request->user()->id, 'verified_at' => now(), 'verification_status' => PaymentOrder::VERIFICATION_REJECTED, 'verification_notes' => $validated['verification_notes'] ?? null, 'status' => PaymentOrder::STATUS_CANCELLED, ]); AuditLogger::log('payment_order.verified_rejected', $paymentOrder, $validated); $message = '付款單已駁回。'; } DB::commit(); return redirect() ->route('admin.payment-orders.show', $paymentOrder) ->with('status', $message); } catch (\Exception $e) { DB::rollBack(); return redirect() ->back() ->with('error', '覆核付款單時發生錯誤:' . $e->getMessage()); } } /** * Cashier executes the payment */ public function execute(Request $request, PaymentOrder $paymentOrder) { // Check authorization $this->authorize('execute_payment'); // Check if can be executed if (!$paymentOrder->canBeExecuted()) { return redirect() ->route('admin.payment-orders.show', $paymentOrder) ->with('error', '此付款單無法執行。'); } $validated = $request->validate([ 'transaction_reference' => ['required', 'string', 'max:100'], 'payment_receipt' => ['nullable', 'file', 'max:10240'], // 10MB max 'execution_notes' => ['nullable', 'string'], ]); DB::beginTransaction(); try { // Handle receipt upload $receiptPath = null; if ($request->hasFile('payment_receipt')) { $receiptPath = $request->file('payment_receipt')->store('payment-receipts', 'local'); } // Execute payment $paymentOrder->update([ 'executed_by_cashier_id' => $request->user()->id, 'executed_at' => now(), 'execution_status' => PaymentOrder::EXECUTION_COMPLETED, 'transaction_reference' => $validated['transaction_reference'], 'payment_receipt_path' => $receiptPath, 'status' => PaymentOrder::STATUS_EXECUTED, ]); // Update finance document $paymentOrder->financeDocument->update([ 'payment_executed_by_cashier_id' => $request->user()->id, 'payment_executed_at' => now(), 'payment_transaction_id' => $validated['transaction_reference'], 'payment_receipt_path' => $receiptPath, 'actual_payment_amount' => $paymentOrder->payment_amount, ]); AuditLogger::log('payment_order.executed', $paymentOrder, $validated); DB::commit(); return redirect() ->route('admin.payment-orders.show', $paymentOrder) ->with('status', '付款已執行完成。'); } catch (\Exception $e) { DB::rollBack(); return redirect() ->back() ->with('error', '執行付款時發生錯誤:' . $e->getMessage()); } } /** * Download payment receipt */ public function downloadReceipt(PaymentOrder $paymentOrder) { if (!$paymentOrder->payment_receipt_path) { abort(404, '找不到付款憑證'); } if (!Storage::disk('local')->exists($paymentOrder->payment_receipt_path)) { abort(404, '付款憑證檔案不存在'); } return Storage::disk('local')->download($paymentOrder->payment_receipt_path); } /** * Cancel a payment order */ public function cancel(Request $request, PaymentOrder $paymentOrder) { // Check authorization $this->authorize('create_payment_order'); // Only accountant can cancel // Cannot cancel if already executed if ($paymentOrder->isExecuted()) { return redirect() ->route('admin.payment-orders.show', $paymentOrder) ->with('error', '已執行的付款單無法取消。'); } DB::beginTransaction(); try { $paymentOrder->update([ 'status' => PaymentOrder::STATUS_CANCELLED, ]); AuditLogger::log('payment_order.cancelled', $paymentOrder, [ 'cancelled_by' => $request->user()->id, ]); DB::commit(); return redirect() ->route('admin.payment-orders.index') ->with('status', '付款單已取消。'); } catch (\Exception $e) { DB::rollBack(); return redirect() ->back() ->with('error', '取消付款單時發生錯誤:' . $e->getMessage()); } } }