259 lines
8.1 KiB
PHP
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,
|
|
]);
|
|
}
|
|
}
|