migrant-web/app/Http/Controllers/Api/WorkerReviewController.php

332 lines
12 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Worker;
use App\Models\User;
use App\Models\JobPost;
use App\Models\JobApplication;
use App\Models\JobOffer;
use App\Models\EmployerReview;
use Illuminate\Support\Facades\Validator;
use Carbon\Carbon;
class WorkerReviewController extends Controller
{
/**
* Add a new review for an employer.
* POST /api/workers/reviews
*/
public function addReview(Request $request)
{
/** @var Worker $worker */
$worker = $request->attributes->get('worker');
// Sanitize job_id: if not a valid existing JobPost ID, treat it as null (job id is not needed/optional)
$jobIdInput = $request->input('job_id');
if (empty($jobIdInput) || !is_numeric($jobIdInput) || !\App\Models\JobPost::where('id', $jobIdInput)->exists()) {
$request->merge(['job_id' => null]);
}
$validator = Validator::make($request->all(), [
'employer_id' => 'required|exists:users,id',
'job_id' => 'nullable|exists:job_posts,id',
'rating' => 'required|integer|min:1|max:5',
'title' => 'nullable|string|max:255',
'comment' => 'required|string|max:1000',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation error.',
'errors' => $validator->errors()
], 422);
}
try {
$employerId = $request->employer_id;
$jobId = $request->job_id;
// 1. Validate confirmed hire and completed joining, and 3-month requirement
$hasConfirmedHire = false;
$joiningDate = null;
// Check standard JobApplication
$appQuery = JobApplication::where('worker_id', $worker->id)
->where('status', 'hired')
->whereNotNull('joining_confirmed_at')
->whereHas('jobPost', function ($query) use ($employerId, $jobId) {
$query->where('employer_id', $employerId);
if ($jobId) {
$query->where('id', $jobId);
}
});
$app = $appQuery->first();
if ($app) {
$hasConfirmedHire = true;
$joiningDate = Carbon::parse($app->joining_confirmed_at);
} else {
// Check direct JobOffer
$offerQuery = JobOffer::where('worker_id', $worker->id)
->where('employer_id', $employerId)
->where('status', 'accepted');
$offer = $offerQuery->first();
if ($offer) {
$hasConfirmedHire = true;
// For direct offers, we treat work_date or updated_at as joining date
$joiningDate = $offer->work_date ? Carbon::parse($offer->work_date) : $offer->updated_at;
}
}
if (!$hasConfirmedHire) {
return response()->json([
'success' => false,
'message' => 'You can only review employers with a confirmed hire and completed joining.'
], 403);
}
// Verify if completed configured months from joining date
$eligibilityMonths = (int) config('reminders.review_eligibility_months', 3);
if ($joiningDate->startOfDay()->diffInMonths(now()->startOfDay(), false) < $eligibilityMonths) {
return response()->json([
'success' => false,
'message' => "You can write a review only after {$eligibilityMonths} months from the joining date."
], 403);
}
// 2. Prevent duplicate reviews for the same job/employer combination
$existing = EmployerReview::where('worker_id', $worker->id)
->where('employer_id', $employerId)
->where('job_id', $jobId)
->first();
if ($existing) {
return response()->json([
'success' => false,
'message' => 'You have already submitted a review for this job/employer.'
], 422);
}
// 3. Create the review
$review = EmployerReview::create([
'worker_id' => $worker->id,
'employer_id' => $employerId,
'job_id' => $jobId,
'rating' => $request->rating,
'title' => $request->title,
'comment' => $request->comment,
]);
return response()->json([
'success' => true,
'message' => 'Review submitted successfully.',
'data' => [
'review' => $review
]
], 201);
} catch (\Exception $e) {
logger()->error('Worker Add Employer Review Failure: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'An error occurred while adding the review.',
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
], 500);
}
}
/**
* Edit/update an existing review.
* PUT /api/workers/reviews/{id}
*/
public function editReview(Request $request, $id)
{
/** @var Worker $worker */
$worker = $request->attributes->get('worker');
$validator = Validator::make($request->all(), [
'rating' => 'required|integer|min:1|max:5',
'title' => 'nullable|string|max:255',
'comment' => 'required|string|max:1000',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation error.',
'errors' => $validator->errors()
], 422);
}
try {
$review = EmployerReview::where('id', $id)
->where('worker_id', $worker->id)
->first();
if (!$review) {
return response()->json([
'success' => false,
'message' => 'Review not found.'
], 404);
}
// Check if the edit window has expired
$editPeriodDays = (int) config('reminders.review_edit_period_days', 7);
if ($review->created_at->addDays($editPeriodDays)->isPast()) {
return response()->json([
'success' => false,
'message' => "The {$editPeriodDays}-day edit window for this review has expired."
], 403);
}
$review->update([
'rating' => $request->rating,
'title' => $request->title,
'comment' => $request->comment,
]);
return response()->json([
'success' => true,
'message' => 'Review updated successfully.',
'data' => [
'review' => $review
]
], 200);
} catch (\Exception $e) {
logger()->error('Worker Edit Employer Review Failure: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'An error occurred while updating the review.',
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
], 500);
}
}
/**
* View a single review.
* GET /api/workers/reviews/{id}
*/
public function viewReview(Request $request, $id)
{
/** @var Worker $worker */
$worker = $request->attributes->get('worker');
try {
$review = EmployerReview::where('id', $id)
->where('worker_id', $worker->id)
->with(['employer.employerProfile', 'jobPost'])
->first();
if (!$review) {
return response()->json([
'success' => false,
'message' => 'Review not found.'
], 404);
}
$editPeriodDays = (int) config('reminders.review_edit_period_days', 7);
$editable = !$review->created_at->addDays($editPeriodDays)->isPast();
return response()->json([
'success' => true,
'data' => [
'review' => [
'id' => $review->id,
'employer_id' => $review->employer_id,
'employer_name' => $review->employer->name ?? 'Employer',
'job_id' => $review->job_id,
'job_title' => $review->jobPost->title ?? null,
'rating' => $review->rating,
'title' => $review->title,
'comment' => $review->comment,
'editable' => $editable,
'created_at' => $review->created_at->toIso8601String(),
'updated_at' => $review->updated_at->toIso8601String(),
]
]
], 200);
} catch (\Exception $e) {
logger()->error('Worker View Employer Review Failure: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'An error occurred while fetching the review.',
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
], 500);
}
}
/**
* Get reviews written by this worker.
* GET /api/workers/reviews
*/
public function getReviews(Request $request)
{
/** @var Worker $worker */
$worker = $request->attributes->get('worker');
try {
$page = (int) $request->input('page', 1);
$perPage = (int) $request->input('per_page', 15);
$query = EmployerReview::where('worker_id', $worker->id)
->with(['employer.employerProfile', 'jobPost'])
->latest();
$total = $query->count();
$offset = ($page - 1) * $perPage;
$reviews = $query->skip($offset)->take($perPage)->get()
->map(function ($rev) {
$editPeriodDays = (int) config('reminders.review_edit_period_days', 7);
$editable = !$rev->created_at->addDays($editPeriodDays)->isPast();
return [
'id' => $rev->id,
'employer_id' => $rev->employer_id,
'employer_name' => $rev->employer->name ?? 'Employer',
'job_id' => $rev->job_id,
'job_title' => $rev->jobPost->title ?? null,
'rating' => $rev->rating,
'title' => $rev->title,
'comment' => $rev->comment,
'editable' => $editable,
'created_at' => $rev->created_at->toIso8601String(),
'updated_at' => $rev->updated_at->toIso8601String(),
];
});
return response()->json([
'success' => true,
'data' => [
'reviews' => $reviews,
'pagination' => [
'total' => $total,
'per_page' => $perPage,
'current_page' => $page,
'last_page' => max(1, (int) ceil($total / $perPage)),
]
]
], 200);
} catch (\Exception $e) {
logger()->error('Worker Get Employer Reviews Failure: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'An error occurred while fetching reviews.',
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
], 500);
}
}
}