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

559 lines
23 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\EmployerProfile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class EmployerProfileController extends Controller
{
/**
* Get the authenticated employer's profile details.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function getProfile(Request $request)
{
/** @var User $employer */
$employer = $request->attributes->get('employer');
try {
$profile = $employer->employerProfile;
if (!$profile) {
$profile = EmployerProfile::create([
'user_id' => $employer->id,
'phone' => '+971 50 123 4567',
'address' => 'Dubai, UAE',
'verification_status' => 'approved',
]);
}
$employerProfile = [
'name' => $employer->name,
'email' => $employer->email,
'phone' => $profile->phone,
'address' => $profile->address,
'country' => $profile->country,
'language' => $profile->language ?? 'English',
'notifications' => (bool)($profile->notifications ?? true),
'verification_status' => $profile->verification_status ?? 'approved',
'id_number' => $profile->emirates_id_number,
'card_number' => $profile->emirates_id_card_number,
'full_name' => $profile->emirates_id_name,
'gender' => $profile->emirates_id_gender,
'emirates_id' => [
'emirates_id_number' => $profile->emirates_id_number,
'name' => $profile->emirates_id_name,
'date_of_birth' => $profile->emirates_id_dob,
'issue_date' => $profile->emirates_id_issue_date,
'expiry_date' => $profile->emirates_id_expiry,
'employer' => $profile->emirates_id_employer,
'issue_place' => $profile->emirates_id_issue_place,
'occupation' => $profile->emirates_id_occupation,
'nationality' => $profile->nationality,
'gender' => $profile->emirates_id_gender,
'card_number' => $profile->emirates_id_card_number,
'country' => 'United Arab Emirates',
],
];
return response()->json([
'success' => true,
'data' => [
'profile' => $employerProfile
]
], 200);
} catch (\Exception $e) {
logger()->error('Mobile Employer Get Profile Failure: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'An error occurred while fetching the profile.',
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
], 500);
}
}
/**
* Update the authenticated employer's profile details.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function updateProfile(Request $request)
{
/** @var User $employer */
$employer = $request->attributes->get('employer');
$sponsor = \App\Models\Sponsor::where('email', $employer->email)->first();
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255',
'email' => [
'required',
'string',
'email',
'max:255',
'unique:users,email,' . $employer->id,
$sponsor ? 'unique:sponsors,email,' . $sponsor->id : 'unique:sponsors,email',
],
'phone' => [
'required',
'string',
'max:255',
'unique:employer_profiles,phone,' . ($employer->employerProfile ? $employer->employerProfile->id : 'NULL'),
$sponsor ? 'unique:sponsors,mobile,' . $sponsor->id : 'unique:sponsors,mobile',
],
'address' => 'required|string|max:255',
'notifications' => 'nullable|boolean',
'fcm_token' => 'nullable|string|max:255',
// Emirates ID optional fields
'id_number' => 'nullable|string|max:255',
'card_number' => 'nullable|string|max:255',
'full_name' => 'nullable|string|max:255',
'date_of_birth' => 'nullable|string|max:255',
'nationality' => 'nullable|string|max:255',
'gender' => 'nullable|string|max:255',
'issue_date' => 'nullable|string|max:255',
'expiry_date' => 'nullable|string|max:255',
'occupation' => 'nullable|string|max:255',
'employer' => 'nullable|string|max:255',
'issuing_place' => 'nullable|string|max:255',
]);
$validator->after(function ($validator) use ($request) {
if ($request->has('full_name') && !$request->filled('full_name')) {
$validator->errors()->add('full_name', 'Failed to extract name from the Emirates ID. Please upload a clearer document.');
}
});
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation error.',
'errors' => $validator->errors()
], 422);
}
try {
if ($request->filled('full_name')) {
$request->merge(['name' => $request->full_name]);
}
// Validate Emirates ID immutability — once set, it cannot be changed
if ($request->filled('id_number')) {
$existingProfile = $employer->employerProfile;
if ($existingProfile && $existingProfile->emirates_id_number && $existingProfile->emirates_id_number !== $request->id_number) {
return response()->json([
'success' => false,
'message' => 'Emirates ID mismatch. You cannot change your registered Emirates ID number.',
'errors' => [
'id_number' => ['Emirates ID mismatch.']
]
], 422);
}
}
// Find old email before update to locate the correct sponsor record
$oldEmail = $employer->getOriginal('email') ?? $employer->email;
// Update user table
$userData = [
'name' => $request->name,
'email' => $request->email,
];
if ($request->has('fcm_token')) {
$userData['fcm_token'] = $request->fcm_token;
}
$employer->update($userData);
// Sync with corresponding sponsor record if found
$matchingSponsor = \App\Models\Sponsor::where('email', $oldEmail)->first();
if ($matchingSponsor) {
$sponsorData = [
'full_name' => $request->name,
'email' => $request->email,
'mobile' => $request->phone,
'address' => $request->address,
];
if ($request->has('id_number')) {
$sponsorData['emirates_id'] = $request->id_number;
}
if ($request->has('card_number')) {
$sponsorData['emirates_id_card_number'] = $request->card_number;
}
if ($request->has('full_name')) {
$sponsorData['emirates_id_name'] = $request->full_name;
}
if ($request->has('date_of_birth')) {
$sponsorData['emirates_id_dob'] = $this->normaliseDate($request->date_of_birth);
}
if ($request->has('nationality')) {
$sponsorData['nationality'] = $request->nationality;
}
if ($request->has('gender')) {
$sponsorData['emirates_id_gender'] = $request->gender;
}
if ($request->has('issue_date')) {
$sponsorData['emirates_id_issue_date'] = $this->normaliseDate($request->issue_date);
}
if ($request->has('expiry_date')) {
$sponsorData['emirates_id_expiry'] = $this->normaliseDate($request->expiry_date);
}
if ($request->has('occupation')) {
$sponsorData['emirates_id_occupation'] = $request->occupation;
}
if ($request->has('employer')) {
$sponsorData['emirates_id_employer'] = $request->employer;
}
if ($request->has('issuing_place')) {
$sponsorData['emirates_id_issue_place'] = $request->issuing_place;
}
$matchingSponsor->update($sponsorData);
}
// Update employer_profiles table
$profile = $employer->employerProfile;
if (!$profile) {
$profile = new EmployerProfile(['user_id' => $employer->id]);
}
$profile->address = $request->address;
$profile->phone = $request->phone;
$profile->notifications = $request->has('notifications') ? $request->notifications : ($profile->notifications ?? true);
if ($request->has('id_number')) {
$profile->emirates_id_number = $request->id_number;
}
if ($request->has('card_number')) {
$profile->emirates_id_card_number = $request->card_number;
}
if ($request->has('full_name')) {
$profile->emirates_id_name = $request->full_name;
}
if ($request->has('date_of_birth')) {
$profile->emirates_id_dob = $this->normaliseDate($request->date_of_birth);
}
if ($request->has('nationality')) {
$profile->nationality = $request->nationality;
}
if ($request->has('gender')) {
$profile->emirates_id_gender = $request->gender;
}
if ($request->has('issue_date')) {
$profile->emirates_id_issue_date = $this->normaliseDate($request->issue_date);
}
if ($request->has('expiry_date')) {
$profile->emirates_id_expiry = $this->normaliseDate($request->expiry_date);
}
if ($request->has('occupation')) {
$profile->emirates_id_occupation = $request->occupation;
}
if ($request->has('employer')) {
$profile->emirates_id_employer = $request->employer;
}
if ($request->has('issuing_place')) {
$profile->emirates_id_issue_place = $request->issuing_place;
}
$profile->save();
$employerProfile = [
'name' => $employer->name,
'email' => $employer->email,
'phone' => $profile->phone,
'address' => $profile->address,
'country' => $profile->country,
'language' => $profile->language,
'notifications' => (bool)$profile->notifications,
'verification_status' => $profile->verification_status ?? 'approved',
'id_number' => $profile->emirates_id_number,
'card_number' => $profile->emirates_id_card_number,
'full_name' => $profile->emirates_id_name,
'gender' => $profile->emirates_id_gender,
'emirates_id' => [
'emirates_id_number' => $profile->emirates_id_number,
'name' => $profile->emirates_id_name,
'date_of_birth' => $profile->emirates_id_dob,
'issue_date' => $profile->emirates_id_issue_date,
'expiry_date' => $profile->emirates_id_expiry,
'employer' => $profile->emirates_id_employer,
'issue_place' => $profile->emirates_id_issue_place,
'occupation' => $profile->emirates_id_occupation,
'nationality' => $profile->nationality,
'gender' => $profile->emirates_id_gender,
'card_number' => $profile->emirates_id_card_number,
'country' => 'United Arab Emirates',
],
];
return response()->json([
'success' => true,
'message' => 'Profile updated successfully.',
'data' => [
'profile' => $employerProfile
]
], 200);
} catch (\Exception $e) {
logger()->error('Mobile Employer Update Profile Failure: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'An error occurred while updating the profile.',
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
], 500);
}
}
/**
* Get dashboard analytics and summary for employer.
*/
public function getDashboard(Request $request)
{
/** @var User $employer */
$employer = $request->attributes->get('employer');
try {
$contactedWorkersCount = \App\Models\Conversation::where('employer_id', $employer->id)->count();
$totalHiredWorkers = \App\Models\JobOffer::where('employer_id', $employer->id)->where('status', 'accepted')->count() +
\App\Models\JobApplication::whereHas('jobPost', function($q) use ($employer) {
$q->where('employer_id', $employer->id);
})->where('status', 'hired')->count();
$savedCandidates = \App\Models\Shortlist::where('employer_id', $employer->id)->count();
$totalWorkers = \App\Models\Worker::with('documents')
->where('status', '!=', 'Hired')
->where('status', '!=', 'hidden')
->get()
->filter(function ($w) {
return !str_contains(strtolower($w->passport_status), 'pending');
})
->count();
// Resolve plan
$sub = \Illuminate\Support\Facades\DB::table('subscriptions')
->where('user_id', $employer->id)
->where('status', 'active')
->latest('id')
->first();
$planId = $sub ? $sub->plan_id : 'premium';
// Map raw subscription plan_id to database plan ID string
$planMap = [
'1' => 'basic',
'2' => 'premium',
'3' => 'vip',
1 => 'basic',
2 => 'premium',
3 => 'vip',
'basic' => 'basic',
'premium' => 'premium',
'enterprise' => 'vip',
'vip' => 'vip',
];
$dbPlanId = $planMap[$planId] ?? 'premium';
$associatedPlan = \App\Models\Plan::find($dbPlanId);
$planIdNumberMap = [
'basic' => 1,
'premium' => 2,
'vip' => 3,
];
$currentPlan = [
'plan_id' => $associatedPlan ? ($planIdNumberMap[$associatedPlan->id] ?? 2) : 2,
'name' => $associatedPlan ? $associatedPlan->name : (ucfirst($planId) . ' Pass'),
'status' => $sub ? $sub->status : 'active',
'starts_at' => $sub ? date('Y-m-d', strtotime($sub->starts_at)) : now()->subDays(6)->format('Y-m-d'),
'expires_at' => $sub ? date('Y-m-d', strtotime($sub->expires_at)) : now()->addDays(24)->format('Y-m-d'),
'max_contact_people' => $associatedPlan ? $associatedPlan->max_contact_people : null,
'unlimited_contacts' => $associatedPlan ? (bool)$associatedPlan->unlimited_contacts : false,
'enable_job_apply' => $associatedPlan ? (bool)$associatedPlan->enable_job_apply : false,
'features' => $associatedPlan ? $associatedPlan->features : [],
'price' => $associatedPlan ? (float)$associatedPlan->price : 0.0,
'duration' => $associatedPlan ? $associatedPlan->duration : 'Monthly',
];
// Recent Announcements / Events
$dbAnnouncements = \App\Models\Announcement::where('status', 'approved')->latest()->limit(2)->get();
$recentAnnouncements = $dbAnnouncements->map(function ($ann) {
$body = $ann->body;
$eventDate = null;
$eventTime = null;
$locationDetails = null;
$locationPin = null;
$providedItems = null;
if (strpos($ann->body, '{"type":"Charity"') === 0) {
$decoded = json_decode($ann->body, true);
if ($decoded) {
$body = $decoded['content'] ?? $ann->body;
$eventDate = $decoded['event_date'] ?? null;
$eventTime = $decoded['event_time'] ?? null;
$locationDetails = $decoded['location_details'] ?? null;
$locationPin = $decoded['location_pin'] ?? null;
$providedItems = $decoded['provided_items'] ?? null;
}
} else {
$body = $ann->body;
$eventDate = $ann->created_at->addDays(2)->format('Y-m-d');
$eventTime = '9:00 AM - 3:00 PM';
$locationDetails = 'Al Quoz Community Center, Dubai';
$locationPin = 'https://maps.google.com';
$providedItems = 'Free Medical Checks & Food Supplies';
}
return [
'id' => $ann->id,
'title' => $ann->title,
'body' => $body,
'is_charity' => true,
'event_date' => $eventDate,
'event_time' => $eventTime,
'location_details' => $locationDetails,
'location_pin' => $locationPin,
'provided_items' => $providedItems,
'created_at' => $ann->created_at->toISOString(),
'time_ago' => $ann->created_at->diffForHumans(),
];
});
return response()->json([
'success' => true,
'data' => [
'stats' => [
'contacted_workers_count' => $contactedWorkersCount,
'total_hired_workers' => $totalHiredWorkers,
'saved_candidates' => $savedCandidates,
'total_workers' => $totalWorkers,
],
'current_plan' => $currentPlan,
'recent_announcements' => $recentAnnouncements,
'recent_events' => $recentAnnouncements, // alias for ease
]
], 200);
} catch (\Exception $e) {
logger()->error('Mobile Employer Get Dashboard Failure: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'An error occurred while fetching the dashboard statistics.',
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
], 500);
}
}
/**
* Change employer's password.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function changePassword(Request $request)
{
/** @var User $employer */
$employer = $request->attributes->get('employer');
$validator = Validator::make($request->all(), [
'current_password' => 'required|string',
'new_password' => 'required|string|min:8|confirmed',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation error.',
'errors' => $validator->errors()
], 422);
}
if (!Hash::check($request->current_password, $employer->password)) {
return response()->json([
'success' => false,
'message' => 'Validation error.',
'errors' => [
'current_password' => ['The current password is incorrect.']
]
], 422);
}
try {
$hashedPassword = Hash::make($request->new_password);
// Update user table
$employer->update([
'password' => $hashedPassword
]);
// Sync with corresponding sponsor record if found
$matchingSponsor = \App\Models\Sponsor::where('email', $employer->email)->first();
if ($matchingSponsor) {
$matchingSponsor->update([
'password' => $hashedPassword
]);
}
return response()->json([
'success' => true,
'message' => 'Password changed successfully.'
], 200);
} catch (\Exception $e) {
logger()->error('Mobile Employer Change Password Failure: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'An error occurred while changing password.',
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
], 500);
}
}
/**
* Normalise date format to Y-m-d.
* E.g. "06/06/2025" -> "2025-06-06"
*/
private function normaliseDate(?string $raw): ?string
{
if (empty($raw)) {
return null;
}
$raw = trim($raw);
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $raw)) {
return $raw;
}
try {
if (preg_match('/^(\d{1,2})[\/\- ]([A-Za-z0-9]+)[\/\- ](\d{4})$/', $raw, $m)) {
$day = str_pad($m[1], 2, '0', STR_PAD_LEFT);
$month = $m[2];
$year = $m[3];
if (is_numeric($month)) {
$month = str_pad($month, 2, '0', STR_PAD_LEFT);
return "{$year}-{$month}-{$day}";
}
$ts = strtotime("{$day} {$month} {$year}");
return $ts ? date('Y-m-d', $ts) : $raw;
}
$dt = new \DateTime($raw);
return $dt->format('Y-m-d');
} catch (\Exception $e) {
return $raw;
}
}
}