employeeService = $employeeService; } public function index(Request $request) { $user = $request->user(); $expenses = TripExpense::with('trip') ->where('created_by', $user->id) ->orderBy('expense_date', 'desc') ->get(); return response()->json([ 'success' => true, 'data' => $expenses->map(function ($e) { $rawStatus = strtolower($e->status ?? ($e->trip->reimbursement_status ?? ($e->trip->status ?? 'pending'))); if (in_array($rawStatus, ['approved', 'paid', 'completed', 'reconciled'])) { $status = 'approved'; } elseif (in_array($rawStatus, ['rejected', 'cancelled'])) { $status = 'rejected'; } else { $status = 'pending'; } return [ 'id' => $e->id, 'item_name' => $e->description ?? $e->expense_type, 'amount' => (float)$e->amount, 'category' => $e->expense_type ?? 'General', 'purchase_date' => $e->expense_date ? $e->expense_date->format('Y-m-d') : date('Y-m-d'), 'status' => $status, 'raw_status' => $rawStatus, 'receipt_url' => $e->receipt ? asset('storage/' . $e->receipt) : null, 'notes' => $e->description, ]; }), 'message' => null, 'errors' => null, ]); } public function store(Request $request) { $request->validate([ 'item_name' => 'required|string', 'amount' => 'required|numeric|min:0', 'category' => 'nullable|string', 'purchase_date' => 'required|date', 'notes' => 'nullable|string', 'receipt' => 'nullable|file|mimes:jpg,jpeg,png,pdf|max:10240', ]); $user = $request->user(); $employee = $this->employeeService->resolveEmployee($user); $receiptPath = null; if ($request->hasFile('receipt')) { $receiptPath = $request->file('receipt')->store('receipts', 'public'); } $trip = Trip::firstOrCreate( ['employee_id' => $user->id, 'purpose' => 'General Employee Expenses'], [ 'title' => 'Reimbursement Trip for ' . $user->name, 'destination' => 'Local', 'start_date' => date('Y-m-d'), 'end_date' => date('Y-m-d'), 'status' => 'approved', 'created_by' => $user->id, ] ); $expense = TripExpense::create([ 'trip_id' => $trip->id, 'expense_type' => $request->category ?? 'General', 'expense_date' => $request->purchase_date, 'amount' => $request->amount, 'currency' => 'PHP', 'description' => $request->item_name . ($request->notes ? ' - ' . $request->notes : ''), 'receipt' => $receiptPath, 'is_reimbursable' => true, 'status' => 'pending', 'created_by' => $user->id, ]); return response()->json([ 'success' => true, 'message' => 'Reimbursement claim submitted successfully', 'data' => [ 'id' => $expense->id, 'item_name' => $request->item_name, 'amount' => (float)$expense->amount, 'category' => $expense->expense_type, 'purchase_date' => $expense->expense_date ? $expense->expense_date->format('Y-m-d') : date('Y-m-d'), 'status' => $expense->status, 'receipt_url' => $receiptPath ? asset('storage/' . $receiptPath) : null, ], 'errors' => null, ], 201); } public function updateStatus(Request $request, $id) { $request->validate([ 'status' => 'required|string|in:approved,rejected,pending,cancelled', ]); $user = $request->user(); $expense = TripExpense::find($id); if (!$expense) { return response()->json([ 'success' => false, 'data' => null, 'message' => 'Reimbursement claim not found', 'errors' => null, ], 404); } // Authorization checks if ($request->status === 'cancelled') { if ($expense->created_by !== $user->id && !in_array($user->type, ['company', 'hr', 'superadmin', 'manager'])) { return response()->json([ 'success' => false, 'data' => null, 'message' => 'Unauthorized to cancel this claim', 'errors' => null, ], 403); } } else { // approved or rejected $userType = strtolower($user->type ?? ''); $isElevatedRole = in_array($userType, ['company', 'hr', 'superadmin', 'manager']); if (!$isElevatedRole) { try { $isElevatedRole = $user->hasAnyRole(['company', 'hr', 'superadmin', 'manager']); } catch (\Throwable $e) { $isElevatedRole = false; } } $isSelf = ($expense->created_by === $user->id) && ($userType === 'employee' || !$isElevatedRole); $appCreator = User::find($expense->created_by) ?? Employee::find($expense->created_by)?->user; $appCompanyId = $appCreator ? ($appCreator->type === 'company' ? $appCreator->id : ($appCreator->created_by ?: $appCreator->id)) : null; $approverCompanyId = $user->type === 'company' ? $user->id : ($user->created_by ?: $user->id); $isSuperAdmin = in_array($userType, ['superadmin']); $isSameCompany = ($appCompanyId !== null && $approverCompanyId !== null && (int)$appCompanyId === (int)$approverCompanyId); if (!$isElevatedRole || $isSelf || (!$isSameCompany && !$isSuperAdmin)) { return response()->json([ 'success' => false, 'data' => null, 'message' => 'Unauthorized to review this reimbursement claim. Approval requires authorized manager or HR within the same organization.', 'errors' => null, ], 403); } } $expense->update(['status' => $request->status]); return response()->json([ 'success' => true, 'message' => 'Reimbursement claim status updated to ' . $request->status, 'data' => $expense, 'errors' => null, ]); } public function approve(Request $request, $id) { return $this->updateStatus($request->merge(['status' => 'approved']), $id); } public function reject(Request $request, $id) { return $this->updateStatus($request->merge(['status' => 'rejected']), $id); } }