Commit b2552435 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(auth): provision employee accounts with role-based permissions

Assigns human-friendly usernames, unique passwords, and business-appropriate
roles to all 62 imported employees based on their HR job titles.
Includes credentials reference sheet for permission testing.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 9548bd3b
# Employee Login Credentials — Testing Reference
Generated by `Phase_101_001_provision_employee_accounts` seed.
All accounts have `force_password_change = 1` — users must change password on first login.
## Admin Accounts (Pre-existing)
| ID | Name | Username | Notes |
|----|------|----------|-------|
| 1 | مدير النظام | admin | Super Admin — all permissions |
## Employee Accounts
| ID | Name (Arabic) | Department | Job Title | Username | Password | Role(s) |
|----|---------------|------------|-----------|----------|----------|---------|
| 7 | امين محمد امين محمد عمر | الإدارة العليا | مستشار المجلس للشئون الرياضية | `ameen` | `Club7821` | board_member |
| 8 | مجدي فتحي الخمري | الإدارة العليا | المشرف العام للنادي | `magdy` | `Club8934` | general_manager |
| 9 | محمد نبيل السيد | الإدارة العليا | مدير التشغيل | `m.nabil` | `Club9045` | general_manager |
| 10 | ايمان احمد محمود منصور | المكتب الفني | نائب مدير المكتب الفني | `eman` | `Club1056` | department_head |
| 11 | رؤي محمد حسن | المكتب الفني | موظف المكتب الفني | `roay` | `Club1167` | report_viewer |
| 12 | صفوت عبد العزيز حسن | المكتب الفني | مندوب مراسلات | `safwat` | `Club1278` | receptionist |
| 13 | محمد عبد الحميد محمد | النشاط الرياضي | مشرف نشاط رياضي | `m.hameed` | `Club1389` | sports_officer |
| 14 | أحمد نبيل محمود | النشاط الرياضي | مشرف نشاط رياضي | `a.nabil` | `Club1490` | sports_officer |
| 15 | عمر محمد طاهر | النشاط الرياضي | مشرف نشاط رياضي | `omar` | `Club1501` | sports_officer |
| 16 | منار هشام محمد محمود | النشاط الرياضي | مشرف نشاط رياضي | `manar` | `Club1612` | sports_officer |
| 17 | محمد ماهر جابر احمد | النشاط الرياضي | مشرف نشاط رياضي | `m.maher` | `Club1723` | sports_officer |
| 18 | احمد حسين عبد المنعم | النشاط الرياضي | مشرف بولينج | `a.hussein` | `Club1834` | sports_officer |
| 19 | هاني محمد إبراهيم محمد | النشاط الرياضي | مشرف بولينج | `hany` | `Club1945` | sports_officer |
| 20 | أسامة هاشم حسن | النشاط الرياضي | مشرف بولينج | `osama` | `Club2056` | sports_officer |
| 21 | احمد عبد الخالق اسماعيل | النشاط الرياضي | مدير مجمع السباحة | `a.khaleq` | `Club2167` | facilities_manager |
| 22 | أحمد محمد سعد عبد العزيز | النشاط الرياضي | مشرف حمام سباحة | `a.saad` | `Club2278` | sports_officer |
| 23 | مصطفى خالد محمد على | النشاط الرياضي | مشرف حمام سباحة | `mostafa.k` | `Club2389` | sports_officer |
| 24 | عبد الله طارق محمد محمد | النشاط الرياضي | مشرف حمام سباحة | `abdullah` | `Club2490` | sports_officer |
| 25 | نجلاء محمد السيد | النشاط الرياضي | مدير إشتراكات أكاديميات | `naglaa` | `Club2501` | academy_manager |
| 26 | محمود محمد اشرف محمود | النشاط الرياضي | مسئول إشتراكات أكاديميات | `mahmoud.m` | `Club2612` | sports_officer |
| 27 | اسلام محمد أحمد محمد | النشاط الرياضي | مسئول إشتراكات أكاديميات | `islam.a` | `Club2723` | sports_officer |
| 28 | رشا شريف مصطفى قاسم | العلاقات العامة | مدير العلاقات العامة | `rasha.sh` | `Club2834` | membership_director |
| 29 | رشا بخيت عبد الشفاق | العلاقات العامة | مسئول علاقات عامة | `rasha.b` | `Club2945` | membership_officer |
| 30 | شيرين فؤاد جمعه | العلاقات العامة | مسئول علاقات عامة | `shereen.f` | `Club3056` | membership_officer |
| 31 | وليد محمد غندور | الادارة المالية | مدير حسابات | `waleed.g` | `Club3167` | accountant, treasury_manager |
| 32 | أحمد إبراهيم الشحات عبد السميع | الادارة المالية | مشرف مالى | `a.ibrahim` | `Club3278` | accountant |
| 33 | علا سيد مصطفى عبد العال | الادارة المالية | محاسب ومراجع مالى | `olaa` | `Club3389` | accountant |
| 34 | ايه احمد محمود | الادارة المالية | مراجع مالى | `aya` | `Club3490` | auditor |
| 35 | علاءالدين محمد مجدى | الادارة المالية | مسئول تحصيل | `alaaeldeen` | `Club3501` | treasury_officer |
| 36 | مصطفي سامي احمد | الادارة المالية | امين خزنة فرعي | `mostafa.s` | `Club3612` | cashier_operator |
| 37 | إسلام محمد حسن | الادارة المالية | امين خزنة فرعي | `islam.h` | `Club3723` | cashier_operator |
| 38 | صلاح هاني صلاح | الادارة المالية | امين خزنة فرعي | `salah` | `Club3834` | cashier_operator |
| 39 | طعمة نبيل حسنين | الادارة المالية | امين خزنة فرعي | `toama` | `Club3945` | cashier_operator |
| 40 | ريهام طلعت محمود | ادارة المخازن | اداري | `reham` | `Club4056` | report_viewer |
| 41 | محمد عبد العزيز يوسف | الاشراف والمتابعة | مدير الإشراف والمتابعه | `m.aziz` | `Club4167` | department_head, security_officer |
| 42 | زياد فرج أحمد | الاشراف والمتابعة | اشراف ادارى | `ziad` | `Club4278` | security_officer |
| 43 | شيماء حمدي طاهر | الاشراف والمتابعة | اشراف اداري | `shimaa` | `Club4389` | security_officer |
| 44 | إسلام سمير أحمد | الاشراف والمتابعة | اشراف اداري | `islam.s` | `Club4490` | security_officer |
| 45 | إسلام محمد مصطفى | الاشراف والمتابعة | اشراف اداري | `islam.m` | `Club4501` | security_officer |
| 46 | محمد يحيي عتريس | الاشراف والمتابعة | اشراف اداري | `m.yahia` | `Club4612` | security_officer |
| 47 | د / عمرو محمد عبد العظيم | الادارة الطبية | مدير الإدارة الطبية | `dr.amr` | `Club4723` | department_head |
| 48 | د / محمد حازم نصر الدين سلامه | الادارة الطبية | طبيب بشرى | `dr.hazem` | `Club4834` | report_viewer |
| 49 | إسماعيل محمد إبراهيم | الادارة الطبية | اخصائي إصابات و تأهيل | `ismail` | `Club4945` | report_viewer |
| 50 | أمل إبراهيم حسين بسيونى | الادارة الطبية | تمريض | `amal` | `Club5056` | receptionist |
| 51 | حسام مصطفى السيد مصطفى | الادارة الطبية | تمريض | `hossam` | `Club5167` | receptionist |
| 52 | وليد محمد السيد عفيفى | الادارة الطبية | مسعف | `waleed.a` | `Club5278` | receptionist |
| 53 | احمد ناصر محمود دويدار | الادارة الطبية | مسعف | `a.nasser` | `Club5389` | receptionist |
| 54 | مراد طارق محمود صالح | التسويق والمبيعات | مدير التسويق والمبيعات | `morad` | `Club5490` | sales_agent, department_head |
| 55 | ميادة محمد خضير حسن | التسويق والمبيعات | مسئول تسويق | `mayada` | `Club5501` | sales_agent |
| 56 | تهاني محسن محمد محجوب | التسويق والمبيعات | مسئول مبيعات | `tahany` | `Club5612` | sales_agent |
| 57 | شهد سيد حسين | التسويق والمبيعات | مسئول مبيعات | `shahd` | `Club5723` | sales_agent |
| 58 | هبة محمود | الشئون القانونية | مدير الشئون القانونية | `heba` | `Club5834` | department_head, auditor |
| 59 | محمد سعد محمد | الموارد البشرية | مدير الموارد البشرية | `m.saad` | `Club5945` | hr_manager |
| 60 | مرفت حنفى محمد المحلاوى | الموارد البشرية | نائب مدير الموارد البشرية | `mervat` | `Club6056` | hr_officer |
| 61 | مصطفى سيد علي | اشتراكات العضوية | مدير اشتراكات عضوية | `mostafa.a` | `Club6167` | membership_director |
| 62 | هالة كارم محمد | اشتراكات العضوية | مسئول اشتراكات عضوية | `hala` | `Club6278` | membership_officer |
| 63 | محمد عبد المنعم محمود | اشتراكات العضوية | مسئول اشتراكات عضوية | `m.moneim` | `Club6389` | membership_officer |
| 64 | احمد انور محمد | النظم والمعلومات | مدير النظم والمعلومات | `a.anwar` | `Club6490` | it_admin |
| 65 | يوسف محمد مصطفى يوسف | النظم والمعلومات | مراقب كاميرات | `yousef` | `Club6501` | security_officer |
| 66 | محمود احمد عبد الفتاح عجلان | النظم والمعلومات | مصمم ومبرمج ومطور | `mahmoud.a` | `Club6612` | it_admin |
| 67 | شيرين محمد حسين | النظم والمعلومات | مصمم ومبرمج ومطور | `shereen.m` | `Club6723` | it_admin |
| 68 | أحمد عبد الله أحمد بكير | المسجد | إمام المسجد | `a.bakeer` | `Club6834` | receptionist |
## Role Summary
| Role | Count | Permissions Scope |
|------|-------|-------------------|
| general_manager | 2 | Full read access to all modules, member management, reports, interviews |
| board_member | 1 | Member view, financial reports, interview decisions, approvals |
| department_head | 6 | HR attendance/leave approval, performance reviews, team oversight |
| hr_manager | 1 | Full HR module access — employees, payroll, contracts, insurance |
| hr_officer | 1 | HR data entry — attendance, contracts, leaves, documents |
| accountant | 3 | Full accounting module — COA, journals, reports, bank reconciliation |
| treasury_manager | 1 | Payments, collections, installments, treasury dashboard |
| treasury_officer | 1 | Payment processing, collections, cashier sessions |
| cashier_operator | 4 | Payment processing, receipt printing, member search |
| auditor | 2 | Read-only view across all financial and membership data |
| membership_director | 3 | Full member management — create, edit, archive, transfers |
| membership_officer | 4 | Member data entry — create, edit, documents, children, spouses |
| sports_officer | 12 | Sports activities — bookings, groups, players, schedules, lockers |
| sports_coordinator | 0 | (Not assigned — reserved for cross-sport coordination) |
| academy_manager | 1 | Academy management — programs, groups, enrollments, subscriptions |
| facilities_manager | 1 | Facility management — bookings, reservations, scheduling |
| sales_agent | 4 | Member search, forms, sales-related views |
| security_officer | 7 | Carnet logs, member search, member view |
| report_viewer | 5 | Member search, reports export, financial/membership/operations reports |
| receptionist | 6 | Carnet management, member search, reservations, support tickets |
| it_admin | 3 | User/role management, alerts, settings, audit logs |
## Testing Notes
1. Login at the ERP URL with any username/password from the table above
2. On first login, you'll be redirected to the password change page
3. New password must be 8+ chars with uppercase, lowercase, and a digit
4. After changing password, you'll see only the menu items your role permits
5. Test different role types to verify sidebar filtering works correctly
<?php
declare(strict_types=1);
use App\Core\Database;
return function (Database $db): void {
// ── Employee credentials: ID → [username, plaintext password] ──
$credentials = [
7 => ['ameen', 'Club7821'],
8 => ['magdy', 'Club8934'],
9 => ['m.nabil', 'Club9045'],
10 => ['eman', 'Club1056'],
11 => ['roay', 'Club1167'],
12 => ['safwat', 'Club1278'],
13 => ['m.hameed', 'Club1389'],
14 => ['a.nabil', 'Club1490'],
15 => ['omar', 'Club1501'],
16 => ['manar', 'Club1612'],
17 => ['m.maher', 'Club1723'],
18 => ['a.hussein', 'Club1834'],
19 => ['hany', 'Club1945'],
20 => ['osama', 'Club2056'],
21 => ['a.khaleq', 'Club2167'],
22 => ['a.saad', 'Club2278'],
23 => ['mostafa.k', 'Club2389'],
24 => ['abdullah', 'Club2490'],
25 => ['naglaa', 'Club2501'],
26 => ['mahmoud.m', 'Club2612'],
27 => ['islam.a', 'Club2723'],
28 => ['rasha.sh', 'Club2834'],
29 => ['rasha.b', 'Club2945'],
30 => ['shereen.f', 'Club3056'],
31 => ['waleed.g', 'Club3167'],
32 => ['a.ibrahim', 'Club3278'],
33 => ['olaa', 'Club3389'],
34 => ['aya', 'Club3490'],
35 => ['alaaeldeen', 'Club3501'],
36 => ['mostafa.s', 'Club3612'],
37 => ['islam.h', 'Club3723'],
38 => ['salah', 'Club3834'],
39 => ['toama', 'Club3945'],
40 => ['reham', 'Club4056'],
41 => ['m.aziz', 'Club4167'],
42 => ['ziad', 'Club4278'],
43 => ['shimaa', 'Club4389'],
44 => ['islam.s', 'Club4490'],
45 => ['islam.m', 'Club4501'],
46 => ['m.yahia', 'Club4612'],
47 => ['dr.amr', 'Club4723'],
48 => ['dr.hazem', 'Club4834'],
49 => ['ismail', 'Club4945'],
50 => ['amal', 'Club5056'],
51 => ['hossam', 'Club5167'],
52 => ['waleed.a', 'Club5278'],
53 => ['a.nasser', 'Club5389'],
54 => ['morad', 'Club5490'],
55 => ['mayada', 'Club5501'],
56 => ['tahany', 'Club5612'],
57 => ['shahd', 'Club5723'],
58 => ['heba', 'Club5834'],
59 => ['m.saad', 'Club5945'],
60 => ['mervat', 'Club6056'],
61 => ['mostafa.a', 'Club6167'],
62 => ['hala', 'Club6278'],
63 => ['m.moneim', 'Club6389'],
64 => ['a.anwar', 'Club6490'],
65 => ['yousef', 'Club6501'],
66 => ['mahmoud.a', 'Club6612'],
67 => ['shereen.m', 'Club6723'],
68 => ['a.bakeer', 'Club6834'],
];
// ── Job Title ID → Role codes ──
$jobTitleRoles = [
2 => ['board_member'],
3 => ['general_manager'],
4 => ['general_manager'],
6 => ['department_head'],
7 => ['report_viewer'],
8 => ['receptionist'],
11 => ['sports_officer'],
12 => ['sports_officer'],
13 => ['cashier_operator'],
14 => ['facilities_manager'],
15 => ['sports_officer'],
16 => ['academy_manager'],
17 => ['sports_officer'],
18 => ['membership_director'],
19 => ['membership_officer'],
21 => ['accountant', 'treasury_manager'],
22 => ['accountant'],
23 => ['accountant'],
24 => ['auditor'],
25 => ['treasury_officer'],
29 => ['report_viewer'],
30 => ['department_head', 'security_officer'],
32 => ['security_officer'],
33 => ['security_officer'],
34 => ['department_head'],
35 => ['report_viewer'],
36 => ['report_viewer'],
37 => ['receptionist'],
38 => ['receptionist'],
39 => ['sales_agent', 'department_head'],
40 => ['sales_agent'],
41 => ['sales_agent'],
42 => ['department_head', 'auditor'],
43 => ['hr_manager'],
44 => ['hr_officer'],
45 => ['membership_director'],
47 => ['membership_officer'],
48 => ['it_admin'],
49 => ['security_officer'],
50 => ['it_admin'],
51 => ['receptionist'],
];
// ── Cache role IDs by role_code ──
$roleRows = $db->select("SELECT id, role_code FROM roles WHERE is_active = 1", []);
$roleMap = [];
foreach ($roleRows as $r) {
$roleMap[$r['role_code']] = (int) $r['id'];
}
// ── Cache job_title_id per employee from HR profiles ──
$profileRows = $db->select(
"SELECT employee_id, job_title_id FROM hr_employee_profiles WHERE is_archived = 0",
[]
);
$employeeJobTitle = [];
foreach ($profileRows as $p) {
$employeeJobTitle[(int) $p['employee_id']] = (int) $p['job_title_id'];
}
$now = date('Y-m-d H:i:s');
$updated = 0;
$rolesAssigned = 0;
$skipped = 0;
foreach ($credentials as $empId => [$username, $plainPassword]) {
// Check if employee exists and still has generic emp00XX username
$emp = $db->selectOne(
"SELECT id, username FROM employees WHERE id = ? AND is_archived = 0",
[$empId]
);
if (!$emp) {
$skipped++;
continue;
}
// Skip if username was already changed from emp00XX pattern
if (!preg_match('/^emp\d{4}/', $emp['username'])) {
echo " SKIP #{$empId} — username already customized: {$emp['username']}\n";
$skipped++;
continue;
}
// Check username not taken by another employee
$taken = $db->selectOne(
"SELECT id FROM employees WHERE username = ? AND id != ?",
[$username, $empId]
);
if ($taken) {
echo " WARN #{$empId} — username '{$username}' taken, using '{$username}.{$empId}'\n";
$username = $username . '.' . $empId;
}
// Update credentials
$hash = password_hash($plainPassword, PASSWORD_BCRYPT, ['cost' => 12]);
$db->update('employees', [
'username' => $username,
'password_hash' => $hash,
'force_password_change' => 1,
'failed_login_count' => 0,
'locked_until' => null,
'updated_at' => $now,
], '`id` = ?', [$empId]);
$updated++;
// Assign roles based on job title
$jobTitleId = $employeeJobTitle[$empId] ?? null;
if ($jobTitleId === null) {
echo " WARN #{$empId} ({$username}) — no HR profile / job title, skipping role assignment\n";
continue;
}
$roleCodes = $jobTitleRoles[$jobTitleId] ?? null;
if ($roleCodes === null) {
echo " WARN #{$empId} ({$username}) — job_title_id={$jobTitleId} has no role mapping\n";
continue;
}
// Check if employee already has role assignments
$existingRoles = $db->select(
"SELECT role_id FROM employee_roles WHERE employee_id = ? AND is_active = 1",
[$empId]
);
if (!empty($existingRoles)) {
echo " SKIP roles #{$empId} ({$username}) — already has " . count($existingRoles) . " role(s)\n";
continue;
}
foreach ($roleCodes as $code) {
$roleId = $roleMap[$code] ?? null;
if ($roleId === null) {
echo " ERR #{$empId} — role '{$code}' not found in roles table\n";
continue;
}
$db->insert('employee_roles', [
'employee_id' => $empId,
'role_id' => $roleId,
'assigned_at' => $now,
'assigned_by' => 1,
'is_active' => 1,
]);
$rolesAssigned++;
}
}
echo "\n";
echo "=== Employee Account Provisioning Complete ===\n";
echo " Credentials updated: {$updated}\n";
echo " Role assignments created: {$rolesAssigned}\n";
echo " Skipped: {$skipped}\n";
echo "\n";
// ── Output credentials table ──
echo "=== CREDENTIALS TABLE (save this!) ===\n";
echo str_pad('ID', 4) . str_pad('Username', 16) . str_pad('Password', 12) . "Roles\n";
echo str_repeat('-', 70) . "\n";
foreach ($credentials as $empId => [$username, $plainPassword]) {
$jobTitleId = $employeeJobTitle[$empId] ?? null;
$roleCodes = ($jobTitleId !== null) ? ($jobTitleRoles[$jobTitleId] ?? ['—']) : ['—'];
echo str_pad((string) $empId, 4)
. str_pad($username, 16)
. str_pad($plainPassword, 12)
. implode(', ', $roleCodes) . "\n";
}
echo "\n";
};
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