Files
HRM-System/app/Http/Controllers/Api/ReimbursementApiController.php

201 lines
7.3 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\TripExpense;
use App\Models\Trip;
use App\Models\User;
use App\Services\EmployeeService;
use Illuminate\Http\Request;
use Exception;
class ReimbursementApiController extends Controller
{
protected EmployeeService $employeeService;
public function __construct(EmployeeService $employeeService)
{
$this->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);
}
}