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