Merge pull request 'mohan' (#12) from mohan into master
Reviewed-on: #12
This commit is contained in:
commit
52957149ff
@ -11,12 +11,89 @@
|
||||
class EmployerAnnouncementController extends Controller
|
||||
{
|
||||
/**
|
||||
* Get all announcements posted by this employer.
|
||||
* Get all approved announcements for employers.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function getAnnouncements(Request $request)
|
||||
{
|
||||
try {
|
||||
$page = (int)$request->input('page', 1);
|
||||
$perPage = (int)$request->input('per_page', 15);
|
||||
|
||||
$query = Announcement::with(['employer.employerProfile', 'sponsor'])->where('status', 'approved')->latest();
|
||||
$total = $query->count();
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$announcements = $query->skip($offset)->take($perPage)->get()
|
||||
->map(function ($announcement) {
|
||||
$postedBy = 'System';
|
||||
$organization = 'Migrant Support';
|
||||
|
||||
if ($announcement->sponsor_id) {
|
||||
$postedBy = $announcement->sponsor->full_name;
|
||||
$organization = $announcement->sponsor->organization_name;
|
||||
} elseif ($announcement->employer_id) {
|
||||
$postedBy = $announcement->employer->name;
|
||||
$organization = $announcement->employer->employerProfile->company_name ?? 'Employer';
|
||||
}
|
||||
|
||||
// Decode charity details if they exist in json
|
||||
$charityDetails = null;
|
||||
$content = $announcement->body;
|
||||
if (strpos($announcement->body, '{"type":"Charity"') === 0) {
|
||||
$decoded = json_decode($announcement->body, true);
|
||||
if ($decoded) {
|
||||
$charityDetails = $decoded;
|
||||
$content = $decoded['content'] ?? $announcement->body;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $announcement->id,
|
||||
'title' => $announcement->title,
|
||||
'body' => $content,
|
||||
'type' => $announcement->type,
|
||||
'employer_name' => $postedBy,
|
||||
'company_name' => $organization,
|
||||
'created_at' => $announcement->created_at->toISOString(),
|
||||
'time_ago' => $announcement->created_at->diffForHumans(),
|
||||
'charity_details' => $charityDetails,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'announcements' => $announcements,
|
||||
'pagination' => [
|
||||
'total' => $total,
|
||||
'per_page' => $perPage,
|
||||
'current_page' => $page,
|
||||
'last_page' => max(1, (int)ceil($total / $perPage)),
|
||||
]
|
||||
]
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
logger()->error('Mobile Employer Get All Announcements Failure: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'An error occurred while fetching announcements.',
|
||||
'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all announcements posted by this employer.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function getMyAnnouncements(Request $request)
|
||||
{
|
||||
/** @var User $employer */
|
||||
$employer = $request->attributes->get('employer');
|
||||
@ -57,7 +134,7 @@ public function getAnnouncements(Request $request)
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
logger()->error('Mobile Employer Get Announcements Failure: ' . $e->getMessage());
|
||||
logger()->error('Mobile Employer Get My Announcements Failure: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
|
||||
@ -309,6 +309,8 @@ public function getDashboard(Request $request)
|
||||
|
||||
$savedCandidates = \App\Models\Shortlist::where('employer_id', $employer->id)->count();
|
||||
|
||||
$totalWorkers = \App\Models\Worker::where('status', 'active')->count();
|
||||
|
||||
// Resolve plan
|
||||
$sub = \Illuminate\Support\Facades\DB::table('subscriptions')
|
||||
->where('user_id', $employer->id)
|
||||
@ -375,6 +377,7 @@ public function getDashboard(Request $request)
|
||||
'contacted_workers_count' => $contactedWorkersCount,
|
||||
'total_hired_workers' => $totalHiredWorkers,
|
||||
'saved_candidates' => $savedCandidates,
|
||||
'total_workers' => $totalWorkers,
|
||||
],
|
||||
'current_plan' => $currentPlan,
|
||||
'recent_announcements' => $recentAnnouncements,
|
||||
@ -393,4 +396,70 @@ public function getDashboard(Request $request)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ private function formatWorker(Worker $w)
|
||||
return [
|
||||
'id' => $w->id,
|
||||
'name' => $w->name,
|
||||
'email' => $w->email,
|
||||
'phone' => $w->phone,
|
||||
'nationality' => $w->nationality,
|
||||
'age' => $w->age,
|
||||
@ -57,6 +58,8 @@ private function formatWorker(Worker $w)
|
||||
'language' => $w->language,
|
||||
'languages' => $langs,
|
||||
'preferred_location' => $w->preferred_location,
|
||||
'country' => $w->country,
|
||||
'city' => $w->city,
|
||||
'area' => $w->area,
|
||||
'live_in_out' => $w->live_in_out,
|
||||
'gender' => $w->gender,
|
||||
@ -71,7 +74,6 @@ private function formatWorker(Worker $w)
|
||||
'visa_expiry_date' => $w->visa_expiry_date,
|
||||
'bio' => $w->bio,
|
||||
'photo' => $photo,
|
||||
'religion' => $w->religion,
|
||||
'rating' => $rating,
|
||||
'reviews_count' => $reviewsCount,
|
||||
'skills' => $w->skills->map(function ($s) {
|
||||
@ -88,6 +90,11 @@ private function formatWorker(Worker $w)
|
||||
];
|
||||
})->toArray(),
|
||||
'documents' => $w->documents->map(function ($doc) {
|
||||
$ocrData = $doc->ocr_data;
|
||||
if ($doc->type === 'visa' && is_array($ocrData)) {
|
||||
unset($ocrData['sponsor']);
|
||||
unset($ocrData['valid_until']);
|
||||
}
|
||||
return [
|
||||
'id' => $doc->id,
|
||||
'worker_id' => $doc->worker_id,
|
||||
@ -97,7 +104,7 @@ private function formatWorker(Worker $w)
|
||||
'expiry_date' => $doc->expiry_date,
|
||||
'ocr_accuracy' => $doc->ocr_accuracy,
|
||||
'file_path' => $doc->file_path ? url($doc->file_path) : null,
|
||||
'ocr_data' => $doc->ocr_data,
|
||||
'ocr_data' => $ocrData,
|
||||
'created_at' => $doc->created_at?->toISOString(),
|
||||
'updated_at' => $doc->updated_at?->toISOString(),
|
||||
];
|
||||
@ -976,25 +983,74 @@ public function getWorkerDetail(Request $request, $id)
|
||||
$workerProfile = [
|
||||
'id' => $w->id,
|
||||
'name' => $w->name,
|
||||
'email' => $w->email,
|
||||
'phone' => $w->phone,
|
||||
'nationality' => $w->nationality,
|
||||
'photo' => $photo,
|
||||
'emirates_id_status' => $emiratesIdStatus,
|
||||
'skills' => $mappedSkills,
|
||||
'visa_status' => $visaStatus,
|
||||
'experience' => $w->experience,
|
||||
'experience_years' => 5,
|
||||
'religion' => $w->religion,
|
||||
'languages' => $langs,
|
||||
'age' => $w->age,
|
||||
'gender' => $w->gender,
|
||||
'salary' => $w->salary,
|
||||
'experience' => $w->experience,
|
||||
'verified' => (bool)$w->verified,
|
||||
'status' => $w->status,
|
||||
'created_at' => $w->created_at?->toISOString(),
|
||||
'updated_at' => $w->updated_at?->toISOString(),
|
||||
'deleted_at' => $w->deleted_at?->toISOString(),
|
||||
'language' => $w->language,
|
||||
'languages' => $langs,
|
||||
'preferred_location' => $w->preferred_location,
|
||||
'country' => $w->country,
|
||||
'city' => $w->city,
|
||||
'area' => $w->area,
|
||||
'live_in_out' => $w->live_in_out,
|
||||
'gender' => $w->gender,
|
||||
'in_country' => (bool)$w->in_country,
|
||||
'visa_status' => $visaStatus,
|
||||
'preferred_job_type' => $preferredJobType,
|
||||
'fcm_token' => $w->fcm_token,
|
||||
'passport_status' => $w->passport_status,
|
||||
'emirates_id_status' => $emiratesIdStatus,
|
||||
'document_expiry_days' => $w->document_expiry_days,
|
||||
'document_expiry_status' => $w->document_expiry_status,
|
||||
'visa_expiry_date' => $w->visa_expiry_date,
|
||||
'bio' => $w->bio,
|
||||
'photo' => $photo,
|
||||
'rating' => $rating,
|
||||
'reviews_count' => $reviewsCount,
|
||||
'reviews' => $reviews,
|
||||
'similar_workers' => $similarWorkers,
|
||||
'conversation_id' => $conversation ? $conversation->id : null,
|
||||
'skills' => $w->skills->map(function ($s) {
|
||||
return [
|
||||
'id' => $s->id,
|
||||
'name' => $s->name,
|
||||
'created_at' => $s->created_at?->toISOString(),
|
||||
'updated_at' => $s->updated_at?->toISOString(),
|
||||
'image_path' => $s->image_path,
|
||||
'pivot' => [
|
||||
'worker_id' => $s->pivot->worker_id,
|
||||
'skill_id' => $s->pivot->skill_id,
|
||||
]
|
||||
];
|
||||
})->toArray(),
|
||||
'documents' => $w->documents->map(function ($doc) {
|
||||
$ocrData = $doc->ocr_data;
|
||||
if ($doc->type === 'visa' && is_array($ocrData)) {
|
||||
unset($ocrData['sponsor']);
|
||||
unset($ocrData['valid_until']);
|
||||
}
|
||||
return [
|
||||
'id' => $doc->id,
|
||||
'worker_id' => $doc->worker_id,
|
||||
'type' => $doc->type,
|
||||
'number' => $doc->number,
|
||||
'issue_date' => $doc->issue_date,
|
||||
'expiry_date' => $doc->expiry_date,
|
||||
'ocr_accuracy' => $doc->ocr_accuracy,
|
||||
'file_path' => $doc->file_path ? url($doc->file_path) : null,
|
||||
'ocr_data' => $ocrData,
|
||||
'created_at' => $doc->created_at?->toISOString(),
|
||||
'updated_at' => $doc->updated_at?->toISOString(),
|
||||
];
|
||||
})->toArray(),
|
||||
];
|
||||
|
||||
return response()->json([
|
||||
|
||||
@ -280,12 +280,17 @@ public function reportFromEmployer(Request $request)
|
||||
*/
|
||||
public function getReasonsForWorker(Request $request)
|
||||
{
|
||||
$type = $request->query('type'); // Chat or Review
|
||||
$type = $request->query('type'); // Chat or Support
|
||||
|
||||
$query = DB::table('report_reasons')->where('status', 'Active');
|
||||
|
||||
if ($type && in_array($type, ['Chat', 'Review'])) {
|
||||
$query->whereIn('type', [$type, 'Both']);
|
||||
if ($type && in_array(strtolower($type), ['chat', 'support'])) {
|
||||
$mappedType = ucfirst(strtolower($type));
|
||||
if ($mappedType === 'Support') {
|
||||
$query->where('type', 'Support');
|
||||
} else {
|
||||
$query->whereIn('type', [$mappedType, 'Both']);
|
||||
}
|
||||
}
|
||||
|
||||
$reasons = $query->orderBy('id', 'asc')->get(['id', 'reason', 'type']);
|
||||
@ -302,12 +307,17 @@ public function getReasonsForWorker(Request $request)
|
||||
*/
|
||||
public function getReasonsForEmployer(Request $request)
|
||||
{
|
||||
$type = $request->query('type'); // Chat or Review
|
||||
$type = $request->query('type'); // Chat or Support
|
||||
|
||||
$query = DB::table('report_reasons')->where('status', 'Active');
|
||||
|
||||
if ($type && in_array($type, ['Chat', 'Review'])) {
|
||||
$query->whereIn('type', [$type, 'Both']);
|
||||
if ($type && in_array(strtolower($type), ['chat', 'support'])) {
|
||||
$mappedType = ucfirst(strtolower($type));
|
||||
if ($mappedType === 'Support') {
|
||||
$query->where('type', 'Support');
|
||||
} else {
|
||||
$query->whereIn('type', [$mappedType, 'Both']);
|
||||
}
|
||||
}
|
||||
|
||||
$reasons = $query->orderBy('id', 'asc')->get(['id', 'reason', 'type']);
|
||||
|
||||
@ -326,4 +326,72 @@ public function getProfile(Request $request)
|
||||
]
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change sponsor's password.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function changePassword(Request $request)
|
||||
{
|
||||
/** @var Sponsor $sponsor */
|
||||
$sponsor = $request->attributes->get('sponsor');
|
||||
|
||||
$validator = \Illuminate\Support\Facades\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 (!\Illuminate\Support\Facades\Hash::check($request->current_password, $sponsor->password)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Validation error.',
|
||||
'errors' => [
|
||||
'current_password' => ['The current password is incorrect.']
|
||||
]
|
||||
], 422);
|
||||
}
|
||||
|
||||
try {
|
||||
$hashedPassword = \Illuminate\Support\Facades\Hash::make($request->new_password);
|
||||
|
||||
// Update sponsor table
|
||||
$sponsor->update([
|
||||
'password' => $hashedPassword
|
||||
]);
|
||||
|
||||
// Sync with corresponding employer user record if found
|
||||
$matchingEmployer = \App\Models\User::where('email', $sponsor->email)
|
||||
->where('role', 'employer')
|
||||
->first();
|
||||
if ($matchingEmployer) {
|
||||
$matchingEmployer->update([
|
||||
'password' => $hashedPassword
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Password changed successfully.'
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
logger()->error('Mobile Sponsor 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -669,6 +669,61 @@ public function getDashboard(Request $request)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change worker's password.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function changePassword(Request $request)
|
||||
{
|
||||
/** @var Worker $worker */
|
||||
$worker = $request->attributes->get('worker');
|
||||
|
||||
$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 (!\Illuminate\Support\Facades\Hash::check($request->current_password, $worker->password)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Validation error.',
|
||||
'errors' => [
|
||||
'current_password' => ['The current password is incorrect.']
|
||||
]
|
||||
], 422);
|
||||
}
|
||||
|
||||
try {
|
||||
$worker->update([
|
||||
'password' => \Illuminate\Support\Facades\Hash::make($request->new_password)
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Password changed successfully.'
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
logger()->error('Mobile Worker 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* E.g. "14/05/1990" -> "1990-05-14"
|
||||
*/
|
||||
|
||||
@ -370,7 +370,6 @@ public function show($id)
|
||||
'skills' => $mappedSkills,
|
||||
'visa_status' => $visaStatus,
|
||||
'experience' => $w->experience,
|
||||
'experience_years' => 5,
|
||||
'salary' => (int) $w->salary,
|
||||
'religion' => $w->religion,
|
||||
'languages' => $langs,
|
||||
|
||||
@ -2696,12 +2696,14 @@
|
||||
"name": "type",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Optional type to filter reasons (Chat or Review). If not provided, returns all active reasons.",
|
||||
"description": "Optional type to filter reasons (Chat or Support). Case-insensitive.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Chat",
|
||||
"Review"
|
||||
"Support",
|
||||
"chat",
|
||||
"support"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -3527,7 +3529,91 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of posted charity events retrieved successfully."
|
||||
"description": "List of posted charity events retrieved successfully.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"announcements": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"example": "Holiday Notice"
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"example": "Office will be closed tomorrow."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "info"
|
||||
},
|
||||
"employer_name": {
|
||||
"type": "string",
|
||||
"example": "Employer One"
|
||||
},
|
||||
"company_name": {
|
||||
"type": "string",
|
||||
"example": "Elite Services"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"example": "2026-06-23T10:13:17.000000Z"
|
||||
},
|
||||
"time_ago": {
|
||||
"type": "string",
|
||||
"example": "2 hours ago"
|
||||
},
|
||||
"charity_details": {
|
||||
"type": "object",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pagination": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"per_page": {
|
||||
"type": "integer",
|
||||
"example": 15
|
||||
},
|
||||
"current_page": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"last_page": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -3628,7 +3714,85 @@
|
||||
"description": "Retrieves stats including contacted workers count, total hired workers, saved candidates, current plan details, and recent announcements or events.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Employer dashboard stats retrieved successfully."
|
||||
"description": "Employer dashboard stats retrieved successfully.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"stats": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"contacted_workers_count": {
|
||||
"type": "integer",
|
||||
"example": 0
|
||||
},
|
||||
"total_hired_workers": {
|
||||
"type": "integer",
|
||||
"example": 0
|
||||
},
|
||||
"saved_candidates": {
|
||||
"type": "integer",
|
||||
"example": 0
|
||||
},
|
||||
"total_workers": {
|
||||
"type": "integer",
|
||||
"example": 15
|
||||
}
|
||||
}
|
||||
},
|
||||
"current_plan": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"plan_id": {
|
||||
"type": "string",
|
||||
"example": "premium"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Premium Pass"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "active"
|
||||
},
|
||||
"starts_at": {
|
||||
"type": "string",
|
||||
"format": "date",
|
||||
"example": "2026-06-23"
|
||||
},
|
||||
"expires_at": {
|
||||
"type": "string",
|
||||
"format": "date",
|
||||
"example": "2026-07-23"
|
||||
}
|
||||
}
|
||||
},
|
||||
"recent_announcements": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"recent_events": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4198,105 +4362,13 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"worker": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Worker"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Jane Doe"
|
||||
},
|
||||
"nationality": {
|
||||
"type": "string",
|
||||
"example": "Filipino"
|
||||
},
|
||||
"photo": {
|
||||
"type": "string",
|
||||
"example": "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=200"
|
||||
},
|
||||
"emirates_id_status": {
|
||||
"type": "string",
|
||||
"example": "Passport Verified",
|
||||
"description": "Deprecated - Use passport_status instead"
|
||||
},
|
||||
"passport_status": {
|
||||
"type": "string",
|
||||
"example": "Passport Verified"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"example": "Domestic Worker"
|
||||
},
|
||||
"skills": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"childcare",
|
||||
"cooking"
|
||||
]
|
||||
},
|
||||
"availability_status": {
|
||||
"type": "string",
|
||||
"example": "Active"
|
||||
},
|
||||
"visa_status": {
|
||||
"type": "string",
|
||||
"example": "Residence Visa"
|
||||
},
|
||||
"experience": {
|
||||
"type": "string",
|
||||
"example": "5 Years"
|
||||
},
|
||||
"experience_years": {
|
||||
"type": "integer",
|
||||
"example": 5
|
||||
},
|
||||
"salary": {
|
||||
"type": "integer",
|
||||
"example": 2500
|
||||
},
|
||||
"religion": {
|
||||
"type": "string",
|
||||
"example": "Christian"
|
||||
},
|
||||
"languages": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"English",
|
||||
"Tagalog"
|
||||
]
|
||||
},
|
||||
"age": {
|
||||
"type": "integer",
|
||||
"example": 28
|
||||
},
|
||||
"verified": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"preferred_job_type": {
|
||||
"type": "string",
|
||||
"example": "live-in"
|
||||
},
|
||||
"bio": {
|
||||
"type": "string",
|
||||
"example": "Experienced and caring domestic worker specialing in childcare and housekeeping..."
|
||||
},
|
||||
"rating": {
|
||||
"type": "number",
|
||||
"example": 4.3
|
||||
},
|
||||
"reviews_count": {
|
||||
"type": "integer",
|
||||
"example": 6
|
||||
},
|
||||
"reviews": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -4316,6 +4388,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5062,12 +5136,14 @@
|
||||
"name": "type",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Optional type to filter reasons (Chat or Review). If not provided, returns all active reasons.",
|
||||
"description": "Optional type to filter reasons (Chat or Support). Case-insensitive.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Chat",
|
||||
"Review"
|
||||
"Support",
|
||||
"chat",
|
||||
"support"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -5605,6 +5681,422 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/employers/my-announcements": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Employer/CharityEvents"
|
||||
],
|
||||
"summary": "Get My Posted Announcements/Charity Events (Employer)",
|
||||
"description": "Retrieves the list of announcements and charity events posted specifically by the logged-in employer.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Page number.",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "per_page",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Number of items per page.",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 15
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of my posted announcements retrieved successfully.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"announcements": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"example": "Holiday Notice"
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"example": "Office will be closed tomorrow."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "info"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "pending"
|
||||
},
|
||||
"remarks": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"example": "2026-06-23T10:13:17.000000Z"
|
||||
},
|
||||
"time_ago": {
|
||||
"type": "string",
|
||||
"example": "2 hours ago"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pagination": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"per_page": {
|
||||
"type": "integer",
|
||||
"example": 15
|
||||
},
|
||||
"current_page": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"last_page": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workers/change-password": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Worker/Profile"
|
||||
],
|
||||
"summary": "Change Password",
|
||||
"description": "Securely changes the user's password by validating the current password and confirming the new password.",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current_password",
|
||||
"new_password",
|
||||
"new_password_confirmation"
|
||||
],
|
||||
"properties": {
|
||||
"current_password": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"description": "The current login password. (REQUIRED)",
|
||||
"example": "OldPassword@123"
|
||||
},
|
||||
"new_password": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"description": "The new secure password (minimum 8 characters). (REQUIRED)",
|
||||
"example": "NewPassword@123"
|
||||
},
|
||||
"new_password_confirmation": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"description": "Confirmation of the new password. (REQUIRED)",
|
||||
"example": "NewPassword@123"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Password changed successfully.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Password changed successfully."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation error (e.g. invalid current password, password too short, or mismatch).",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Validation error."
|
||||
},
|
||||
"errors": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"current_password": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"The current password is incorrect."
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/employers/change-password": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Employer/Profile"
|
||||
],
|
||||
"summary": "Change Password",
|
||||
"description": "Securely changes the user's password by validating the current password and confirming the new password.",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current_password",
|
||||
"new_password",
|
||||
"new_password_confirmation"
|
||||
],
|
||||
"properties": {
|
||||
"current_password": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"description": "The current login password. (REQUIRED)",
|
||||
"example": "OldPassword@123"
|
||||
},
|
||||
"new_password": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"description": "The new secure password (minimum 8 characters). (REQUIRED)",
|
||||
"example": "NewPassword@123"
|
||||
},
|
||||
"new_password_confirmation": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"description": "Confirmation of the new password. (REQUIRED)",
|
||||
"example": "NewPassword@123"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Password changed successfully.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Password changed successfully."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation error (e.g. invalid current password, password too short, or mismatch).",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Validation error."
|
||||
},
|
||||
"errors": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"current_password": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"The current password is incorrect."
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sponsors/change-password": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Sponsor"
|
||||
],
|
||||
"summary": "Change Password",
|
||||
"description": "Securely changes the user's password by validating the current password and confirming the new password.",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current_password",
|
||||
"new_password",
|
||||
"new_password_confirmation"
|
||||
],
|
||||
"properties": {
|
||||
"current_password": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"description": "The current login password. (REQUIRED)",
|
||||
"example": "OldPassword@123"
|
||||
},
|
||||
"new_password": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"description": "The new secure password (minimum 8 characters). (REQUIRED)",
|
||||
"example": "NewPassword@123"
|
||||
},
|
||||
"new_password_confirmation": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"description": "Confirmation of the new password. (REQUIRED)",
|
||||
"example": "NewPassword@123"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Password changed successfully.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Password changed successfully."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation error (e.g. invalid current password, password too short, or mismatch).",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Validation error."
|
||||
},
|
||||
"errors": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"current_password": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"The current password is incorrect."
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
@ -5738,10 +6230,6 @@
|
||||
"type": "string",
|
||||
"example": "4 Years"
|
||||
},
|
||||
"religion": {
|
||||
"type": "string",
|
||||
"example": "Muslim"
|
||||
},
|
||||
"bio": {
|
||||
"type": "string",
|
||||
"example": "Certified infant caregiver and housekeeper with excellent cooking skills."
|
||||
|
||||
@ -75,6 +75,7 @@
|
||||
Route::post('/workers/profile/mark-hired', [WorkerProfileController::class, 'markHired']);
|
||||
Route::get('/workers/dashboard/views', [WorkerProfileController::class, 'getProfileViews']);
|
||||
Route::get('/workers/dashboard', [WorkerProfileController::class, 'getDashboard']);
|
||||
Route::post('/workers/change-password', [WorkerProfileController::class, 'changePassword']);
|
||||
|
||||
|
||||
|
||||
@ -110,6 +111,7 @@
|
||||
Route::get('/employers/profile', [EmployerProfileController::class, 'getProfile']);
|
||||
Route::post('/employers/profile/update', [EmployerProfileController::class, 'updateProfile']);
|
||||
Route::get('/employers/dashboard', [EmployerProfileController::class, 'getDashboard']);
|
||||
Route::post('/employers/change-password', [EmployerProfileController::class, 'changePassword']);
|
||||
|
||||
// Messaging Management
|
||||
Route::get('/employers/conversations', [EmployerMessageController::class, 'getConversations']);
|
||||
@ -119,6 +121,7 @@
|
||||
|
||||
// Announcement Management
|
||||
Route::get('/employers/announcements', [EmployerAnnouncementController::class, 'getAnnouncements']);
|
||||
Route::get('/employers/my-announcements', [EmployerAnnouncementController::class, 'getMyAnnouncements']);
|
||||
Route::post('/employers/announcements', [EmployerAnnouncementController::class, 'createAnnouncement']);
|
||||
Route::delete('/employers/announcements/{id}', [EmployerAnnouncementController::class, 'deleteAnnouncement']);
|
||||
|
||||
@ -159,4 +162,5 @@
|
||||
Route::get('/sponsors/dashboard', [SponsorController::class, 'getDashboard']);
|
||||
Route::get('/sponsors/charity-events', [SponsorController::class, 'getCharityEvents']);
|
||||
Route::post('/sponsors/charity-events', [SponsorController::class, 'postCharityEvent']);
|
||||
Route::post('/sponsors/change-password', [SponsorController::class, 'changePassword']);
|
||||
});
|
||||
|
||||
@ -103,9 +103,9 @@ public function test_worker_can_get_announcements()
|
||||
}
|
||||
|
||||
/**
|
||||
* Test employer can get only their posted announcements.
|
||||
* Test employer can get only their posted announcements via my-announcements.
|
||||
*/
|
||||
public function test_employer_can_get_posted_announcements()
|
||||
public function test_employer_can_get_my_announcements()
|
||||
{
|
||||
// Announcement by this employer
|
||||
Announcement::create([
|
||||
@ -131,13 +131,66 @@ public function test_employer_can_get_posted_announcements()
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->employerToken,
|
||||
])->getJson('/api/employers/announcements');
|
||||
])->getJson('/api/employers/my-announcements');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$this->assertCount(1, $response->json('data.announcements'));
|
||||
$this->assertEquals('Employer Ann', $response->json('data.announcements.0.title'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test employer can get all approved announcements.
|
||||
*/
|
||||
public function test_employer_can_get_all_approved_announcements()
|
||||
{
|
||||
// Approved announcement by this employer
|
||||
Announcement::create([
|
||||
'title' => 'Approved Ann 1',
|
||||
'body' => 'Message 1',
|
||||
'type' => 'info',
|
||||
'employer_id' => $this->employer->id,
|
||||
'status' => 'approved',
|
||||
]);
|
||||
|
||||
// Pending announcement by this employer
|
||||
Announcement::create([
|
||||
'title' => 'Pending Ann 1',
|
||||
'body' => 'Message 2',
|
||||
'type' => 'info',
|
||||
'employer_id' => $this->employer->id,
|
||||
'status' => 'pending',
|
||||
]);
|
||||
|
||||
// Approved announcement by another sponsor
|
||||
$sponsor = \App\Models\Sponsor::create([
|
||||
'full_name' => 'Sponsor Name',
|
||||
'email' => 'sponsor@example.com',
|
||||
'mobile' => '+971501112233',
|
||||
'organization_name' => 'Charity Org',
|
||||
'address' => 'Dubai',
|
||||
'role' => 'sponsor',
|
||||
'password' => bcrypt('password'),
|
||||
]);
|
||||
Announcement::create([
|
||||
'title' => 'Approved Ann 2',
|
||||
'body' => 'Message 3',
|
||||
'type' => 'info',
|
||||
'sponsor_id' => $sponsor->id,
|
||||
'status' => 'approved',
|
||||
]);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->employerToken,
|
||||
])->getJson('/api/employers/announcements');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$this->assertCount(2, $response->json('data.announcements'));
|
||||
$titles = collect($response->json('data.announcements'))->pluck('title');
|
||||
$this->assertTrue($titles->contains('Approved Ann 1'));
|
||||
$this->assertTrue($titles->contains('Approved Ann 2'));
|
||||
$this->assertFalse($titles->contains('Pending Ann 1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test employer can create a new announcement.
|
||||
*/
|
||||
|
||||
@ -241,4 +241,104 @@ public function test_employer_dashboard_returns_only_two_charity_drives()
|
||||
$this->assertFalse($titles->contains('Charity Drive 1'));
|
||||
$this->assertFalse($titles->contains('Pending Charity Drive'));
|
||||
}
|
||||
|
||||
public function test_employer_dashboard_returns_total_workers_active()
|
||||
{
|
||||
// Delete any existing workers seeded or created to make count precise
|
||||
\App\Models\Worker::query()->delete();
|
||||
|
||||
// Create 3 active workers
|
||||
\App\Models\Worker::create([
|
||||
'name' => 'Active 1', 'email' => 'a1@test.com', 'phone' => '+971501110001',
|
||||
'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian',
|
||||
'preferred_location' => 'Dubai', 'age' => 25, 'salary' => 1500, 'experience' => '3 Years',
|
||||
'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker',
|
||||
]);
|
||||
\App\Models\Worker::create([
|
||||
'name' => 'Active 2', 'email' => 'a2@test.com', 'phone' => '+971501110002',
|
||||
'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian',
|
||||
'preferred_location' => 'Dubai', 'age' => 26, 'salary' => 1500, 'experience' => '3 Years',
|
||||
'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker',
|
||||
]);
|
||||
\App\Models\Worker::create([
|
||||
'name' => 'Active 3', 'email' => 'a3@test.com', 'phone' => '+971501110003',
|
||||
'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian',
|
||||
'preferred_location' => 'Dubai', 'age' => 27, 'salary' => 1500, 'experience' => '3 Years',
|
||||
'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker',
|
||||
]);
|
||||
|
||||
// Create 2 inactive/hidden workers
|
||||
\App\Models\Worker::create([
|
||||
'name' => 'Inactive 1', 'email' => 'i1@test.com', 'phone' => '+971502220001',
|
||||
'password' => bcrypt('password'), 'status' => 'hidden', 'nationality' => 'Indian',
|
||||
'preferred_location' => 'Dubai', 'age' => 28, 'salary' => 1500, 'experience' => '3 Years',
|
||||
'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker',
|
||||
]);
|
||||
\App\Models\Worker::create([
|
||||
'name' => 'Inactive 2', 'email' => 'i2@test.com', 'phone' => '+971502220002',
|
||||
'password' => bcrypt('password'), 'status' => 'Hired', 'nationality' => 'Indian',
|
||||
'preferred_location' => 'Dubai', 'age' => 29, 'salary' => 1500, 'experience' => '3 Years',
|
||||
'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker',
|
||||
]);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->token,
|
||||
])->getJson('/api/employers/dashboard');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$this->assertEquals(3, $response->json('data.stats.total_workers'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test employer can change password successfully.
|
||||
*/
|
||||
public function test_employer_can_change_password()
|
||||
{
|
||||
// 1. Create a matching sponsor
|
||||
$sponsor = \App\Models\Sponsor::create([
|
||||
'full_name' => 'Employer One',
|
||||
'email' => $this->employer->email,
|
||||
'mobile' => '+971509990011',
|
||||
'organization_name' => 'Elite Services',
|
||||
'address' => 'Dubai',
|
||||
'role' => 'sponsor',
|
||||
'password' => bcrypt('Password@123'),
|
||||
]);
|
||||
|
||||
// Verify initial password hash is set
|
||||
$this->employer->update([
|
||||
'password' => bcrypt('Password@123')
|
||||
]);
|
||||
|
||||
// 2. Try with wrong current password
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->token,
|
||||
])->postJson('/api/employers/change-password', [
|
||||
'current_password' => 'WrongPassword',
|
||||
'new_password' => 'NewPassword@123',
|
||||
'new_password_confirmation' => 'NewPassword@123',
|
||||
]);
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonValidationErrors(['current_password']);
|
||||
|
||||
// 3. Try with correct current password
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->token,
|
||||
])->postJson('/api/employers/change-password', [
|
||||
'current_password' => 'Password@123',
|
||||
'new_password' => 'NewPassword@123',
|
||||
'new_password_confirmation' => 'NewPassword@123',
|
||||
]);
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
'success' => true,
|
||||
'message' => 'Password changed successfully.'
|
||||
]);
|
||||
|
||||
// Verify password is updated for both User and Sponsor
|
||||
$this->assertTrue(\Illuminate\Support\Facades\Hash::check('NewPassword@123', $this->employer->fresh()->password));
|
||||
$this->assertTrue(\Illuminate\Support\Facades\Hash::check('NewPassword@123', $sponsor->fresh()->password));
|
||||
}
|
||||
}
|
||||
|
||||
@ -532,4 +532,87 @@ public function test_worker_list_api_has_full_details_and_pagination()
|
||||
$this->assertEquals('passport', $matchingWorker['documents'][0]['type']);
|
||||
$this->assertEquals('visa', $matchingWorker['documents'][1]['type']);
|
||||
}
|
||||
|
||||
public function test_worker_detail_api_has_full_details_without_religion_and_cleaned_ocr_data()
|
||||
{
|
||||
$worker = Worker::create([
|
||||
'name' => 'Detail Worker Test',
|
||||
'email' => 'detail_test@example.com',
|
||||
'phone' => '+971509999777',
|
||||
'password' => bcrypt('password'),
|
||||
'nationality' => 'Indian',
|
||||
'verified' => true,
|
||||
'status' => 'active',
|
||||
'preferred_location' => 'Dubai Marina',
|
||||
'live_in_out' => 'live_out',
|
||||
'gender' => 'male',
|
||||
'in_country' => true,
|
||||
'visa_status' => 'Tourist Visa',
|
||||
'preferred_job_type' => 'full-time',
|
||||
'fcm_token' => 'fcm_token_example',
|
||||
'age' => 36,
|
||||
'salary' => 1500.00,
|
||||
'experience' => '4 Years',
|
||||
'language' => 'English, Arabic',
|
||||
'availability' => 'Immediate',
|
||||
'religion' => 'Christian',
|
||||
'bio' => 'Experienced helper',
|
||||
]);
|
||||
|
||||
$doc1 = WorkerDocument::create([
|
||||
'worker_id' => $worker->id,
|
||||
'type' => 'passport',
|
||||
'number' => 'YB98239279327',
|
||||
'issue_date' => '2017-07-05',
|
||||
'expiry_date' => '2022-07-05',
|
||||
'ocr_accuracy' => 99,
|
||||
'ocr_data' => ['authority' => 'FMP LLC'],
|
||||
]);
|
||||
|
||||
$doc2 = WorkerDocument::create([
|
||||
'worker_id' => $worker->id,
|
||||
'type' => 'visa',
|
||||
'number' => '343434',
|
||||
'issue_date' => '2022-02-16',
|
||||
'expiry_date' => '2024-02-15',
|
||||
'ocr_accuracy' => 99,
|
||||
'ocr_data' => [
|
||||
'sponsor' => 'some sponsor',
|
||||
'sponsor_name' => 'some sponsor name',
|
||||
'valid_until' => '2024-02-15',
|
||||
'expiry_date' => '2024-02-15'
|
||||
],
|
||||
]);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->token,
|
||||
])->getJson('/api/employers/workers/' . $worker->id);
|
||||
|
||||
$response->assertStatus(200);
|
||||
$wData = $response->json('data.worker');
|
||||
|
||||
$this->assertNotNull($wData);
|
||||
$this->assertEquals('+971509999777', $wData['phone']);
|
||||
$this->assertEquals('detail_test@example.com', $wData['email']);
|
||||
$this->assertEquals('1500.00', $wData['salary']);
|
||||
$this->assertEquals('active', $wData['status']);
|
||||
$this->assertEquals('English, Arabic', $wData['language']);
|
||||
$this->assertEquals('fcm_token_example', $wData['fcm_token']);
|
||||
$this->assertEquals('2024-02-15', $wData['visa_expiry_date']);
|
||||
|
||||
// Assert religion is removed
|
||||
$this->assertArrayNotHasKey('religion', $wData);
|
||||
|
||||
// Check documents object array
|
||||
$this->assertIsArray($wData['documents']);
|
||||
$this->assertCount(2, $wData['documents']);
|
||||
|
||||
// Assert visa ocr_data is cleaned up
|
||||
$visaDoc = collect($wData['documents'])->firstWhere('type', 'visa');
|
||||
$this->assertNotNull($visaDoc);
|
||||
$this->assertArrayNotHasKey('sponsor', $visaDoc['ocr_data']);
|
||||
$this->assertArrayNotHasKey('valid_until', $visaDoc['ocr_data']);
|
||||
$this->assertEquals('some sponsor name', $visaDoc['ocr_data']['sponsor_name']);
|
||||
$this->assertEquals('2024-02-15', $visaDoc['ocr_data']['expiry_date']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,6 +276,7 @@ public function test_get_report_reasons_filtering()
|
||||
['reason' => 'Review Abuse', 'status' => 'Active', 'type' => 'Review'],
|
||||
['reason' => 'General Impersonation', 'status' => 'Active', 'type' => 'Both'],
|
||||
['reason' => 'Inactive Reason', 'status' => 'Inactive', 'type' => 'Both'],
|
||||
['reason' => 'Billing Issue', 'status' => 'Active', 'type' => 'Support'],
|
||||
]);
|
||||
|
||||
// 1. Worker retrieves all Active reasons
|
||||
@ -284,33 +285,43 @@ public function test_get_report_reasons_filtering()
|
||||
])->getJson('/api/workers/report-reasons');
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJsonCount(3, 'reasons')
|
||||
->assertJsonCount(4, 'reasons')
|
||||
->assertJsonFragment(['reason' => 'Chat Spam'])
|
||||
->assertJsonFragment(['reason' => 'Review Abuse'])
|
||||
->assertJsonFragment(['reason' => 'General Impersonation'])
|
||||
->assertJsonFragment(['reason' => 'Billing Issue'])
|
||||
->assertJsonMissing(['reason' => 'Inactive Reason']);
|
||||
|
||||
// 2. Worker retrieves Chat reasons only
|
||||
// 2. Worker retrieves Chat reasons only (case-insensitive)
|
||||
$responseChat = $this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->workerToken,
|
||||
])->getJson('/api/workers/report-reasons?type=Chat');
|
||||
])->getJson('/api/workers/report-reasons?type=chat');
|
||||
|
||||
$responseChat->assertStatus(200)
|
||||
->assertJsonCount(2, 'reasons')
|
||||
->assertJsonFragment(['reason' => 'Chat Spam'])
|
||||
->assertJsonFragment(['reason' => 'General Impersonation'])
|
||||
->assertJsonMissing(['reason' => 'Review Abuse']);
|
||||
->assertJsonMissing(['reason' => 'Review Abuse'])
|
||||
->assertJsonMissing(['reason' => 'Billing Issue']);
|
||||
|
||||
// 3. Worker retrieves Review reasons only
|
||||
// 3. Worker retrieves Review reasons (unsupported type, ignored)
|
||||
$responseReview = $this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->workerToken,
|
||||
])->getJson('/api/workers/report-reasons?type=Review');
|
||||
|
||||
$responseReview->assertStatus(200)
|
||||
->assertJsonCount(2, 'reasons')
|
||||
->assertJsonFragment(['reason' => 'Review Abuse'])
|
||||
->assertJsonFragment(['reason' => 'General Impersonation'])
|
||||
->assertJsonMissing(['reason' => 'Chat Spam']);
|
||||
->assertJsonCount(4, 'reasons');
|
||||
|
||||
// 4. Worker retrieves Support reasons only (case-insensitive)
|
||||
$responseSupport = $this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->workerToken,
|
||||
])->getJson('/api/workers/report-reasons?type=support');
|
||||
|
||||
$responseSupport->assertStatus(200)
|
||||
->assertJsonCount(1, 'reasons')
|
||||
->assertJsonFragment(['reason' => 'Billing Issue'])
|
||||
->assertJsonMissing(['reason' => 'Chat Spam'])
|
||||
->assertJsonMissing(['reason' => 'General Impersonation']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -603,4 +603,58 @@ public function test_sponsor_forgot_password_user_not_found()
|
||||
'message' => 'user not found this email id',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test sponsor can change password successfully.
|
||||
*/
|
||||
public function test_sponsor_can_change_password()
|
||||
{
|
||||
$sponsor = Sponsor::create([
|
||||
'full_name' => 'Sponsor Changer',
|
||||
'email' => 'changer.sponsor@example.com',
|
||||
'mobile' => '+971509997788',
|
||||
'password' => Hash::make('Password@123'),
|
||||
'api_token' => 'sponsor-changer-token',
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
// Sync with corresponding employer user record to test sync
|
||||
$user = User::create([
|
||||
'name' => 'Sponsor Changer',
|
||||
'email' => 'changer.sponsor@example.com',
|
||||
'password' => Hash::make('Password@123'),
|
||||
'role' => 'employer',
|
||||
]);
|
||||
|
||||
// 1. Try with wrong current password
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer sponsor-changer-token',
|
||||
])->postJson('/api/sponsors/change-password', [
|
||||
'current_password' => 'WrongPassword',
|
||||
'new_password' => 'NewPassword@123',
|
||||
'new_password_confirmation' => 'NewPassword@123',
|
||||
]);
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonValidationErrors(['current_password']);
|
||||
|
||||
// 2. Try with correct current password
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer sponsor-changer-token',
|
||||
])->postJson('/api/sponsors/change-password', [
|
||||
'current_password' => 'Password@123',
|
||||
'new_password' => 'NewPassword@123',
|
||||
'new_password_confirmation' => 'NewPassword@123',
|
||||
]);
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
'success' => true,
|
||||
'message' => 'Password changed successfully.'
|
||||
]);
|
||||
|
||||
// Verify password is updated for both Sponsor and User
|
||||
$this->assertTrue(Hash::check('NewPassword@123', $sponsor->fresh()->password));
|
||||
$this->assertTrue(Hash::check('NewPassword@123', $user->fresh()->password));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1540,4 +1540,55 @@ public function test_profile_response_does_not_contain_deprecated_fields()
|
||||
$this->assertArrayNotHasKey('city', $json);
|
||||
$this->assertArrayNotHasKey('area', $json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test worker can change password successfully.
|
||||
*/
|
||||
public function test_worker_can_change_password()
|
||||
{
|
||||
$worker = Worker::create([
|
||||
'name' => 'Password Changer',
|
||||
'email' => 'changer@example.com',
|
||||
'phone' => '+971509990099',
|
||||
'language' => 'HI',
|
||||
'password' => bcrypt('OldPassword@123'),
|
||||
'nationality' => 'Indian',
|
||||
'age' => 25,
|
||||
'salary' => 1500,
|
||||
'availability' => 'Immediate',
|
||||
'experience' => 'None',
|
||||
'religion' => 'Christian',
|
||||
'bio' => 'Some Bio',
|
||||
'api_token' => 'changer-token',
|
||||
]);
|
||||
|
||||
// 1. Invalid current password
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer changer-token',
|
||||
])->postJson('/api/workers/change-password', [
|
||||
'current_password' => 'WrongPassword',
|
||||
'new_password' => 'NewPassword@123',
|
||||
'new_password_confirmation' => 'NewPassword@123',
|
||||
]);
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonValidationErrors(['current_password']);
|
||||
|
||||
// 2. Successful change
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer changer-token',
|
||||
])->postJson('/api/workers/change-password', [
|
||||
'current_password' => 'OldPassword@123',
|
||||
'new_password' => 'NewPassword@123',
|
||||
'new_password_confirmation' => 'NewPassword@123',
|
||||
]);
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
'success' => true,
|
||||
'message' => 'Password changed successfully.'
|
||||
]);
|
||||
|
||||
$this->assertTrue(\Illuminate\Support\Facades\Hash::check('NewPassword@123', $worker->fresh()->password));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user