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, ]); } }