Commit e1f1837a authored by Mahmoud Aglan's avatar Mahmoud Aglan

Overhaul coach create form: full validation, NID auto-parse, edge cases

Frontend:
- Client-side NID parser (no API call) — extracts DOB, gender, age, governorate
- Shows green badge with parsed info (governorate, gender, age)
- Red highlight + Arabic message on every invalid field
- Validates: code, name, employment_type, payment_model (non-academy),
  academy_id (academy), NID length, email format, phone format
- Hides payment/rate fields for academy coaches (not needed)
- Shows note explaining academy coaches follow salary system
- Gender/DOB auto-locked when NID is valid, manual otherwise
- Age displayed under DOB field

Backend:
- Strips non-digits from NID input
- Uppercase code automatically
- Checks NID uniqueness against existing coaches
- Validates email with filter_var
- Academy coach requires academy_id selected
- payment_model defaults to 'salary' for academy, 'per_session' fallback
- max_groups minimum 1
- Success message includes coach name
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 646d11e6
...@@ -99,10 +99,10 @@ class CoachController extends Controller ...@@ -99,10 +99,10 @@ class CoachController extends Controller
{ {
$db = App::getInstance()->db(); $db = App::getInstance()->db();
$code = trim((string) $request->post('code', '')); $code = strtoupper(trim((string) $request->post('code', '')));
$fullNameAr = trim((string) $request->post('full_name_ar', '')); $fullNameAr = trim((string) $request->post('full_name_ar', ''));
$fullNameEn = trim((string) $request->post('full_name_en', '')); $fullNameEn = trim((string) $request->post('full_name_en', ''));
$nationalId = trim((string) $request->post('national_id', '')); $nationalId = preg_replace('/\D/', '', trim((string) $request->post('national_id', '')));
$phone = trim((string) $request->post('phone', '')); $phone = trim((string) $request->post('phone', ''));
$email = trim((string) $request->post('email', '')); $email = trim((string) $request->post('email', ''));
$dateOfBirth = trim((string) $request->post('date_of_birth', '')); $dateOfBirth = trim((string) $request->post('date_of_birth', ''));
...@@ -114,6 +114,11 @@ class CoachController extends Controller ...@@ -114,6 +114,11 @@ class CoachController extends Controller
$monthlyRate = trim((string) $request->post('monthly_rate', '')); $monthlyRate = trim((string) $request->post('monthly_rate', ''));
$maxGroups = trim((string) $request->post('max_groups', '')); $maxGroups = trim((string) $request->post('max_groups', ''));
$bioAr = trim((string) $request->post('bio_ar', '')); $bioAr = trim((string) $request->post('bio_ar', ''));
$coachType = trim((string) $request->post('coach_type', 'independent'));
if (!array_key_exists($coachType, Coach::getCoachTypeOptions())) {
$coachType = 'independent';
}
// Validation // Validation
$errors = []; $errors = [];
...@@ -121,6 +126,8 @@ class CoachController extends Controller ...@@ -121,6 +126,8 @@ class CoachController extends Controller
$errors[] = 'كود المدرب مطلوب'; $errors[] = 'كود المدرب مطلوب';
} elseif (mb_strlen($code) > 30) { } elseif (mb_strlen($code) > 30) {
$errors[] = 'كود المدرب يجب ألا يتجاوز 30 حرف'; $errors[] = 'كود المدرب يجب ألا يتجاوز 30 حرف';
} elseif (Coach::findByCode($code) !== null) {
$errors[] = 'كود المدرب "' . $code . '" مستخدم بالفعل — اختر كود آخر';
} }
if ($fullNameAr === '') { if ($fullNameAr === '') {
$errors[] = 'الاسم بالعربي مطلوب'; $errors[] = 'الاسم بالعربي مطلوب';
...@@ -130,15 +137,23 @@ class CoachController extends Controller ...@@ -130,15 +137,23 @@ class CoachController extends Controller
if ($employmentType === '') { if ($employmentType === '') {
$errors[] = 'نوع التوظيف مطلوب'; $errors[] = 'نوع التوظيف مطلوب';
} }
$coachType = trim((string) $request->post('coach_type', 'independent'));
if (!array_key_exists($coachType, Coach::getCoachTypeOptions())) {
$coachType = 'independent';
}
if ($coachType !== 'academy' && $paymentModel === '') { if ($coachType !== 'academy' && $paymentModel === '') {
$errors[] = 'نموذج الدفع مطلوب'; $errors[] = 'نموذج الدفع مطلوب للمدربين غير التابعين لأكاديمية';
}
if ($coachType === 'academy' && !(int) $request->post('academy_id', 0)) {
$errors[] = 'يجب اختيار الأكاديمية لمدرب أكاديمية';
}
if ($nationalId !== '' && strlen($nationalId) !== 14) {
$errors[] = 'الرقم القومي يجب أن يكون 14 رقم بالضبط';
}
if ($nationalId !== '' && strlen($nationalId) === 14) {
$existingCoach = $db->selectOne("SELECT id, full_name_ar FROM sa_coaches WHERE national_id = ? AND is_archived = 0", [$nationalId]);
if ($existingCoach) {
$errors[] = 'الرقم القومي مسجل بالفعل للمدرب: ' . ($existingCoach['full_name_ar'] ?? '');
}
} }
if ($code !== '' && Coach::findByCode($code) !== null) { if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'كود المدرب مستخدم بالفعل'; $errors[] = 'صيغة البريد الإلكتروني غير صحيحة';
} }
if (!empty($errors)) { if (!empty($errors)) {
...@@ -149,11 +164,12 @@ class CoachController extends Controller ...@@ -149,11 +164,12 @@ class CoachController extends Controller
return $this->redirect('/sa/coaches/create'); return $this->redirect('/sa/coaches/create');
} }
// Parse NID for DOB/gender
if ($nationalId !== '' && strlen($nationalId) === 14) { if ($nationalId !== '' && strlen($nationalId) === 14) {
$parsed = NationalIdParser::parse($nationalId); $parsed = NationalIdParser::parse($nationalId);
if ($parsed['is_valid']) { if ($parsed['is_valid']) {
$dateOfBirth = $parsed['dob']; if ($parsed['dob']) $dateOfBirth = $parsed['dob'];
$gender = $parsed['gender']; if ($parsed['gender']) $gender = $parsed['gender'];
} }
} }
...@@ -175,16 +191,15 @@ class CoachController extends Controller ...@@ -175,16 +191,15 @@ class CoachController extends Controller
'hourly_rate' => $coachType === 'academy' ? null : ($hourlyRate !== '' ? (float) $hourlyRate : null), 'hourly_rate' => $coachType === 'academy' ? null : ($hourlyRate !== '' ? (float) $hourlyRate : null),
'session_rate' => $coachType === 'academy' ? null : ($sessionRate !== '' ? (float) $sessionRate : null), 'session_rate' => $coachType === 'academy' ? null : ($sessionRate !== '' ? (float) $sessionRate : null),
'monthly_rate' => $coachType === 'academy' ? null : ($monthlyRate !== '' ? (float) $monthlyRate : null), 'monthly_rate' => $coachType === 'academy' ? null : ($monthlyRate !== '' ? (float) $monthlyRate : null),
'max_groups' => $maxGroups !== '' ? (int) $maxGroups : 10, 'max_groups' => $maxGroups !== '' ? max(1, (int) $maxGroups) : 10,
'bio_ar' => $bioAr ?: null, 'bio_ar' => $bioAr ?: null,
'is_active' => 1, 'is_active' => 1,
'branch_id' => App::getInstance()->session()->get('branch_id', 1), 'branch_id' => App::getInstance()->session()->get('branch_id', 1),
]); ]);
// Sync disciplines
$this->syncDisciplines($db, (int) $coach->id, $request); $this->syncDisciplines($db, (int) $coach->id, $request);
return $this->redirect('/sa/coaches')->withSuccess('تم إضافة المدرب بنجاح'); return $this->redirect('/sa/coaches')->withSuccess('تم إضافة المدرب "' . $fullNameAr . '" بنجاح');
} }
public function show(Request $request, string $id): Response public function show(Request $request, string $id): Response
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment