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

376 lines
13 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\PettyCash;
use App\Models\Project;
use App\Models\Currency;
use App\Helpers\CurrencyHelper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class PettyCashController extends Controller
{
/**
* Display a listing of petty cash requests.
*/
public function index(Request $request)
{
$this->authorize('viewAny', PettyCash::class);
$query = PettyCash::with(['project', 'currency', 'requestedBy', 'approvedBy']);
if ($request->filled('project_id')) {
$query->where('project_id', $request->project_id);
}
if ($request->filled('status')) {
$query->byStatus($request->status);
}
// Date range filter
if ($request->filled('from_date')) {
$query->where('request_date', '>=', \App\Helpers\CalendarHelper::parseDate($request->from_date));
}
if ($request->filled('to_date')) {
$query->where('request_date', '<=', \App\Helpers\CalendarHelper::parseDate($request->to_date));
}
$query->orderBy('request_date', 'desc');
$pettyCashes = $query->paginate(20)->appends($request->query());
$projects = Project::whereIn('status', ['planning', 'active'])->get();
$statuses = ['pending', 'approved', 'rejected', 'settled'];
// Summary statistics
$baseQuery = PettyCash::query();
if ($request->filled('project_id')) {
$baseQuery->where('project_id', $request->project_id);
}
if ($request->filled('status')) {
$baseQuery->where('status', $request->status);
}
$totalAmount = (clone $baseQuery)->where('status', '!=', 'rejected')->sum('amount');
$totalExpenses = (clone $baseQuery)->where('status', '!=', 'rejected')->with('expenses')
->get()->sum(function ($pc) {
return $pc->expenses->whereIn('status', ['approved', 'paid'])->sum('amount');
});
$totalRemaining = $totalAmount - $totalExpenses;
return view('petty-cashes.index', compact('pettyCashes', 'projects', 'statuses', 'totalAmount', 'totalExpenses', 'totalRemaining'));
}
/**
* Show the form for creating a new petty cash request.
*/
public function create()
{
$this->authorize('create', PettyCash::class);
$pettyCash = null;
$projects = Project::whereIn('status', ['planning', 'active'])->get();
$currencies = Currency::where('is_active', true)->get();
$baseCurrency = CurrencyHelper::getBaseCurrency();
return view('petty-cashes.form', compact('pettyCash', 'projects', 'currencies', 'baseCurrency'));
}
/**
* Show the form for adding an expense to a petty cash.
*/
public function createExpense(PettyCash $pettyCash)
{
$this->authorize('update', $pettyCash);
if ($pettyCash->status !== 'approved') {
return redirect()->route('petty-cashes.show', $pettyCash)
->with('error', __('pettyCash.only_approved_can_add_expense'));
}
$pettyCash->load(['project', 'currency']);
$currencies = Currency::where('is_active', true)->get();
$categories = ['materials', 'labor', 'equipment', 'transport', 'services', 'food', 'accommodation', 'other'];
$baseCurrency = CurrencyHelper::getBaseCurrency();
return view('petty-cashes.expense-form', compact('pettyCash', 'currencies', 'categories', 'baseCurrency'));
}
/**
* Store an expense linked to a petty cash.
*/
public function storeExpense(Request $request, PettyCash $pettyCash)
{
$this->authorize('update', $pettyCash);
if ($pettyCash->status !== 'approved') {
return redirect()->route('petty-cashes.show', $pettyCash)
->with('error', __('pettyCash.only_approved_can_add_expense'));
}
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'amount' => 'required|numeric|min:0',
'currency_id' => 'required|exists:currencies,id',
'category' => 'required|string',
'expense_date' => 'required|date',
'receipt' => 'nullable|file|mimes:jpg,jpeg,png,pdf,doc,docx|max:10240',
'attachments' => 'nullable|array|max:5',
'attachments.*' => 'file|mimes:jpg,jpeg,png,pdf,doc,docx|max:10240',
]);
$validated['tenant_id'] = $pettyCash->tenant_id;
$validated['project_id'] = $pettyCash->project_id;
$validated['petty_cash_id'] = $pettyCash->id;
$validated['requested_by'] = Auth::id();
$validated['status'] = 'pending';
// Parse Jalali date
if (!empty($validated['expense_date'])) {
$validated['expense_date'] = \App\Helpers\CalendarHelper::parseDate($validated['expense_date']);
}
// Handle currency conversion
$baseCurrency = CurrencyHelper::getBaseCurrency();
$selectedCurrency = Currency::find($validated['currency_id']);
if ($selectedCurrency && $baseCurrency && $selectedCurrency->id !== $baseCurrency->id) {
$validated['original_amount'] = $validated['amount'];
$validated['amount'] = CurrencyHelper::convert(
(float) $validated['amount'],
$selectedCurrency->code,
$baseCurrency->code
);
}
// Handle receipt upload
if ($request->hasFile('receipt')) {
$validated['receipt_path'] = $request->file('receipt')->store('receipts', 'public');
}
$expense = \App\Models\Expense::create($validated);
// Handle multiple file attachments
if ($request->hasFile('attachments')) {
$expense->attachFiles($request->file('attachments'), 'receipt');
}
// Update petty cash remaining
$pettyCash->updateRemaining();
return redirect()
->route('petty-cashes.show', $pettyCash)
->with('success', __('pettyCash.expense_added_successfully'));
}
/**
* Store a newly created petty cash request.
*/
public function store(Request $request)
{
$this->authorize('create', PettyCash::class);
$validated = $request->validate([
'project_id' => 'required|exists:projects,id',
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'amount' => 'required|numeric|min:0',
'currency_id' => 'required|exists:currencies,id',
'request_date' => 'required|date',
'receipt' => 'nullable|file|mimes:jpg,jpeg,png,pdf,doc,docx|max:10240',
'attachments' => 'nullable|array|max:5',
'attachments.*' => 'file|mimes:jpg,jpeg,png,pdf,doc,docx|max:10240',
]);
$validated['tenant_id'] = $this->getTenantId();
$validated['requested_by'] = Auth::id();
$validated['status'] = 'pending';
// Handle currency conversion
$baseCurrency = CurrencyHelper::getBaseCurrency();
$selectedCurrency = Currency::find($validated['currency_id']);
if ($selectedCurrency && $baseCurrency && $selectedCurrency->id !== $baseCurrency->id) {
$validated['original_amount'] = $validated['amount'];
$validated['amount'] = CurrencyHelper::convert(
(float) $validated['amount'],
$selectedCurrency->code,
$baseCurrency->code
);
}
// Handle receipt upload (legacy)
if ($request->hasFile('receipt')) {
$validated['receipt_path'] = $request->file('receipt')->store('receipts/petty-cash', 'public');
}
$pettyCash = PettyCash::create($validated);
// Set initial remaining = amount
$pettyCash->remaining = $pettyCash->amount;
$pettyCash->saveQuietly();
// Handle multiple file attachments
if ($request->hasFile('attachments')) {
$pettyCash->attachFiles($request->file('attachments'), 'receipt');
}
return redirect()
->route('petty-cashes.show', $pettyCash)
->with('success', __('pettyCash.created_successfully'));
}
/**
* Display the specified petty cash request.
*/
public function show(PettyCash $pettyCash)
{
$this->authorize('view', $pettyCash);
$pettyCash->load(['project', 'currency', 'requestedBy', 'approvedBy', 'expenses', 'attachments']);
return view('petty-cashes.show', compact('pettyCash'));
}
/**
* Show the form for editing the specified petty cash request.
*/
public function edit(PettyCash $pettyCash)
{
$this->authorize('update', $pettyCash);
$projects = Project::whereIn('status', ['planning', 'active'])->get();
$currencies = Currency::where('is_active', true)->get();
$baseCurrency = CurrencyHelper::getBaseCurrency();
return view('petty-cashes.form', compact('pettyCash', 'projects', 'currencies', 'baseCurrency'));
}
/**
* Update the specified petty cash request.
*/
public function update(Request $request, PettyCash $pettyCash)
{
$this->authorize('update', $pettyCash);
$validated = $request->validate([
'project_id' => 'required|exists:projects,id',
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'amount' => 'required|numeric|min:0',
'currency_id' => 'required|exists:currencies,id',
'request_date' => 'required|date',
'receipt' => 'nullable|file|mimes:jpg,jpeg,png,pdf,doc,docx|max:10240',
'attachments' => 'nullable|array|max:5',
'attachments.*' => 'file|mimes:jpg,jpeg,png,pdf,doc,docx|max:10240',
]);
// Handle currency conversion
$baseCurrency = CurrencyHelper::getBaseCurrency();
$selectedCurrency = Currency::find($validated['currency_id']);
if ($selectedCurrency && $baseCurrency && $selectedCurrency->id !== $baseCurrency->id) {
$validated['original_amount'] = $validated['amount'];
$validated['amount'] = CurrencyHelper::convert(
(float) $validated['amount'],
$selectedCurrency->code,
$baseCurrency->code
);
}
// Handle receipt upload
if ($request->hasFile('receipt')) {
// Delete old receipt if exists
if ($pettyCash->receipt_path) {
\Illuminate\Support\Facades\Storage::disk('public')->delete($pettyCash->receipt_path);
}
$validated['receipt_path'] = $request->file('receipt')->store('receipts/petty-cash', 'public');
}
$pettyCash->update($validated);
// Handle multiple file attachments
if ($request->hasFile('attachments')) {
$pettyCash->attachFiles($request->file('attachments'), 'receipt');
}
return redirect()
->route('petty-cashes.show', $pettyCash)
->with('success', __('pettyCash.updated_successfully'));
}
/**
* Remove the specified petty cash request.
*/
public function destroy(PettyCash $pettyCash)
{
$this->authorize('delete', $pettyCash);
$pettyCash->delete();
return redirect()
->route('petty-cashes.index')
->with('success', __('pettyCash.deleted_successfully'));
}
/**
* Approve a petty cash request.
*/
public function approve(PettyCash $pettyCash)
{
$this->authorize('approve', $pettyCash);
$pettyCash->update([
'status' => 'approved',
'approved_by' => Auth::id(),
'approval_date' => now(),
]);
return response()->json([
'success' => true,
'message' => __('pettyCash.approved_successfully'),
]);
}
/**
* Reject a petty cash request.
*/
public function reject(PettyCash $pettyCash)
{
$this->authorize('approve', $pettyCash);
$pettyCash->update([
'status' => 'rejected',
'approved_by' => Auth::id(),
'approval_date' => now(),
]);
return response()->json([
'success' => true,
'message' => __('pettyCash.rejected_successfully'),
]);
}
/**
* Settle a petty cash request (mark as settled after usage).
*/
public function settle(PettyCash $pettyCash)
{
$this->authorize('approve', $pettyCash);
// Update remaining before settling
$pettyCash->updateRemaining();
$pettyCash->update([
'status' => 'settled',
]);
return response()->json([
'success' => true,
'message' => __('pettyCash.settled_successfully'),
'remaining' => $pettyCash->remaining,
]);
}
}