vernova/app/Http/Controllers/ExpenseController.php
2026-07-26 19:58:27 +03:30

259 lines
8.1 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Http\Requests\StoreExpenseRequest;
use App\Models\Expense;
use App\Models\Project;
use App\Models\Currency;
use App\Helpers\CurrencyHelper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ExpenseController extends Controller
{
/**
* Display a listing of expenses with filters.
*/
public function index(Request $request)
{
$this->authorize('viewAny', Expense::class);
$query = Expense::with(['project', 'currency', 'requestedBy', 'approvedBy']);
// Filter by project
if ($request->filled('project_id')) {
$query->where('project_id', $request->project_id);
}
// Filter by category
if ($request->filled('category')) {
$query->byCategory($request->category);
}
// Filter by status
if ($request->filled('status')) {
$query->byStatus($request->status);
}
// Filter by date range
if ($request->filled('from_date')) {
$query->where('expense_date', '>=', $request->from_date);
}
if ($request->filled('to_date')) {
$query->where('expense_date', '<=', $request->to_date);
}
$query->orderBy('expense_date', 'desc');
$expenses = $query->paginate(20)->appends($request->query());
$projects = Project::whereIn('status', ['planning', 'active'])->get();
$categories = ['materials', 'labor', 'equipment', 'transport', 'services', 'food', 'accommodation', 'other'];
$statuses = ['pending', 'approved', 'rejected', 'paid'];
return view('expenses.index', compact('expenses', 'projects', 'categories', 'statuses'));
}
/**
* Show the form for creating a new expense (multi-currency aware).
*/
public function create()
{
$this->authorize('create', Expense::class);
$expense = null; // ← این خط اضافه بشه
$projects = Project::whereIn('status', ['planning', 'active'])->get();
$currencies = Currency::where('is_active', true)->get();
$categories = ['materials', 'labor', 'equipment', 'transport', 'services', 'food', 'accommodation', 'other'];
$baseCurrency = CurrencyHelper::getBaseCurrency();
return view('expenses.form', compact('expense', 'projects', 'currencies', 'categories', 'baseCurrency'));
}
/**
* Store a newly created expense in storage.
*/
public function store(StoreExpenseRequest $request)
{
$this->authorize('create', Expense::class);
$data = $request->validated();
$data['tenant_id'] = $this->getTenantId();
$data['requested_by'] = Auth::id();
// Convert jalali date if needed
if (!empty($data['expense_date'])) {
$data['expense_date'] = \App\Helpers\CalendarHelper::parseDate($data['expense_date']);
}
// Handle currency conversion
$baseCurrency = CurrencyHelper::getBaseCurrency();
$selectedCurrency = Currency::find($data['currency_id']);
if ($selectedCurrency && $baseCurrency && $selectedCurrency->id !== $baseCurrency->id) {
$data['original_amount'] = $data['amount'];
$data['amount'] = CurrencyHelper::convert(
(float) $data['amount'],
$selectedCurrency->code,
$baseCurrency->code
);
}
// Handle receipt upload
if ($request->hasFile('receipt')) {
$data['receipt_path'] = $request->file('receipt')->store('receipts', 'public');
}
$expense = Expense::create($data);
// Handle multiple file attachments
if ($request->hasFile('attachments')) {
$expense->attachFiles($request->file('attachments'), 'receipt');
}
return redirect()
->route('expenses.show', $expense)
->with('success', __('expense.created_successfully'));
}
/**
* Display the specified expense.
*/
public function show(Expense $expense)
{
$this->authorize('view', $expense);
$expense->load(['project', 'currency', 'requestedBy', 'approvedBy', 'pettyCash']);
return view('expenses.show', compact('expense'));
}
/**
* Show the form for editing the specified expense.
*/
public function edit(Expense $expense)
{
$this->authorize('update', $expense);
$projects = Project::whereIn('status', ['planning', 'active'])->get();
$currencies = Currency::where('is_active', true)->get();
$categories = ['materials', 'labor', 'equipment', 'transport', 'services', 'food', 'accommodation', 'other'];
$baseCurrency = CurrencyHelper::getBaseCurrency();
return view('expenses.form', compact('expense', 'projects', 'currencies', 'categories', 'baseCurrency'));
}
/**
* Update the specified expense in storage.
*/
public function update(StoreExpenseRequest $request, Expense $expense)
{
$this->authorize('update', $expense);
$data = $request->validated();
// Convert jalali date if needed
if (!empty($data['expense_date'])) {
$data['expense_date'] = \App\Helpers\CalendarHelper::parseDate($data['expense_date']);
}
// Handle currency conversion
$baseCurrency = CurrencyHelper::getBaseCurrency();
$selectedCurrency = Currency::find($data['currency_id']);
if ($selectedCurrency && $baseCurrency && $selectedCurrency->id !== $baseCurrency->id) {
$data['original_amount'] = $data['amount'];
$data['amount'] = CurrencyHelper::convert(
(float) $data['amount'],
$selectedCurrency->code,
$baseCurrency->code
);
}
// Handle receipt upload
if ($request->hasFile('receipt')) {
// Delete old receipt
if ($expense->receipt_path) {
\Illuminate\Support\Facades\Storage::disk('public')->delete($expense->receipt_path);
}
$data['receipt_path'] = $request->file('receipt')->store('receipts', 'public');
}
$expense->update($data);
// Handle multiple file attachments
if ($request->hasFile('attachments')) {
$expense->attachFiles($request->file('attachments'), 'receipt');
}
return redirect()
->route('expenses.show', $expense)
->with('success', __('expense.updated_successfully'));
}
/**
* Remove the specified expense from storage.
*/
public function destroy(Expense $expense)
{
$this->authorize('delete', $expense);
$expense->delete();
return redirect()
->route('expenses.index')
->with('success', __('expense.deleted_successfully'));
}
/**
* Approve an expense via AJAX.
*/
public function approve(Expense $expense)
{
$this->authorize('approve', $expense);
$expense->update([
'status' => 'approved',
'approved_by' => Auth::id(),
'approval_date' => now(),
]);
// Update petty cash remaining if linked
if ($expense->petty_cash_id && $expense->pettyCash) {
$expense->pettyCash->updateRemaining();
}
return response()->json([
'success' => true,
'message' => __('expense.approved_successfully'),
'status' => $expense->status,
]);
}
/**
* Reject an expense via AJAX.
*/
public function reject(Expense $expense)
{
$this->authorize('approve', $expense);
$expense->update([
'status' => 'rejected',
'approved_by' => Auth::id(),
'approval_date' => now(),
]);
// Update petty cash remaining if linked
if ($expense->petty_cash_id && $expense->pettyCash) {
$expense->pettyCash->updateRemaining();
}
return response()->json([
'success' => true,
'message' => __('expense.rejected_successfully'),
'status' => $expense->status,
]);
}
}