360 lines
13 KiB
PHP
360 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\FinanceDocument;
|
|
use App\Models\PaymentOrder;
|
|
use App\Support\AuditLogger;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class PaymentOrderController extends Controller
|
|
{
|
|
/**
|
|
* Display a listing of payment orders
|
|
*/
|
|
public function index(Request $request)
|
|
{
|
|
$query = PaymentOrder::query()
|
|
->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, FinanceDocument $financeDocument)
|
|
{
|
|
// Check authorization
|
|
$this->authorize('create_payment_order');
|
|
|
|
// Check if document is ready
|
|
if (!$financeDocument->canCreatePaymentOrder()) {
|
|
return redirect()
|
|
->route('admin.finance.show', $financeDocument)
|
|
->with('error', '此財務申請單尚未完成審核流程,無法製作付款單。');
|
|
}
|
|
|
|
$validated = $request->validate([
|
|
'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) {
|
|
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());
|
|
}
|
|
}
|
|
}
|