Initial commit
This commit is contained in:
359
app/Http/Controllers/PaymentOrderController.php
Normal file
359
app/Http/Controllers/PaymentOrderController.php
Normal file
@@ -0,0 +1,359 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user