Commit 9bd4d36c authored by Mahmoud Aglan's avatar Mahmoud Aglan

fixedHR

parent f6048765
......@@ -112,7 +112,7 @@ class AttendanceController extends Controller
'check_in_time' => $checkIn ?: null,
'check_out_time' => $checkOut ?: null,
'actual_hours' => (string) $actualHours,
'check_method' => 'manual',
'check_in_method' => 'manual',
'status' => $checkIn !== '' ? 'present' : 'absent',
'updated_at' => date('Y-m-d H:i:s'),
'updated_by' => $employee ? (int) $employee->id : null,
......
......@@ -82,15 +82,15 @@ class ContractController extends Controller
$db = App::getInstance()->db();
// Previous contract in renewal chain
$previousContract = $contract->previous_contract_id
? HrContract::find((int) $contract->previous_contract_id)
$previousContract = $contract->renewal_of_contract_id
? HrContract::find((int) $contract->renewal_of_contract_id)
: null;
// Renewal history
$renewals = $db->select(
"SELECT id, contract_number, start_date, end_date, status
FROM hr_contracts
WHERE previous_contract_id = ? AND is_archived = 0
WHERE renewal_of_contract_id = ? AND is_archived = 0
ORDER BY start_date ASC",
[(int) $id]
);
......@@ -182,7 +182,10 @@ class ContractController extends Controller
return $this->redirect('/hr/contracts/' . $id)->withError('تاريخ بداية العقد الجديد مطلوب');
}
$result = ContractService::renew((int) $id, $newStartDate, $newEndDate);
$result = ContractService::renew((int) $id, [
'start_date' => $newStartDate,
'end_date' => $newEndDate,
]);
if (!$result['success']) {
return $this->redirect('/hr/contracts/' . $id)->withError($result['error']);
......@@ -198,14 +201,13 @@ class ContractController extends Controller
return $this->redirect('/hr/contracts')->withError('العقد غير موجود');
}
$terminationDate = trim((string) $request->post('termination_date', date('Y-m-d')));
$terminationReason = trim((string) $request->post('termination_reason', ''));
if ($terminationReason === '') {
return $this->redirect('/hr/contracts/' . $id)->withError('سبب إنهاء العقد مطلوب');
}
$result = ContractService::terminate((int) $id, $terminationDate, $terminationReason);
$result = ContractService::terminate((int) $id, $terminationReason);
if (!$result['success']) {
return $this->redirect('/hr/contracts/' . $id)->withError($result['error']);
......
......@@ -23,7 +23,7 @@ class DepartmentController extends Controller
$result = HrDepartment::search($filters, 25, $page);
$db = App::getInstance()->db();
$branches = $db->select("SELECT id, name_ar FROM branches WHERE is_archived = 0 ORDER BY name_ar");
$branches = $db->select("SELECT id, name_ar FROM branches WHERE is_active = 1 ORDER BY name_ar");
return $this->view('HR.Views.departments.index', [
'departments' => $result['data'],
......@@ -37,7 +37,7 @@ class DepartmentController extends Controller
{
$db = App::getInstance()->db();
$departments = HrDepartment::allActive();
$branches = $db->select("SELECT id, name_ar FROM branches WHERE is_archived = 0 ORDER BY name_ar");
$branches = $db->select("SELECT id, name_ar FROM branches WHERE is_active = 1 ORDER BY name_ar");
$employees = HrEmployeeProfile::getActiveIds();
return $this->view('HR.Views.departments.form', [
......@@ -73,8 +73,8 @@ class DepartmentController extends Controller
$parent = $department->parent_id ? HrDepartment::find((int) $department->parent_id) : null;
$manager = $department->manager_id
? $db->selectOne("SELECT hp.id, hp.first_name_ar, hp.last_name_ar FROM hr_employee_profiles hp WHERE hp.id = ? AND hp.is_archived = 0", [(int) $department->manager_id])
$manager = $department->manager_employee_id
? $db->selectOne("SELECT hp.id, hp.first_name_ar, hp.last_name_ar FROM hr_employee_profiles hp WHERE hp.employee_id = ? AND hp.is_archived = 0", [(int) $department->manager_employee_id])
: null;
$branch = $department->branch_id
......@@ -82,7 +82,7 @@ class DepartmentController extends Controller
: null;
$children = $db->select(
"SELECT id, name_ar, code FROM hr_departments WHERE parent_id = ? AND is_archived = 0 ORDER BY name_ar",
"SELECT id, name_ar, department_code FROM hr_departments WHERE parent_id = ? AND is_archived = 0 ORDER BY name_ar",
[(int) $id]
);
......@@ -110,7 +110,7 @@ class DepartmentController extends Controller
$db = App::getInstance()->db();
$departments = HrDepartment::allActive();
$branches = $db->select("SELECT id, name_ar FROM branches WHERE is_archived = 0 ORDER BY name_ar");
$branches = $db->select("SELECT id, name_ar FROM branches WHERE is_active = 1 ORDER BY name_ar");
$employees = HrEmployeeProfile::getActiveIds();
return $this->view('HR.Views.departments.form', [
......@@ -181,9 +181,9 @@ class DepartmentController extends Controller
return [
'name_ar' => trim((string) $request->post('name_ar', '')),
'name_en' => trim((string) $request->post('name_en', '')) ?: null,
'code' => strtoupper(trim((string) $request->post('code', ''))),
'parent_id' => ((int) $request->post('parent_id', 0)) ?: null,
'manager_id' => ((int) $request->post('manager_id', 0)) ?: null,
'department_code' => strtoupper(trim((string) $request->post('department_code', ''))),
'parent_id' => ((int) $request->post('parent_id', 0)) ?: null,
'manager_employee_id' => ((int) $request->post('manager_employee_id', 0)) ?: null,
'branch_id' => ((int) $request->post('branch_id', 0)) ?: null,
'is_active' => (int) ($request->post('is_active', 1)),
];
......@@ -195,7 +195,7 @@ class DepartmentController extends Controller
if ($data['name_ar'] === '' || mb_strlen($data['name_ar']) < 2) {
$errors[] = 'اسم القسم بالعربي مطلوب (حرفان على الأقل)';
}
if ($data['code'] === '') {
if ($data['department_code'] === '') {
$errors[] = 'كود القسم مطلوب';
}
return $errors;
......
......@@ -272,7 +272,7 @@ class EmployeeProfileController extends Controller
// Check if detail already exists for this component+date
$existing = $db->selectOne(
"SELECT id FROM hr_employee_salary_details WHERE employee_profile_id = ? AND salary_component_id = ? AND effective_date = ? AND is_archived = 0",
"SELECT id FROM hr_employee_salary_details WHERE employee_profile_id = ? AND component_id = ? AND effective_from = ? AND is_active = 1",
[(int) $id, (int) $componentId, $effectiveDate]
);
......@@ -285,9 +285,9 @@ class EmployeeProfileController extends Controller
} else {
$db->insert('hr_employee_salary_details', [
'employee_profile_id' => (int) $id,
'salary_component_id' => (int) $componentId,
'component_id' => (int) $componentId,
'amount' => $amount,
'effective_date' => $effectiveDate,
'effective_from' => $effectiveDate,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'created_by' => $employee ? (int) $employee->id : null,
......
......@@ -202,7 +202,7 @@ class EndOfServiceController extends Controller
$db->update('hr_end_of_service', [
'status' => 'paid',
'payment_date' => $paymentDate,
'paid_date' => $paymentDate,
'updated_at' => date('Y-m-d H:i:s'),
'updated_by' => $employee ? (int) $employee->id : null,
], '`id` = ?', [(int) $id]);
......
......@@ -92,7 +92,7 @@ class HolidayController extends Controller
return [
'name_ar' => trim((string) $request->post('name_ar', '')),
'name_en' => trim((string) $request->post('name_en', '')) ?: null,
'holiday_date' => trim((string) $request->post('holiday_date', '')),
'date' => trim((string) $request->post('date', '')),
'duration_days' => max(1, (int) $request->post('duration_days', 1)),
'holiday_type' => trim((string) $request->post('holiday_type', 'national')),
'is_recurring' => (int) ($request->post('is_recurring', 0)),
......@@ -107,7 +107,7 @@ class HolidayController extends Controller
if ($data['name_ar'] === '' || mb_strlen($data['name_ar']) < 2) {
$errors[] = 'اسم العطلة بالعربي مطلوب';
}
if ($data['holiday_date'] === '') {
if ($data['date'] === '') {
$errors[] = 'تاريخ العطلة مطلوب';
}
if (!in_array($data['holiday_type'], ['national', 'religious', 'company'], true)) {
......
......@@ -60,7 +60,7 @@ class HrReportController extends Controller
"SELECT pp.month,
pp.status as period_status,
COUNT(pr.id) as employee_count,
SUM(pr.gross_salary) as total_gross,
SUM(pr.gross_earnings) as total_gross,
SUM(pr.total_deductions) as total_deductions,
SUM(pr.net_salary) as total_net,
SUM(pr.overtime_amount) as total_overtime,
......@@ -159,9 +159,9 @@ class HrReportController extends Controller
$monthlySummary = $db->select(
"SELECT pp.month,
COUNT(ir.id) as employee_count,
SUM(ir.employee_share_total) as total_employee_share,
SUM(ir.employer_share_total) as total_employer_share,
SUM(ir.employee_share_total + ir.employer_share_total) as total_combined
SUM(ir.total_employee_share) as total_employee_share,
SUM(ir.total_employer_share) as total_employer_share,
SUM(ir.total_employee_share + ir.total_employer_share) as total_combined
FROM hr_payroll_periods pp
LEFT JOIN hr_insurance_records ir ON ir.period_id = pp.id AND ir.is_archived = 0
WHERE pp.year = ? AND pp.is_archived = 0
......@@ -185,9 +185,9 @@ class HrReportController extends Controller
$monthlySummary = $db->select(
"SELECT pp.month,
COUNT(tr.id) as employee_count,
SUM(tr.gross_taxable_income) as total_taxable,
SUM(tr.total_exemptions) as total_exemptions,
SUM(tr.tax_amount) as total_tax
SUM(tr.gross_taxable_monthly) as total_taxable,
SUM(tr.personal_exemption + tr.insurance_exemption) as total_exemptions,
SUM(tr.monthly_tax) as total_tax
FROM hr_payroll_periods pp
LEFT JOIN hr_tax_records tr ON tr.period_id = pp.id AND tr.is_archived = 0
WHERE pp.year = ? AND pp.is_archived = 0
......@@ -197,7 +197,7 @@ class HrReportController extends Controller
);
$ytdTotal = $db->selectOne(
"SELECT SUM(tr.tax_amount) as ytd_tax
"SELECT SUM(tr.monthly_tax) as ytd_tax
FROM hr_tax_records tr
JOIN hr_payroll_periods pp ON pp.id = tr.period_id
WHERE pp.year = ? AND tr.is_archived = 0",
......
......@@ -21,8 +21,8 @@ class InsuranceController extends Controller
$periods = $db->select(
"SELECT pp.id, pp.period_code, pp.year, pp.month, pp.status,
(SELECT COUNT(*) FROM hr_insurance_records ir WHERE ir.period_id = pp.id AND ir.is_archived = 0) as record_count,
(SELECT SUM(employee_share_total) FROM hr_insurance_records ir WHERE ir.period_id = pp.id AND ir.is_archived = 0) as total_employee_share,
(SELECT SUM(employer_share_total) FROM hr_insurance_records ir WHERE ir.period_id = pp.id AND ir.is_archived = 0) as total_employer_share
(SELECT SUM(total_employee_share) FROM hr_insurance_records ir WHERE ir.period_id = pp.id AND ir.is_archived = 0) as total_employee_share,
(SELECT SUM(total_employer_share) FROM hr_insurance_records ir WHERE ir.period_id = pp.id AND ir.is_archived = 0) as total_employer_share
FROM hr_payroll_periods pp
WHERE pp.year = ? AND pp.is_archived = 0
ORDER BY pp.month ASC",
......
......@@ -107,11 +107,11 @@ class JobTitleController extends Controller
return [
'name_ar' => trim((string) $request->post('name_ar', '')),
'name_en' => trim((string) $request->post('name_en', '')) ?: null,
'code' => strtoupper(trim((string) $request->post('code', ''))),
'title_code' => strtoupper(trim((string) $request->post('title_code', ''))),
'grade_level' => ((int) $request->post('grade_level', 0)) ?: null,
'min_salary' => trim((string) $request->post('min_salary', '')) ?: null,
'max_salary' => trim((string) $request->post('max_salary', '')) ?: null,
'description' => trim((string) $request->post('description', '')) ?: null,
'description_ar'=> trim((string) $request->post('description_ar', '')) ?: null,
'is_active' => (int) ($request->post('is_active', 1)),
];
}
......@@ -122,7 +122,7 @@ class JobTitleController extends Controller
if ($data['name_ar'] === '' || mb_strlen($data['name_ar']) < 2) {
$errors[] = 'اسم المسمى الوظيفي بالعربي مطلوب (حرفان على الأقل)';
}
if ($data['code'] === '') {
if ($data['title_code'] === '') {
$errors[] = 'كود المسمى الوظيفي مطلوب';
}
if ($data['min_salary'] !== null && $data['max_salary'] !== null) {
......
......@@ -65,7 +65,7 @@ class LeaveController extends Controller
'leave_type_id' => (int) $request->post('leave_type_id', 0),
'start_date' => trim((string) $request->post('start_date', '')),
'end_date' => trim((string) $request->post('end_date', '')),
'is_half_day' => (int) ($request->post('is_half_day', 0)),
'half_day' => (int) ($request->post('half_day', 0)),
'reason' => trim((string) $request->post('reason', '')) ?: null,
];
......@@ -267,7 +267,7 @@ class LeaveController extends Controller
$employee = $this->currentEmployee();
$balance = $db->selectOne(
"SELECT * FROM hr_leave_balances WHERE employee_profile_id = ? AND leave_type_id = ? AND year = ? AND is_archived = 0",
"SELECT * FROM hr_leave_balances WHERE employee_profile_id = ? AND leave_type_id = ? AND year = ?",
[$profileId, $leaveTypeId, $year]
);
......
......@@ -93,7 +93,7 @@ class PayrollController extends Controller
$db = App::getInstance()->db();
$totals = $db->selectOne(
"SELECT COUNT(*) as employee_count,
SUM(gross_salary) as total_gross,
SUM(gross_earnings) as total_gross,
SUM(net_salary) as total_net,
SUM(total_deductions) as total_deductions
FROM hr_payroll_runs
......@@ -183,14 +183,14 @@ class PayrollController extends Controller
try {
$db->update('hr_payroll_periods', [
'status' => 'paid',
'payment_date' => $paymentDate,
'paid_date' => $paymentDate,
'updated_at' => date('Y-m-d H:i:s'),
'updated_by' => $employee ? (int) $employee->id : null,
], '`id` = ?', [(int) $id]);
// Mark all runs as paid
$db->update('hr_payroll_runs', [
'payment_status' => 'paid',
'status' => 'paid',
'paid_at' => $paymentDate . ' ' . date('H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
], '`period_id` = ? AND `is_archived` = 0', [(int) $id]);
......
......@@ -22,8 +22,8 @@ class TaxController extends Controller
$monthlySummary = $db->select(
"SELECT pp.month,
COUNT(tr.id) as employee_count,
SUM(tr.gross_taxable_income) as total_gross_taxable,
SUM(tr.tax_amount) as total_tax
SUM(tr.gross_taxable_monthly) as total_gross_taxable,
SUM(tr.monthly_tax) as total_tax
FROM hr_payroll_periods pp
LEFT JOIN hr_tax_records tr ON tr.period_id = pp.id AND tr.is_archived = 0
WHERE pp.year = ? AND pp.is_archived = 0
......@@ -34,8 +34,8 @@ class TaxController extends Controller
// YTD totals
$ytdTotals = $db->selectOne(
"SELECT SUM(tr.gross_taxable_income) as ytd_gross_taxable,
SUM(tr.tax_amount) as ytd_tax
"SELECT SUM(tr.gross_taxable_monthly) as ytd_gross_taxable,
SUM(tr.monthly_tax) as ytd_tax
FROM hr_tax_records tr
JOIN hr_payroll_periods pp ON pp.id = tr.period_id
WHERE pp.year = ? AND tr.is_archived = 0 AND pp.is_archived = 0",
......
......@@ -16,7 +16,7 @@ class WorkScheduleController extends Controller
$db = App::getInstance()->db();
$schedules = $db->select(
"SELECT ws.*,
(SELECT COUNT(*) FROM hr_employee_schedules es WHERE es.schedule_id = ws.id AND es.is_active = 1 AND es.is_archived = 0) as employee_count
(SELECT COUNT(*) FROM hr_employee_schedules es WHERE es.schedule_id = ws.id AND es.is_active = 1) as employee_count
FROM hr_work_schedules ws
WHERE ws.is_archived = 0
ORDER BY ws.name_ar ASC"
......@@ -104,7 +104,7 @@ class WorkScheduleController extends Controller
'hours_per_week' => trim((string) $request->post('hours_per_week', '48.00')),
'ramadan_hours_per_day' => trim((string) $request->post('ramadan_hours_per_day', '')) ?: null,
'is_night_shift' => (int) ($request->post('is_night_shift', 0)),
'days_config' => json_encode($daysConfig, JSON_UNESCAPED_UNICODE),
'days_config_json' => json_encode($daysConfig, JSON_UNESCAPED_UNICODE),
'is_active' => (int) ($request->post('is_active', 1)),
];
}
......
......@@ -16,11 +16,12 @@ class HrAttendance extends Model
protected static bool $autoTrackAuthor = true;
protected static array $fillable = [
'employee_profile_id', 'attendance_date', 'schedule_id',
'employee_profile_id', 'attendance_date',
'check_in_time', 'check_out_time', 'check_in_method', 'check_out_method',
'expected_hours', 'actual_hours', 'overtime_hours',
'actual_hours', 'overtime_hours', 'overtime_type',
'late_minutes', 'early_leave_minutes',
'status', 'is_ramadan', 'notes',
'status', 'is_ramadan_schedule', 'notes',
'is_approved', 'approved_by', 'approved_at',
];
public static function getStatuses(): array
......@@ -130,12 +131,12 @@ class HrAttendance extends Model
$offset = ($page - 1) * $perPage;
$rows = $db->select(
"SELECT a.*, p.full_name_ar, p.employee_number, d.name_ar as department_name
"SELECT a.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number, d.name_ar as department_name
FROM hr_attendance a
JOIN hr_employee_profiles p ON p.id = a.employee_profile_id
LEFT JOIN hr_departments d ON d.id = p.department_id
WHERE {$where}
ORDER BY a.attendance_date DESC, p.full_name_ar ASC
ORDER BY a.attendance_date DESC, p.first_name_ar ASC
LIMIT {$perPage} OFFSET {$offset}",
$params
);
......
......@@ -19,9 +19,11 @@ class HrContract extends Model
'employee_profile_id', 'contract_number', 'contract_type',
'start_date', 'end_date', 'probation_months',
'working_hours_per_day', 'working_days_per_week',
'notice_period_months', 'basic_salary', 'total_salary',
'salary_structure_id', 'previous_contract_id',
'signed_copy_document_id', 'status', 'notes',
'notice_period_months', 'basic_salary', 'total_package',
'terms_json', 'renewal_of_contract_id',
'workflow_instance_id', 'signed_date',
'signed_by_employee', 'signed_by_employer',
'document_path', 'status', 'notes',
];
public static function getContractTypes(): array
......@@ -58,7 +60,7 @@ class HrContract extends Model
$db = App::getInstance()->db();
$futureDate = date('Y-m-d', strtotime("+{$days} days"));
return $db->select(
"SELECT c.*, p.full_name_ar, p.employee_number
"SELECT c.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number
FROM hr_contracts c
JOIN hr_employee_profiles p ON p.id = c.employee_profile_id
WHERE c.status = 'active'
......@@ -80,7 +82,7 @@ class HrContract extends Model
if (!empty($filters['q'])) {
$search = '%' . $filters['q'] . '%';
$where .= ' AND (p.full_name_ar LIKE ? OR c.contract_number LIKE ?)';
$where .= ' AND (CONCAT(p.first_name_ar, \' \', p.last_name_ar) LIKE ? OR c.contract_number LIKE ?)';
$params[] = $search;
$params[] = $search;
}
......@@ -101,7 +103,7 @@ class HrContract extends Model
$offset = ($page - 1) * $perPage;
$rows = $db->select(
"SELECT c.*, p.full_name_ar, p.employee_number
"SELECT c.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number
FROM hr_contracts c
JOIN hr_employee_profiles p ON p.id = c.employee_profile_id
WHERE {$where}
......
......@@ -16,8 +16,8 @@ class HrDepartment extends Model
protected static bool $autoTrackAuthor = true;
protected static array $fillable = [
'code', 'name_ar', 'name_en', 'parent_id', 'manager_employee_id',
'branch_id', 'description', 'is_active',
'department_code', 'name_ar', 'name_en', 'parent_id', 'manager_employee_id',
'branch_id', 'description_ar', 'is_active', 'sort_order',
];
public static function allActive(): array
......@@ -56,7 +56,7 @@ class HrDepartment extends Model
if (!empty($filters['q'])) {
$search = '%' . $filters['q'] . '%';
$where .= ' AND (d.name_ar LIKE ? OR d.name_en LIKE ? OR d.code LIKE ?)';
$where .= ' AND (d.name_ar LIKE ? OR d.name_en LIKE ? OR d.department_code LIKE ?)';
$params[] = $search;
$params[] = $search;
$params[] = $search;
......
......@@ -114,7 +114,7 @@ class HrDisciplinaryAction extends Model
$offset = ($page - 1) * $perPage;
$rows = $db->select(
"SELECT da.*, p.full_name_ar, p.employee_number
"SELECT da.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number
FROM hr_disciplinary_actions da
JOIN hr_employee_profiles p ON p.id = da.employee_profile_id
WHERE {$where}
......
......@@ -54,7 +54,7 @@ class HrEmployeeDocument extends Model
$db = App::getInstance()->db();
$futureDate = date('Y-m-d', strtotime("+{$days} days"));
return $db->select(
"SELECT d.*, p.full_name_ar, p.employee_number
"SELECT d.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number
FROM hr_employee_documents d
JOIN hr_employee_profiles p ON p.id = d.employee_profile_id
WHERE d.expiry_date IS NOT NULL
......@@ -93,7 +93,7 @@ class HrEmployeeDocument extends Model
$offset = ($page - 1) * $perPage;
$rows = $db->select(
"SELECT d.*, p.full_name_ar, p.employee_number
"SELECT d.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number
FROM hr_employee_documents d
JOIN hr_employee_profiles p ON p.id = d.employee_profile_id
WHERE {$where}
......
......@@ -104,7 +104,7 @@ class HrEmployeeLoan extends Model
$offset = ($page - 1) * $perPage;
$rows = $db->select(
"SELECT el.*, p.full_name_ar, p.employee_number
"SELECT el.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number
FROM hr_employee_loans el
JOIN hr_employee_profiles p ON p.id = el.employee_profile_id
WHERE {$where}
......
......@@ -16,17 +16,19 @@ class HrEmployeeProfile extends Model
protected static bool $autoTrackAuthor = true;
protected static array $fillable = [
'employee_id', 'employee_number', 'national_id', 'full_name_ar', 'full_name_en',
'employee_id', 'employee_number', 'national_id',
'first_name_ar', 'last_name_ar', 'first_name_en', 'last_name_en',
'date_of_birth', 'gender', 'marital_status', 'religion', 'nationality',
'phone', 'email', 'address',
'department_id', 'job_title_id', 'salary_structure_id',
'hire_date', 'probation_end_date', 'employment_type', 'employment_status',
'department_id', 'job_title_id', 'direct_manager_id', 'salary_structure_id',
'hire_date', 'probation_end_date', 'probation_status',
'employment_type', 'employment_status',
'basic_salary', 'insurable_salary',
'bank_name', 'bank_account_number', 'bank_iban',
'insurance_number', 'insurance_start_date',
'military_status', 'has_disability', 'disability_description',
'emergency_contact_name', 'emergency_contact_phone', 'emergency_contact_relation',
'notes',
'insurance_number', 'insurance_start_date', 'tax_card_number',
'military_status', 'has_disability', 'disability_details',
'emergency_contact_name', 'emergency_contact_phone',
'photo_path', 'notes',
];
public static function getEmploymentStatuses(): array
......@@ -97,7 +99,7 @@ class HrEmployeeProfile extends Model
{
return static::query()
->where('employment_status', '=', 'active')
->orderBy('full_name_ar', 'ASC')
->orderBy('first_name_ar', 'ASC')
->get();
}
......@@ -118,7 +120,9 @@ class HrEmployeeProfile extends Model
if (!empty($filters['q'])) {
$search = '%' . $filters['q'] . '%';
$where .= ' AND (p.full_name_ar LIKE ? OR p.full_name_en LIKE ? OR p.employee_number LIKE ? OR p.national_id LIKE ?)';
$where .= ' AND (p.first_name_ar LIKE ? OR p.last_name_ar LIKE ? OR p.first_name_en LIKE ? OR p.last_name_en LIKE ? OR p.employee_number LIKE ? OR p.national_id LIKE ?)';
$params[] = $search;
$params[] = $search;
$params[] = $search;
$params[] = $search;
$params[] = $search;
......@@ -147,7 +151,7 @@ class HrEmployeeProfile extends Model
LEFT JOIN hr_departments d ON d.id = p.department_id
LEFT JOIN hr_job_titles j ON j.id = p.job_title_id
WHERE {$where}
ORDER BY p.full_name_ar ASC
ORDER BY p.first_name_ar ASC
LIMIT {$perPage} OFFSET {$offset}",
$params
);
......
......@@ -15,8 +15,8 @@ class HrEmployeeSalaryDetail extends Model
protected static bool $autoTrackAuthor = true;
protected static array $fillable = [
'employee_profile_id', 'component_id', 'amount', 'effective_from',
'effective_to', 'notes',
'employee_profile_id', 'component_id', 'amount', 'percentage',
'effective_from', 'effective_to', 'is_active',
];
public static function getForEmployee(int $profileId, ?string $asOfDate = null): array
......
......@@ -77,7 +77,7 @@ class HrEndOfService extends Model
$offset = ($page - 1) * $perPage;
$rows = $db->select(
"SELECT eos.*, p.full_name_ar, p.employee_number
"SELECT eos.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number
FROM hr_end_of_service eos
JOIN hr_employee_profiles p ON p.employee_id = eos.employee_id
WHERE {$where}
......
......@@ -19,18 +19,18 @@ class HrInsuranceRecord extends Model
'basic_insurable_salary', 'variable_insurable_salary',
'employee_basic_share', 'employee_variable_share', 'total_employee_share',
'employer_basic_share', 'employer_variable_share', 'total_employer_share',
'total_contribution', 'notes',
'total_contribution', 'insurance_number', 'notes',
];
public static function getForPeriod(int $periodId): array
{
$db = App::getInstance()->db();
return $db->select(
"SELECT ir.*, p.full_name_ar, p.employee_number, p.insurance_number
"SELECT ir.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number, p.insurance_number
FROM hr_insurance_records ir
JOIN hr_employee_profiles p ON p.id = ir.employee_profile_id
WHERE ir.period_id = ?
ORDER BY p.full_name_ar ASC",
ORDER BY p.first_name_ar ASC",
[$periodId]
);
}
......@@ -62,8 +62,8 @@ class HrInsuranceRecord extends Model
$row = $db->selectOne(
"SELECT
COUNT(*) as employee_count,
COALESCE(SUM(total_employee_share), 0) as total_employee,
COALESCE(SUM(total_employer_share), 0) as total_employer,
COALESCE(SUM(total_employee_share), 0) as total_employee_share,
COALESCE(SUM(total_employer_share), 0) as total_employer_share,
COALESCE(SUM(total_contribution), 0) as grand_total
FROM hr_insurance_records WHERE period_id = ?",
[$periodId]
......
......@@ -16,8 +16,8 @@ class HrJobTitle extends Model
protected static bool $autoTrackAuthor = true;
protected static array $fillable = [
'code', 'name_ar', 'name_en', 'grade_level', 'min_salary',
'max_salary', 'description', 'is_active',
'title_code', 'name_ar', 'name_en', 'grade_level', 'min_salary',
'max_salary', 'description_ar', 'is_active',
];
public static function allActive(): array
......@@ -37,7 +37,7 @@ class HrJobTitle extends Model
if (!empty($filters['q'])) {
$search = '%' . $filters['q'] . '%';
$where .= ' AND (name_ar LIKE ? OR name_en LIKE ? OR code LIKE ?)';
$where .= ' AND (name_ar LIKE ? OR name_en LIKE ? OR title_code LIKE ?)';
$params[] = $search;
$params[] = $search;
$params[] = $search;
......
......@@ -17,7 +17,7 @@ class HrLeaveBalance extends Model
protected static array $fillable = [
'employee_profile_id', 'leave_type_id', 'year',
'entitled_days', 'carried_over_days', 'adjustment_days',
'used_days', 'pending_days', 'notes',
'used_days', 'pending_days', 'adjustment_reason',
];
public static function getForEmployee(int $profileId, int $year): array
......
......@@ -17,9 +17,10 @@ class HrLeaveRequest extends Model
protected static array $fillable = [
'employee_profile_id', 'leave_type_id', 'start_date', 'end_date',
'total_days', 'is_half_day', 'half_day_period', 'reason',
'attachment_path', 'status', 'workflow_instance_id',
'approved_by', 'approved_at', 'rejection_reason', 'notes',
'total_days', 'half_day', 'half_day_type', 'reason',
'document_path', 'status', 'workflow_instance_id',
'approved_by', 'approved_at', 'rejection_reason',
'return_date', 'notes',
];
public static function getStatuses(): array
......@@ -73,7 +74,7 @@ class HrLeaveRequest extends Model
$offset = ($page - 1) * $perPage;
$rows = $db->select(
"SELECT lr.*, p.full_name_ar, p.employee_number, lt.name_ar as leave_type_name, d.name_ar as department_name
"SELECT lr.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number, lt.name_ar as leave_type_name, d.name_ar as department_name
FROM hr_leave_requests lr
JOIN hr_employee_profiles p ON p.id = lr.employee_profile_id
JOIN hr_leave_types lt ON lt.id = lr.leave_type_id
......@@ -91,7 +92,7 @@ class HrLeaveRequest extends Model
{
$db = App::getInstance()->db();
return $db->select(
"SELECT lr.*, p.full_name_ar, p.employee_number, lt.name_ar as leave_type_name
"SELECT lr.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number, lt.name_ar as leave_type_name
FROM hr_leave_requests lr
JOIN hr_employee_profiles p ON p.id = lr.employee_profile_id
JOIN hr_leave_types lt ON lt.id = lr.leave_type_id
......
......@@ -16,7 +16,7 @@ class HrLoanInstallment extends Model
protected static array $fillable = [
'loan_id', 'installment_number', 'due_date', 'amount',
'paid_amount', 'paid_date', 'payroll_run_id', 'status', 'notes',
'payroll_run_id', 'deducted_at', 'status', 'notes',
];
public static function getForLoan(int $loanId): array
......@@ -36,14 +36,13 @@ class HrLoanInstallment extends Model
);
}
public static function markPaid(int $installmentId, string $amount, ?int $payrollRunId = null): void
public static function markDeducted(int $installmentId, ?int $payrollRunId = null): void
{
$db = App::getInstance()->db();
$db->update('hr_loan_installments', [
'paid_amount' => $amount,
'paid_date' => date('Y-m-d'),
'payroll_run_id' => $payrollRunId,
'status' => 'paid',
'deducted_at' => date('Y-m-d H:i:s'),
'status' => 'deducted',
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [$installmentId]);
}
......
......@@ -30,12 +30,12 @@ class HrPayrollRun extends Model
{
$db = App::getInstance()->db();
return $db->select(
"SELECT pr.*, p.full_name_ar, p.employee_number, d.name_ar as department_name
"SELECT pr.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number, d.name_ar as department_name
FROM hr_payroll_runs pr
JOIN hr_employee_profiles p ON p.id = pr.employee_profile_id
LEFT JOIN hr_departments d ON d.id = p.department_id
WHERE pr.period_id = ?
ORDER BY p.full_name_ar ASC",
ORDER BY p.first_name_ar ASC",
[$periodId]
);
}
......@@ -65,7 +65,7 @@ class HrPayrollRun extends Model
{
$db = App::getInstance()->db();
return $db->select(
"SELECT * FROM hr_payroll_components_log WHERE payroll_run_id = ? ORDER BY sort_order ASC",
"SELECT *, name_ar as component_name FROM hr_payroll_components_log WHERE payroll_run_id = ? ORDER BY sort_order ASC",
[$runId]
);
}
......
......@@ -50,14 +50,14 @@ class HrPerformanceCycle extends Model
{
$db = App::getInstance()->db();
return $db->select(
"SELECT pr.*, p.full_name_ar, p.employee_number, d.name_ar as department_name,
rev.full_name_ar as reviewer_name
"SELECT pr.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number, d.name_ar as department_name,
CONCAT(rev.first_name_ar, ' ', rev.last_name_ar) as reviewer_name
FROM hr_performance_reviews pr
JOIN hr_employee_profiles p ON p.employee_id = pr.employee_id
LEFT JOIN hr_departments d ON d.id = p.department_id
LEFT JOIN hr_employee_profiles rev ON rev.employee_id = pr.reviewer_id
WHERE pr.cycle_id = ?
ORDER BY p.full_name_ar ASC",
ORDER BY p.first_name_ar ASC",
[$cycleId]
);
}
......
......@@ -58,7 +58,7 @@ class HrPerformanceReview extends Model
$db = App::getInstance()->db();
return $db->select(
"SELECT pr.*, pc.cycle_code, pc.name_ar as cycle_name, pc.year,
rev.full_name_ar as reviewer_name
CONCAT(rev.first_name_ar, ' ', rev.last_name_ar) as reviewer_name
FROM hr_performance_reviews pr
JOIN hr_performance_cycles pc ON pc.id = pr.cycle_id
LEFT JOIN hr_employee_profiles rev ON rev.employee_id = pr.reviewer_id
......
......@@ -80,7 +80,7 @@ class HrSalaryAdjustment extends Model
$offset = ($page - 1) * $perPage;
$rows = $db->select(
"SELECT sa.*, p.full_name_ar, p.employee_number
"SELECT sa.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number
FROM hr_salary_adjustments sa
JOIN hr_employee_profiles p ON p.employee_id = sa.employee_id
WHERE {$where}
......
......@@ -26,11 +26,11 @@ class HrTaxRecord extends Model
{
$db = App::getInstance()->db();
return $db->select(
"SELECT tr.*, p.full_name_ar, p.employee_number
"SELECT tr.*, CONCAT(p.first_name_ar, ' ', p.last_name_ar) as full_name_ar, p.employee_number
FROM hr_tax_records tr
JOIN hr_employee_profiles p ON p.employee_id = tr.employee_id
WHERE tr.period_id = ?
ORDER BY p.full_name_ar ASC",
ORDER BY p.first_name_ar ASC",
[$periodId]
);
}
......
......@@ -36,17 +36,15 @@ final class AttendanceService
}
$scheduleData = self::getScheduleForEmployee($profileId, $date);
$expectedHours = self::getExpectedHours($profile, $scheduleData, $date);
$isRamadan = self::isRamadanDay($date, $profile->religion ?? null);
if ($existing) {
$db->update('hr_attendance', [
'check_in_time' => $time,
'check_in_method' => $method,
'expected_hours' => $expectedHours,
'is_ramadan' => $isRamadan ? 1 : 0,
'status' => 'present',
'updated_at' => date('Y-m-d H:i:s'),
'check_in_time' => $time,
'check_in_method' => $method,
'is_ramadan_schedule' => $isRamadan ? 1 : 0,
'status' => 'present',
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [(int) $existing['id']]);
} else {
$isHoliday = HrHoliday::isHoliday($date, $profile->religion);
......@@ -59,12 +57,10 @@ final class AttendanceService
$db->insert('hr_attendance', [
'employee_profile_id' => $profileId,
'attendance_date' => $date,
'schedule_id' => $scheduleData['id'] ?? null,
'check_in_time' => $time,
'check_in_method' => $method,
'expected_hours' => $expectedHours,
'status' => $status,
'is_ramadan' => $isRamadan ? 1 : 0,
'is_ramadan_schedule' => $isRamadan ? 1 : 0,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
......@@ -92,19 +88,20 @@ final class AttendanceService
$checkOut->modify('+1 day');
}
$diffMinutes = (int) (($checkOut->getTimestamp() - $checkIn->getTimestamp()) / 60);
$breakMinutes = (int) ($existing['break_minutes'] ?? 60);
$scheduleData = self::getScheduleForEmployee($profileId, $date);
$breakMinutes = (int) ($scheduleData['break_duration_minutes'] ?? 60);
$actualMinutes = max(0, $diffMinutes - $breakMinutes);
$actualHours = number_format($actualMinutes / 60, 2, '.', '');
$actualHours = bcdiv((string) $actualMinutes, '60', 2);
// Calculate overtime
$expectedHours = (float) ($existing['expected_hours'] ?? 8);
$profile = HrEmployeeProfile::find($profileId);
$expectedHours = (float) self::getExpectedHours($profile, $scheduleData, $date);
$overtimeHours = '0.00';
if ((float) $actualHours > $expectedHours) {
$overtimeHours = number_format((float) $actualHours - $expectedHours, 2, '.', '');
if (bccomp($actualHours, (string) $expectedHours, 2) > 0) {
$overtimeHours = bcsub($actualHours, (string) $expectedHours, 2);
}
// Calculate late minutes
$scheduleData = self::getScheduleForEmployee($profileId, $date);
$lateMinutes = 0;
$earlyLeaveMinutes = 0;
......
......@@ -57,9 +57,8 @@ final class ContractService
'working_days_per_week' => $data['working_days_per_week'] ?? 6,
'notice_period_months' => $data['notice_period_months'] ?? 2,
'basic_salary' => $data['basic_salary'] ?? '0.00',
'total_salary' => $data['total_salary'] ?? '0.00',
'salary_structure_id' => $data['salary_structure_id'] ?? null,
'previous_contract_id' => $data['previous_contract_id'] ?? null,
'total_package' => $data['total_package'] ?? '0.00',
'renewal_of_contract_id' => $data['renewal_of_contract_id'] ?? null,
'status' => 'draft',
'notes' => $data['notes'] ?? null,
'created_at' => date('Y-m-d H:i:s'),
......@@ -111,9 +110,8 @@ final class ContractService
'working_days_per_week' => $data['working_days_per_week'] ?? $contract['working_days_per_week'],
'notice_period_months' => $data['notice_period_months'] ?? $contract['notice_period_months'],
'basic_salary' => $data['basic_salary'] ?? $contract['basic_salary'],
'total_salary' => $data['total_salary'] ?? $contract['total_salary'],
'salary_structure_id' => $data['salary_structure_id'] ?? $contract['salary_structure_id'],
'previous_contract_id' => $contractId,
'total_package' => $data['total_package'] ?? $contract['total_package'],
'renewal_of_contract_id' => $contractId,
'status' => 'active',
'notes' => $data['notes'] ?? null,
'created_at' => date('Y-m-d H:i:s'),
......
......@@ -75,7 +75,7 @@ final class LeaveService
// Calculate total days
$totalDays = self::calculateBusinessDays($startDate, $endDate, $profileId);
$isHalfDay = (int) ($data['is_half_day'] ?? 0);
$isHalfDay = (int) ($data['half_day'] ?? 0);
if ($isHalfDay) {
$totalDays = '0.5';
}
......@@ -122,10 +122,10 @@ final class LeaveService
'start_date' => $startDate,
'end_date' => $endDate,
'total_days' => $totalDays,
'is_half_day' => $isHalfDay,
'half_day_period' => $data['half_day_period'] ?? null,
'half_day' => $isHalfDay,
'half_day_type' => $data['half_day_type'] ?? null,
'reason' => $data['reason'] ?? null,
'attachment_path' => $data['attachment_path'] ?? null,
'document_path' => $data['document_path'] ?? null,
'status' => 'pending',
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
......@@ -191,7 +191,7 @@ final class LeaveService
EventBus::dispatch('hr.leave.approved', [
'request_id' => $requestId,
'employee_id' => $profile ? $profile->employee_id : null,
'employee_name'=> $profile ? $profile->full_name_ar : '',
'employee_name'=> $profile ? trim(($profile->first_name_ar ?? '') . ' ' . ($profile->last_name_ar ?? '')) : '',
'leave_type' => $request['leave_type_id'],
'leave_start' => $request['start_date'],
'leave_end' => $request['end_date'],
......@@ -243,7 +243,7 @@ final class LeaveService
EventBus::dispatch('hr.leave.rejected', [
'request_id' => $requestId,
'employee_id' => $profile ? $profile->employee_id : null,
'employee_name' => $profile ? $profile->full_name_ar : '',
'employee_name' => $profile ? trim(($profile->first_name_ar ?? '') . ' ' . ($profile->last_name_ar ?? '')) : '',
]);
return ['success' => true];
......
......@@ -74,7 +74,6 @@ final class LoanService
'installment_number' => $i,
'due_date' => $dueDate,
'amount' => $amount,
'paid_amount' => '0.00',
'status' => 'pending',
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
......@@ -130,7 +129,7 @@ final class LoanService
$db->update('hr_employee_loans', [
'status' => 'disbursed',
'disbursed_date' => date('Y-m-d'),
'disbursed_date' => date('Y-m-d'),
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [$loanId]);
......@@ -153,7 +152,7 @@ final class LoanService
$db->beginTransaction();
try {
HrLoanInstallment::markPaid((int) $nextInstallment['id'], $amount, $payrollRunId);
HrLoanInstallment::markDeducted((int) $nextInstallment['id'], $payrollRunId);
$newRemaining = bcsub($loan['remaining_amount'], $amount, 2);
$newPaid = (int) $loan['paid_installments'] + 1;
......
......@@ -24,7 +24,7 @@
</div>
<div>
<label style="display:block;margin-bottom:4px;font-size:13px;">الكود <span style="color:#DC2626;">*</span></label>
<input type="text" name="code" value="<?= e(old('code') ?? ($isEdit ? $department->code : '')) ?>" class="form-control" required>
<input type="text" name="department_code" value="<?= e(old('department_code') ?? ($isEdit ? $department->department_code : '')) ?>" class="form-control" required>
</div>
<div>
<label style="display:block;margin-bottom:4px;font-size:13px;">القسم الأب</label>
......@@ -38,10 +38,10 @@
</div>
<div>
<label style="display:block;margin-bottom:4px;font-size:13px;">المدير</label>
<select name="manager_id" class="form-control">
<select name="manager_employee_id" class="form-control">
<option value="">-- بدون --</option>
<?php foreach ($employees as $emp): ?>
<option value="<?= (int) $emp['id'] ?>" <?= (old('manager_id') ?? ($isEdit ? $department->manager_id : '')) == $emp['id'] ? 'selected' : '' ?>><?= e($emp['first_name_ar'] . ' ' . $emp['last_name_ar']) ?></option>
<option value="<?= (int) $emp['id'] ?>" <?= (old('manager_employee_id') ?? ($isEdit ? $department->manager_employee_id : '')) == $emp['id'] ? 'selected' : '' ?>><?= e($emp['first_name_ar'] . ' ' . $emp['last_name_ar']) ?></option>
<?php endforeach; ?>
</select>
</div>
......
......@@ -16,7 +16,7 @@
</h3>
</div>
<div style="padding:16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;">
<div><span style="color:#6B7280;font-size:13px;">الكود</span><div style="font-weight:600;"><?= e($department->code) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">الكود</span><div style="font-weight:600;"><?= e($department->department_code) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">الاسم بالعربي</span><div style="font-weight:600;"><?= e($department->name_ar) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">الاسم بالإنجليزي</span><div><?= e($department->name_en ?? '-') ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">القسم الأب</span><div><?= $parent ? '<a href="/hr/departments/' . (int) $parent->id . '">' . e($parent->name_ar) . '</a>' : '-' ?></div></div>
......@@ -44,7 +44,7 @@
<tbody>
<?php foreach ($children as $child): ?>
<tr>
<td><code><?= e($child['code']) ?></code></td>
<td><code><?= e($child['department_code']) ?></code></td>
<td><a href="/hr/departments/<?= (int) $child['id'] ?>"><?= e($child['name_ar']) ?></a></td>
</tr>
<?php endforeach; ?>
......
......@@ -39,7 +39,7 @@
<?php
$current = null;
foreach ($salaryDetails as $sd) {
if ((int) $sd['salary_component_id'] === (int) $comp['id']) {
if ((int) $sd['component_id'] === (int) $comp['id']) {
$current = $sd;
break;
}
......
......@@ -9,7 +9,7 @@
<div style="padding:16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:16px;">
<div><label style="display:block;margin-bottom:4px;font-size:13px;">الاسم بالعربي <span style="color:#DC2626;">*</span></label><input type="text" name="name_ar" value="<?= e(old('name_ar') ?? ($isEdit ? $holiday->name_ar : '')) ?>" class="form-control" required></div>
<div><label style="display:block;margin-bottom:4px;font-size:13px;">الاسم بالإنجليزي</label><input type="text" name="name_en" value="<?= e(old('name_en') ?? ($isEdit ? $holiday->name_en : '')) ?>" class="form-control"></div>
<div><label style="display:block;margin-bottom:4px;font-size:13px;">التاريخ <span style="color:#DC2626;">*</span></label><input type="date" name="holiday_date" value="<?= e(old('holiday_date') ?? ($isEdit ? $holiday->holiday_date : '')) ?>" class="form-control" required></div>
<div><label style="display:block;margin-bottom:4px;font-size:13px;">التاريخ <span style="color:#DC2626;">*</span></label><input type="date" name="date" value="<?= e(old('date') ?? ($isEdit ? $holiday->date : '')) ?>" class="form-control" required></div>
<div><label style="display:block;margin-bottom:4px;font-size:13px;">المدة (أيام)</label><input type="number" name="duration_days" value="<?= e(old('duration_days') ?? ($isEdit ? $holiday->duration_days : '1')) ?>" class="form-control" min="1"></div>
<div><label style="display:block;margin-bottom:4px;font-size:13px;">النوع</label>
<select name="holiday_type" class="form-control">
......
......@@ -22,7 +22,7 @@
<?php foreach ($holidays as $h): ?>
<tr>
<td><?= e($h['name_ar']) ?></td>
<td><?= e($h['holiday_date']) ?></td>
<td><?= e($h['date']) ?></td>
<td><?= (int) $h['duration_days'] ?> يوم</td>
<td><?= e($typeLabels[$h['holiday_type']] ?? $h['holiday_type']) ?></td>
<td><?= $h['is_recurring'] ? 'نعم' : 'لا' ?></td>
......
......@@ -13,8 +13,8 @@
<td><?= e($mn[(int) ($r['month'] ?? 0)] ?? '-') ?></td>
<td><?= number_format((float) $r['basic_insurable_salary'], 2) ?></td>
<td><?= number_format((float) $r['variable_insurable_salary'], 2) ?></td>
<td><?= number_format((float) $r['employee_share_total'], 2) ?></td>
<td><?= number_format((float) $r['employer_share_total'], 2) ?></td>
<td><?= number_format((float) $r['total_employee_share'], 2) ?></td>
<td><?= number_format((float) $r['total_employer_share'], 2) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
......
......@@ -21,8 +21,8 @@
<td><?= e($r['insurance_number'] ?? '-') ?></td>
<td><?= number_format((float) $r['basic_insurable_salary'], 2) ?></td>
<td><?= number_format((float) $r['variable_insurable_salary'], 2) ?></td>
<td><?= number_format((float) $r['employee_share_total'], 2) ?></td>
<td><?= number_format((float) $r['employer_share_total'], 2) ?></td>
<td><?= number_format((float) $r['total_employee_share'], 2) ?></td>
<td><?= number_format((float) $r['total_employer_share'], 2) ?></td>
</tr>
<?php endforeach; ?>
<?php if ($totals): ?>
......
......@@ -19,8 +19,8 @@
<td><a href="/hr/insurance/employee/<?= (int) $r['employee_profile_id'] ?>"><?= e(($r['first_name_ar'] ?? '') . ' ' . ($r['last_name_ar'] ?? '')) ?></a></td>
<td><?= number_format((float) $r['basic_insurable_salary'], 2) ?></td>
<td><?= number_format((float) $r['variable_insurable_salary'], 2) ?></td>
<td><?= number_format((float) $r['employee_share_total'], 2) ?></td>
<td><?= number_format((float) $r['employer_share_total'], 2) ?></td>
<td><?= number_format((float) $r['total_employee_share'], 2) ?></td>
<td><?= number_format((float) $r['total_employer_share'], 2) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
......
......@@ -21,7 +21,7 @@
</div>
<div>
<label style="display:block;margin-bottom:4px;font-size:13px;">الكود <span style="color:#DC2626;">*</span></label>
<input type="text" name="code" value="<?= e(old('code') ?? ($isEdit ? $jobTitle->code : '')) ?>" class="form-control" required>
<input type="text" name="title_code" value="<?= e(old('title_code') ?? ($isEdit ? $jobTitle->title_code : '')) ?>" class="form-control" required>
</div>
<div>
<label style="display:block;margin-bottom:4px;font-size:13px;">الدرجة الوظيفية</label>
......@@ -37,7 +37,7 @@
</div>
<div style="grid-column:1/-1;">
<label style="display:block;margin-bottom:4px;font-size:13px;">الوصف</label>
<textarea name="description" class="form-control" rows="3"><?= e(old('description') ?? ($isEdit ? $jobTitle->description : '')) ?></textarea>
<textarea name="description_ar" class="form-control" rows="3"><?= e(old('description_ar') ?? ($isEdit ? $jobTitle->description_ar : '')) ?></textarea>
</div>
</div>
</div>
......
......@@ -35,7 +35,7 @@
<?php $bal = $balancesMatrix[$emp['id']][$lt['id']] ?? null; ?>
<td style="text-align:center;font-size:12px;">
<?php if ($bal): ?>
<span title="المتبقي / المستحق"><?= number_format((float) ($bal['remaining'] ?? 0), 0) ?>/<?= number_format((float) ($bal['entitled_days'] ?? 0), 0) ?></span>
<span title="المتبقي / المستحق"><?= number_format((float) ($bal['remaining_days'] ?? 0), 0) ?>/<?= number_format((float) ($bal['entitled_days'] ?? 0), 0) ?></span>
<?php else: ?>-<?php endif; ?>
</td>
<?php endforeach; ?>
......
......@@ -16,7 +16,7 @@
<td><?= number_format((float) ($b['adjustment_days'] ?? 0), 1) ?></td>
<td><?= number_format((float) ($b['used_days'] ?? 0), 1) ?></td>
<td><?= number_format((float) ($b['pending_days'] ?? 0), 1) ?></td>
<td style="font-weight:700;color:#059669;"><?= number_format((float) ($b['remaining'] ?? 0), 1) ?></td>
<td style="font-weight:700;color:#059669;"><?= number_format((float) ($b['remaining_days'] ?? 0), 1) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
......
......@@ -18,7 +18,7 @@
</div>
<div><label style="display:block;margin-bottom:4px;font-size:13px;">تاريخ البداية <span style="color:#DC2626;">*</span></label><input type="date" name="start_date" value="<?= e(old('start_date') ?? '') ?>" class="form-control" required></div>
<div><label style="display:block;margin-bottom:4px;font-size:13px;">تاريخ النهاية <span style="color:#DC2626;">*</span></label><input type="date" name="end_date" value="<?= e(old('end_date') ?? '') ?>" class="form-control" required></div>
<div><label style="display:block;margin-bottom:4px;font-size:13px;"><input type="checkbox" name="is_half_day" value="1" <?= old('is_half_day') ? 'checked' : '' ?>> نصف يوم</label></div>
<div><label style="display:block;margin-bottom:4px;font-size:13px;"><input type="checkbox" name="half_day" value="1" <?= old('half_day') ? 'checked' : '' ?>> نصف يوم</label></div>
<div style="grid-column:1/-1;"><label style="display:block;margin-bottom:4px;font-size:13px;">السبب</label><textarea name="reason" class="form-control" rows="3"><?= e(old('reason') ?? '') ?></textarea></div>
</div>
</div>
......
......@@ -18,7 +18,7 @@
<div><span style="color:#6B7280;font-size:13px;">من</span><div><?= e($leaveRequest->start_date) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">إلى</span><div><?= e($leaveRequest->end_date) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">عدد الأيام</span><div style="font-weight:600;"><?= number_format((float) $leaveRequest->total_days, 1) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">نصف يوم</span><div><?= $leaveRequest->is_half_day ? 'نعم' : 'لا' ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">نصف يوم</span><div><?= $leaveRequest->half_day ? 'نعم' : 'لا' ?></div></div>
<?php if ($leaveRequest->reason): ?>
<div style="grid-column:1/-1;"><span style="color:#6B7280;font-size:13px;">السبب</span><div><?= e($leaveRequest->reason) ?></div></div>
<?php endif; ?>
......@@ -34,7 +34,7 @@
<div style="padding:16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:16px;">
<div><span style="color:#6B7280;font-size:13px;">المستحق</span><div style="font-weight:600;"><?= number_format((float) ($balance['entitled_days'] ?? 0), 1) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">المستخدم</span><div style="font-weight:600;"><?= number_format((float) ($balance['used_days'] ?? 0), 1) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">المتبقي</span><div style="font-weight:700;font-size:18px;color:#059669;"><?= number_format((float) ($balance['remaining'] ?? 0), 1) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">المتبقي</span><div style="font-weight:700;font-size:18px;color:#059669;"><?= number_format((float) ($balance['remaining_days'] ?? 0), 1) ?></div></div>
</div>
</div>
<?php endif; ?>
......
......@@ -28,14 +28,14 @@
<table class="data-table">
<thead><tr><th>#</th><th>تاريخ الاستحقاق</th><th>المبلغ</th><th>المدفوع</th><th>الحالة</th></tr></thead>
<tbody>
<?php $instSl = ['pending'=>'معلق','paid'=>'مدفوع','partial'=>'جزئي','overdue'=>'متأخر']; ?>
<?php $instSc = ['pending'=>'#FEF3C7;color:#92400E','paid'=>'#DEF7EC;color:#03543F','partial'=>'#DBEAFE;color:#1E40AF','overdue'=>'#FDE8E8;color:#9B1C1C']; ?>
<?php $instSl = ['pending'=>'معلق','deducted'=>'مخصوم','skipped'=>'تم تخطيه','cancelled'=>'ملغي']; ?>
<?php $instSc = ['pending'=>'#FEF3C7;color:#92400E','deducted'=>'#DEF7EC;color:#03543F','skipped'=>'#DBEAFE;color:#1E40AF','cancelled'=>'#E5E7EB;color:#374151']; ?>
<?php foreach ($installments as $inst): ?>
<tr>
<td><?= (int) $inst['installment_number'] ?></td>
<td><?= e($inst['due_date']) ?></td>
<td><?= number_format((float) $inst['amount'], 2) ?></td>
<td><?= number_format((float) ($inst['paid_amount'] ?? 0), 2) ?></td>
<td><?= $inst['deducted_at'] ? e(date('Y-m-d', strtotime($inst['deducted_at']))) : '-' ?></td>
<td><span style="display:inline-block;padding:2px 8px;border-radius:9999px;font-size:12px;background:<?= $instSc[$inst['status']] ?? '#E5E7EB;color:#374151' ?>;"><?= e($instSl[$inst['status']] ?? $inst['status']) ?></span></td>
</tr>
<?php endforeach; ?>
......
......@@ -13,7 +13,7 @@
<?php foreach ($runs as $r): ?>
<tr>
<td><?= e(($mn[(int) $r['month']] ?? '') . ' ' . $r['year']) ?></td>
<td style="color:#059669;"><?= number_format((float) $r['gross_salary'], 2) ?></td>
<td style="color:#059669;"><?= number_format((float) $r['gross_earnings'], 2) ?></td>
<td style="color:#DC2626;"><?= number_format((float) $r['total_deductions'], 2) ?></td>
<td style="font-weight:700;"><?= number_format((float) $r['net_salary'], 2) ?></td>
<td><a href="/hr/payroll/runs/<?= (int) $r['id'] ?>/slip" style="color:#2563EB;"><i data-lucide="file-text" style="width:16px;height:16px;"></i></a></td>
......
......@@ -46,7 +46,7 @@
<tr>
<td><a href="/hr/employees/<?= (int) $r['employee_profile_id'] ?>"><?= e(($r['first_name_ar'] ?? '') . ' ' . ($r['last_name_ar'] ?? '')) ?></a></td>
<td><?= number_format((float) $r['basic_salary'], 2) ?></td>
<td style="color:#059669;font-weight:600;"><?= number_format((float) $r['gross_salary'], 2) ?></td>
<td style="color:#059669;font-weight:600;"><?= number_format((float) $r['gross_earnings'], 2) ?></td>
<td style="color:#DC2626;"><?= number_format((float) $r['total_deductions'], 2) ?></td>
<td style="font-weight:700;"><?= number_format((float) $r['net_salary'], 2) ?></td>
<td style="white-space:nowrap;">
......
......@@ -7,7 +7,7 @@
<?php $__template->section('content'); ?>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:20px;">
<div class="card" style="padding:16px;text-align:center;"><div style="font-size:13px;color:#6B7280;">الأساسي</div><div style="font-size:18px;font-weight:700;"><?= number_format((float) $run['basic_salary'], 2) ?></div></div>
<div class="card" style="padding:16px;text-align:center;"><div style="font-size:13px;color:#6B7280;">الإجمالي</div><div style="font-size:18px;font-weight:700;color:#059669;"><?= number_format((float) $run['gross_salary'], 2) ?></div></div>
<div class="card" style="padding:16px;text-align:center;"><div style="font-size:13px;color:#6B7280;">الإجمالي</div><div style="font-size:18px;font-weight:700;color:#059669;"><?= number_format((float) $run['gross_earnings'], 2) ?></div></div>
<div class="card" style="padding:16px;text-align:center;"><div style="font-size:13px;color:#6B7280;">الخصومات</div><div style="font-size:18px;font-weight:700;color:#DC2626;"><?= number_format((float) $run['total_deductions'], 2) ?></div></div>
<div class="card" style="padding:16px;text-align:center;"><div style="font-size:13px;color:#6B7280;">الصافي</div><div style="font-size:24px;font-weight:700;color:#2563EB;"><?= number_format((float) $run['net_salary'], 2) ?></div></div>
</div>
......@@ -45,8 +45,8 @@
<div class="card" style="margin-bottom:20px;">
<div style="padding:16px;border-bottom:1px solid #E5E7EB;"><h3 style="margin:0;font-size:16px;">التأمينات الاجتماعية</h3></div>
<div style="padding:16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;">
<div><span style="color:#6B7280;font-size:13px;">حصة الموظف</span><div style="font-weight:600;"><?= number_format((float) $insurance['employee_share_total'], 2) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">حصة صاحب العمل</span><div style="font-weight:600;"><?= number_format((float) $insurance['employer_share_total'], 2) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">حصة الموظف</span><div style="font-weight:600;"><?= number_format((float) $insurance['total_employee_share'], 2) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">حصة صاحب العمل</span><div style="font-weight:600;"><?= number_format((float) $insurance['total_employer_share'], 2) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">الأساسي المؤمن عليه</span><div><?= number_format((float) $insurance['basic_insurable_salary'], 2) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">المتغير المؤمن عليه</span><div><?= number_format((float) $insurance['variable_insurable_salary'], 2) ?></div></div>
</div>
......@@ -57,9 +57,9 @@
<div class="card" style="margin-bottom:20px;">
<div style="padding:16px;border-bottom:1px solid #E5E7EB;"><h3 style="margin:0;font-size:16px;">ضريبة الدخل</h3></div>
<div style="padding:16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;">
<div><span style="color:#6B7280;font-size:13px;">الدخل الخاضع للضريبة</span><div style="font-weight:600;"><?= number_format((float) $tax['gross_taxable_income'], 2) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">الإعفاءات</span><div><?= number_format((float) $tax['total_exemptions'], 2) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">مبلغ الضريبة</span><div style="font-weight:700;color:#DC2626;"><?= number_format((float) $tax['tax_amount'], 2) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">الدخل الخاضع للضريبة</span><div style="font-weight:600;"><?= number_format((float) ($tax['gross_taxable_monthly'] ?? 0), 2) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">الإعفاءات</span><div><?= number_format(((float) ($tax['personal_exemption'] ?? 0)) + ((float) ($tax['insurance_exemption'] ?? 0)), 2) ?></div></div>
<div><span style="color:#6B7280;font-size:13px;">مبلغ الضريبة</span><div style="font-weight:700;color:#DC2626;"><?= number_format((float) ($tax['monthly_tax'] ?? 0), 2) ?></div></div>
</div>
</div>
<?php endif; ?>
......
......@@ -5,8 +5,8 @@
<?php $__template->section('content'); ?>
<?php
$daysConfig = [];
if ($isEdit && $schedule->days_config) {
$daysConfig = json_decode($schedule->days_config, true) ?: [];
if ($isEdit && $schedule->days_config_json) {
$daysConfig = json_decode($schedule->days_config_json, true) ?: [];
}
$dayLabels = ['saturday'=>'السبت','sunday'=>'الأحد','monday'=>'الاثنين','tuesday'=>'الثلاثاء','wednesday'=>'الأربعاء','thursday'=>'الخميس','friday'=>'الجمعة'];
?>
......
......@@ -17,9 +17,9 @@
<?php foreach ($records as $r): ?>
<tr>
<td><?= e($mn[(int) ($r['month'] ?? 0)] ?? '-') ?></td>
<td><?= number_format((float) $r['gross_taxable_income'], 2) ?></td>
<td><?= number_format((float) $r['total_exemptions'], 2) ?></td>
<td style="font-weight:600;color:#DC2626;"><?= number_format((float) $r['tax_amount'], 2) ?></td>
<td><?= number_format((float) $r['gross_taxable_monthly'], 2) ?></td>
<td><?= number_format(((float) ($r['personal_exemption'] ?? 0)) + ((float) ($r['insurance_exemption'] ?? 0)), 2) ?></td>
<td style="font-weight:600;color:#DC2626;"><?= number_format((float) $r['monthly_tax'], 2) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
......
......@@ -27,28 +27,28 @@ class HrAttendanceAutoCloseJob
// Find attendance records from yesterday (or older) with check_in but no check_out
$openRecords = $this->db->select(
"SELECT a.id, a.employee_profile_id, a.attendance_date, a.check_in,
"SELECT a.id, a.employee_profile_id, a.attendance_date, a.check_in_time,
es.schedule_id,
ws.days_config
ws.days_config_json
FROM hr_attendance a
LEFT JOIN hr_employee_schedules es ON es.employee_profile_id = a.employee_profile_id
AND es.is_active = 1 AND es.is_archived = 0
AND es.is_active = 1
LEFT JOIN hr_work_schedules ws ON ws.id = es.schedule_id AND ws.is_archived = 0
WHERE a.check_in IS NOT NULL
AND a.check_out IS NULL
WHERE a.check_in_time IS NOT NULL
AND a.check_out_time IS NULL
AND a.attendance_date <= ?
AND a.is_archived = 0",
[$yesterday]
);
foreach ($openRecords as $rec) {
$checkIn = $rec['check_in'];
$checkIn = $rec['check_in_time'];
$attDate = $rec['attendance_date'];
// Determine check_out time: try schedule end time, fallback to 8 hours after check_in
$checkOut = null;
if (!empty($rec['days_config'])) {
$daysConfig = json_decode($rec['days_config'], true);
if (!empty($rec['days_config_json'])) {
$daysConfig = json_decode($rec['days_config_json'], true);
if (is_array($daysConfig)) {
$dayOfWeek = (int) date('w', strtotime($attDate)); // 0=Sun, 6=Sat
$dayKey = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday'][$dayOfWeek] ?? '';
......@@ -72,7 +72,7 @@ class HrAttendanceAutoCloseJob
$actualHours = round(($outTime - $inTime) / 3600, 2);
$this->db->update('hr_attendance', [
'check_out' => $checkOut,
'check_out_time' => $checkOut,
'actual_hours' => $actualHours,
'notes' => 'تم إغلاق الحضور تلقائياً - لم يسجل الموظف انصرافه',
'updated_at' => date('Y-m-d H:i:s'),
......
......@@ -61,7 +61,7 @@ class HrLeaveBalanceResetJob
$leaveTypeId = (int) $lt['id'];
// Calculate entitlement based on Egyptian law for annual leave
$entitled = (float) ($lt['default_days'] ?? 0);
$entitled = (float) ($lt['default_days_per_year'] ?? 0);
if (($lt['code'] ?? '') === 'annual') {
if ($yearsOfService >= 10 || $age >= 50) {
$entitled = 30.0;
......@@ -74,15 +74,16 @@ class HrLeaveBalanceResetJob
// Calculate carry-over from previous year (max per leave type config)
$carryOver = 0.0;
$maxCarry = (float) ($lt['max_carry_over'] ?? 0);
$maxCarry = (float) ($lt['max_carry_over_days'] ?? 0);
if ($maxCarry > 0) {
$prevBalance = $this->db->selectOne(
"SELECT remaining FROM hr_leave_balances
"SELECT (entitled_days + carried_over_days + adjustment_days - used_days) as remaining_calc
FROM hr_leave_balances
WHERE employee_profile_id = ? AND leave_type_id = ? AND year = ?",
[$profileId, $leaveTypeId, $prevYear]
);
if ($prevBalance) {
$remaining = (float) ($prevBalance['remaining'] ?? 0);
$remaining = (float) ($prevBalance['remaining_calc'] ?? 0);
$carryOver = min($remaining, $maxCarry);
}
}
......@@ -93,11 +94,10 @@ class HrLeaveBalanceResetJob
'leave_type_id' => $leaveTypeId,
'year' => $newYear,
'entitled_days' => $entitled,
'carried_over' => $carryOver,
'carried_over_days' => $carryOver,
'used_days' => 0,
'pending_days' => 0,
'adjustment' => 0,
'remaining' => bcadd((string) $entitled, (string) $carryOver, 2),
'adjustment_days' => 0,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
......
......@@ -37,7 +37,6 @@ class HrLoanDeductionReminderJob
WHERE li.status = 'pending'
AND li.due_date >= ?
AND li.due_date <= ?
AND li.is_archived = 0
AND l.status IN ('disbursed', 'repaying')
AND l.is_archived = 0",
[$today, $monthEnd]
......@@ -67,18 +66,21 @@ class HrLoanDeductionReminderJob
JOIN hr_employee_loans l ON l.id = li.loan_id
WHERE li.status = 'pending'
AND li.due_date < ?
AND li.is_archived = 0
AND l.status IN ('disbursed', 'repaying')
AND l.is_archived = 0",
[$today]
);
foreach ($overdueInstallments as $inst) {
$this->db->update('hr_loan_installments', [
'status' => 'overdue',
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [(int) $inst['id']]);
$overdue++;
$overdue = count($overdueInstallments);
if ($overdue > 0) {
$this->db->insert('notifications', [
'type' => 'hr_loan_overdue',
'title' => 'تنبيه: أقساط سلف متأخرة',
'message' => "يوجد {$overdue} قسط سلفة متأخر عن موعد السداد. يرجى مراجعة حالة السلف.",
'link' => '/hr/loans',
'is_read' => 0,
'created_at' => date('Y-m-d H:i:s'),
]);
}
if ($reminded > 0 || $overdue > 0) {
......
......@@ -5,17 +5,20 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_salary_structures` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`structure_code` VARCHAR(30) NOT NULL,
`code` VARCHAR(30) NOT NULL,
`name_ar` VARCHAR(200) NOT NULL,
`name_en` VARCHAR(200) NULL,
`description_ar` TEXT NULL,
`description` TEXT NULL,
`is_default` TINYINT(1) NOT NULL DEFAULT 0,
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_salary_structures_code` (`structure_code`),
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_salary_structures_code` (`code`),
INDEX `idx_hr_salary_structures_active` (`is_active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
",
......
......@@ -9,15 +9,21 @@ return [
`component_code` VARCHAR(30) NOT NULL,
`name_ar` VARCHAR(200) NOT NULL,
`name_en` VARCHAR(200) NULL,
`component_type` VARCHAR(20) NOT NULL COMMENT 'earning or deduction',
`type` VARCHAR(20) NOT NULL COMMENT 'earning or deduction',
`calculation_type` VARCHAR(30) NOT NULL COMMENT 'fixed, percentage_of_basic, percentage_of_gross, formula',
`default_amount` DECIMAL(15,2) NULL,
`default_percentage` DECIMAL(5,2) NULL,
`percentage_of` VARCHAR(30) NULL COMMENT 'basic, gross, etc.',
`percentage_value` DECIMAL(5,2) NULL,
`formula_json` JSON NULL,
`is_taxable` TINYINT(1) NOT NULL DEFAULT 1,
`is_insurable` TINYINT(1) NOT NULL DEFAULT 1,
`is_basic` TINYINT(1) NOT NULL DEFAULT 0,
`is_variable` TINYINT(1) NOT NULL DEFAULT 0,
`is_system_calculated` TINYINT(1) NOT NULL DEFAULT 0,
`is_mandatory` TINYINT(1) NOT NULL DEFAULT 0,
`affects_insurance_base` TINYINT(1) NOT NULL DEFAULT 0,
`affects_overtime` TINYINT(1) NOT NULL DEFAULT 0,
`sort_order` INT UNSIGNED NOT NULL DEFAULT 0,
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
......@@ -26,9 +32,9 @@ return [
`updated_by` BIGINT UNSIGNED NULL,
INDEX `idx_hr_salary_comp_structure` (`structure_id`),
UNIQUE KEY `uq_hr_salary_comp_code` (`structure_id`, `component_code`),
INDEX `idx_hr_salary_comp_type` (`component_type`),
INDEX `idx_hr_salary_comp_type` (`type`),
CONSTRAINT `fk_hr_salary_comp_structure` FOREIGN KEY (`structure_id`) REFERENCES `hr_salary_structures`(`id`) ON DELETE CASCADE,
CONSTRAINT `chk_hr_salary_comp_type` CHECK (`component_type` IN ('earning', 'deduction')),
CONSTRAINT `chk_hr_salary_comp_type` CHECK (`type` IN ('earning', 'deduction')),
CONSTRAINT `chk_hr_salary_calc_type` CHECK (`calculation_type` IN ('fixed', 'percentage_of_basic', 'percentage_of_gross', 'formula'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
",
......
......@@ -7,6 +7,12 @@ return [
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`employee_id` BIGINT UNSIGNED NOT NULL,
`employee_number` VARCHAR(20) NOT NULL,
`first_name_ar` VARCHAR(100) NOT NULL,
`last_name_ar` VARCHAR(100) NOT NULL,
`first_name_en` VARCHAR(100) NULL,
`last_name_en` VARCHAR(100) NULL,
`phone` VARCHAR(20) NULL,
`email` VARCHAR(200) NULL,
`national_id` VARCHAR(14) NULL,
`date_of_birth` DATE NULL,
`gender` VARCHAR(10) NULL COMMENT 'male, female',
......@@ -23,7 +29,7 @@ return [
`probation_end_date` DATE NULL,
`probation_status` VARCHAR(20) NULL DEFAULT 'in_probation' COMMENT 'in_probation, passed, failed',
`employment_type` VARCHAR(20) NOT NULL DEFAULT 'full_time' COMMENT 'full_time, part_time, contract, temporary',
`employment_status` VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT 'active, on_leave, suspended, terminated, resigned',
`employment_status` VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT 'active, probation, on_leave, suspended, terminated, resigned',
`salary_structure_id` BIGINT UNSIGNED NULL,
`basic_salary` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`insurable_salary` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
......@@ -39,6 +45,7 @@ return [
`notes` TEXT NULL,
`photo_path` VARCHAR(500) NULL,
`termination_date` DATE NULL,
`termination_type` VARCHAR(30) NULL,
`termination_reason` TEXT NULL,
`last_working_day` DATE NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
......@@ -50,7 +57,7 @@ return [
`archived_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_profiles_employee` (`employee_id`),
UNIQUE KEY `uq_hr_profiles_number` (`employee_number`),
UNIQUE KEY `uq_hr_profiles_national_id` (`national_id`),
INDEX `idx_hr_profiles_national_id` (`national_id`),
INDEX `idx_hr_profiles_department` (`department_id`),
INDEX `idx_hr_profiles_job_title` (`job_title_id`),
INDEX `idx_hr_profiles_manager` (`direct_manager_id`),
......@@ -67,7 +74,6 @@ return [
CONSTRAINT `chk_hr_profiles_marital` CHECK (`marital_status` IN ('single', 'married', 'divorced', 'widowed')),
CONSTRAINT `chk_hr_profiles_religion` CHECK (`religion` IN ('muslim', 'christian', 'other')),
CONSTRAINT `chk_hr_profiles_emp_type` CHECK (`employment_type` IN ('full_time', 'part_time', 'contract', 'temporary')),
CONSTRAINT `chk_hr_profiles_emp_status` CHECK (`employment_status` IN ('active', 'on_leave', 'suspended', 'terminated', 'resigned')),
CONSTRAINT `chk_hr_profiles_basic_salary` CHECK (`basic_salary` >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
",
......
......@@ -5,7 +5,7 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_employee_salary_details` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`employee_id` BIGINT UNSIGNED NOT NULL,
`employee_profile_id` BIGINT UNSIGNED NOT NULL,
`component_id` BIGINT UNSIGNED NOT NULL,
`amount` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`percentage` DECIMAL(5,2) NULL,
......@@ -16,11 +16,11 @@ return [
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
INDEX `idx_hr_emp_salary_employee` (`employee_id`),
INDEX `idx_hr_emp_salary_employee` (`employee_profile_id`),
INDEX `idx_hr_emp_salary_component` (`component_id`),
INDEX `idx_hr_emp_salary_effective` (`effective_from`, `effective_to`),
INDEX `idx_hr_emp_salary_active` (`is_active`),
CONSTRAINT `fk_hr_emp_salary_employee` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`),
CONSTRAINT `fk_hr_emp_salary_employee` FOREIGN KEY (`employee_profile_id`) REFERENCES `hr_employee_profiles`(`id`),
CONSTRAINT `fk_hr_emp_salary_component` FOREIGN KEY (`component_id`) REFERENCES `hr_salary_components`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
",
......
......@@ -5,7 +5,7 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_contracts` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`employee_id` BIGINT UNSIGNED NOT NULL,
`employee_profile_id` BIGINT UNSIGNED NOT NULL,
`contract_number` VARCHAR(30) NOT NULL,
`contract_type` VARCHAR(20) NOT NULL COMMENT 'definite, indefinite',
`start_date` DATE NOT NULL,
......@@ -33,12 +33,12 @@ return [
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_contracts_number` (`contract_number`),
INDEX `idx_hr_contracts_employee` (`employee_id`),
INDEX `idx_hr_contracts_employee` (`employee_profile_id`),
INDEX `idx_hr_contracts_status` (`status`),
INDEX `idx_hr_contracts_dates` (`start_date`, `end_date`),
INDEX `idx_hr_contracts_workflow` (`workflow_instance_id`),
INDEX `idx_hr_contracts_archived` (`is_archived`),
CONSTRAINT `fk_hr_contracts_employee` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`),
CONSTRAINT `fk_hr_contracts_employee` FOREIGN KEY (`employee_profile_id`) REFERENCES `hr_employee_profiles`(`id`),
CONSTRAINT `fk_hr_contracts_renewal` FOREIGN KEY (`renewal_of_contract_id`) REFERENCES `hr_contracts`(`id`) ON DELETE SET NULL,
CONSTRAINT `fk_hr_contracts_workflow` FOREIGN KEY (`workflow_instance_id`) REFERENCES `workflow_instances`(`id`) ON DELETE SET NULL,
CONSTRAINT `chk_hr_contracts_type` CHECK (`contract_type` IN ('definite', 'indefinite')),
......
......@@ -5,7 +5,7 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_employee_documents` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`employee_id` BIGINT UNSIGNED NOT NULL,
`employee_profile_id` BIGINT UNSIGNED NOT NULL,
`document_type` VARCHAR(50) NOT NULL COMMENT 'national_id_copy, birth_certificate, degree, military_cert, insurance_form1, insurance_form2, insurance_form6, tax_card, criminal_record, medical_report, contract_copy, photo, experience_cert, other',
`title_ar` VARCHAR(300) NOT NULL,
`original_filename` VARCHAR(500) NOT NULL,
......@@ -26,11 +26,11 @@ return [
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
INDEX `idx_hr_docs_employee` (`employee_id`),
INDEX `idx_hr_docs_employee` (`employee_profile_id`),
INDEX `idx_hr_docs_type` (`document_type`),
INDEX `idx_hr_docs_expiry` (`expiry_date`),
INDEX `idx_hr_docs_archived` (`is_archived`),
CONSTRAINT `fk_hr_docs_employee` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`)
CONSTRAINT `fk_hr_docs_employee` FOREIGN KEY (`employee_profile_id`) REFERENCES `hr_employee_profiles`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
",
'down' => "DROP TABLE IF EXISTS `hr_employee_documents`",
......
......@@ -5,21 +5,31 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_work_schedules` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`schedule_code` VARCHAR(30) NOT NULL,
`code` VARCHAR(30) NOT NULL,
`name_ar` VARCHAR(200) NOT NULL,
`name_en` VARCHAR(200) NULL,
`schedule_type` VARCHAR(20) NOT NULL DEFAULT 'fixed' COMMENT 'fixed, rotating, flexible',
`work_days_json` JSON NOT NULL,
`hours_per_day` DECIMAL(3,1) NOT NULL DEFAULT 8.0,
`ramadan_hours_per_day` DECIMAL(3,1) NOT NULL DEFAULT 7.0,
`night_shift` TINYINT(1) NOT NULL DEFAULT 0,
`working_days_per_week` INT UNSIGNED NOT NULL DEFAULT 6,
`hours_per_day` DECIMAL(4,2) NOT NULL DEFAULT 8.00,
`hours_per_week` DECIMAL(5,2) NOT NULL DEFAULT 48.00,
`ramadan_hours_per_day` DECIMAL(4,2) NOT NULL DEFAULT 7.00,
`is_night_shift` TINYINT(1) NOT NULL DEFAULT 0,
`start_time` TIME NULL,
`end_time` TIME NULL,
`break_duration_minutes` INT UNSIGNED NOT NULL DEFAULT 60,
`late_tolerance_minutes` INT UNSIGNED NOT NULL DEFAULT 15,
`early_leave_tolerance` INT UNSIGNED NOT NULL DEFAULT 15,
`days_config_json` JSON NOT NULL,
`is_default` TINYINT(1) NOT NULL DEFAULT 0,
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_work_schedules_code` (`schedule_code`),
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_work_schedules_code` (`code`),
INDEX `idx_hr_work_schedules_active` (`is_active`),
CONSTRAINT `chk_hr_work_sched_type` CHECK (`schedule_type` IN ('fixed', 'rotating', 'flexible'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
......
......@@ -5,7 +5,7 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_employee_schedules` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`employee_id` BIGINT UNSIGNED NOT NULL,
`employee_profile_id` BIGINT UNSIGNED NOT NULL,
`schedule_id` BIGINT UNSIGNED NOT NULL,
`effective_from` DATE NOT NULL,
`effective_to` DATE NULL,
......@@ -14,10 +14,10 @@ return [
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
INDEX `idx_hr_emp_sched_employee` (`employee_id`),
INDEX `idx_hr_emp_sched_employee` (`employee_profile_id`),
INDEX `idx_hr_emp_sched_schedule` (`schedule_id`),
INDEX `idx_hr_emp_sched_dates` (`effective_from`, `effective_to`),
CONSTRAINT `fk_hr_emp_sched_employee` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`),
CONSTRAINT `fk_hr_emp_sched_employee` FOREIGN KEY (`employee_profile_id`) REFERENCES `hr_employee_profiles`(`id`),
CONSTRAINT `fk_hr_emp_sched_schedule` FOREIGN KEY (`schedule_id`) REFERENCES `hr_work_schedules`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
",
......
......@@ -5,7 +5,7 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_attendance` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`employee_id` BIGINT UNSIGNED NOT NULL,
`employee_profile_id` BIGINT UNSIGNED NOT NULL,
`attendance_date` DATE NOT NULL,
`check_in_time` TIME NULL,
`check_out_time` TIME NULL,
......@@ -16,21 +16,25 @@ return [
`overtime_type` VARCHAR(10) NOT NULL DEFAULT 'none' COMMENT 'none, day, night',
`late_minutes` INT UNSIGNED NOT NULL DEFAULT 0,
`early_leave_minutes` INT UNSIGNED NOT NULL DEFAULT 0,
`status` VARCHAR(20) NOT NULL COMMENT 'present, absent, leave, holiday, weekend, half_day',
`status` VARCHAR(20) NOT NULL COMMENT 'present, absent, late, leave, holiday, rest_day, half_day',
`is_ramadan_schedule` TINYINT(1) NOT NULL DEFAULT 0,
`notes` TEXT NULL,
`is_approved` TINYINT(1) NOT NULL DEFAULT 0,
`approved_by` BIGINT UNSIGNED NULL,
`approved_at` TIMESTAMP NULL DEFAULT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_attendance_emp_date` (`employee_id`, `attendance_date`),
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_attendance_emp_date` (`employee_profile_id`, `attendance_date`),
INDEX `idx_hr_attendance_date` (`attendance_date`),
INDEX `idx_hr_attendance_status` (`status`),
INDEX `idx_hr_attendance_employee` (`employee_id`),
CONSTRAINT `fk_hr_attendance_employee` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`),
CONSTRAINT `chk_hr_attendance_status` CHECK (`status` IN ('present', 'absent', 'leave', 'holiday', 'weekend', 'half_day')),
INDEX `idx_hr_attendance_employee` (`employee_profile_id`),
CONSTRAINT `fk_hr_attendance_employee` FOREIGN KEY (`employee_profile_id`) REFERENCES `hr_employee_profiles`(`id`),
CONSTRAINT `chk_hr_attendance_status` CHECK (`status` IN ('present', 'absent', 'late', 'leave', 'holiday', 'rest_day', 'half_day')),
CONSTRAINT `chk_hr_attendance_ot_type` CHECK (`overtime_type` IN ('none', 'day', 'night'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
",
......
......@@ -5,34 +5,46 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_leave_types` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`leave_code` VARCHAR(30) NOT NULL,
`code` VARCHAR(30) NOT NULL,
`name_ar` VARCHAR(200) NOT NULL,
`name_en` VARCHAR(200) NULL,
`category` VARCHAR(30) NULL COMMENT 'annual, sick, special, casual, unpaid',
`default_days_per_year` DECIMAL(5,1) NULL,
`max_days_per_year` DECIMAL(5,1) NULL,
`is_paid` TINYINT(1) NOT NULL DEFAULT 1,
`pay_percentage` DECIMAL(5,2) NOT NULL DEFAULT 100.00,
`requires_approval` TINYINT(1) NOT NULL DEFAULT 1,
`requires_document` TINYINT(1) NOT NULL DEFAULT 0,
`requires_attachment` TINYINT(1) NOT NULL DEFAULT 0,
`min_days_per_request` DECIMAL(5,1) NULL,
`max_days_per_request` DECIMAL(5,1) NULL,
`min_consecutive_annual` DECIMAL(5,1) NULL,
`max_consecutive_days` INT UNSIGNED NULL,
`max_per_occurrence` INT UNSIGNED NULL,
`max_times_in_career` INT UNSIGNED NULL,
`min_service_months` INT UNSIGNED NULL,
`gender_restriction` VARCHAR(10) NOT NULL DEFAULT 'all' COMMENT 'male, female, all',
`advance_notice_days` INT UNSIGNED NOT NULL DEFAULT 0,
`gender_restriction` VARCHAR(10) NULL DEFAULT NULL COMMENT 'male, female, null=all',
`carry_over_allowed` TINYINT(1) NOT NULL DEFAULT 0,
`carry_over_max_days` INT UNSIGNED NULL,
`max_carry_over_days` DECIMAL(5,1) NULL,
`carry_over_expiry_months` INT UNSIGNED NULL,
`is_accumulative` TINYINT(1) NOT NULL DEFAULT 0,
`max_accumulation_years` INT UNSIGNED NULL DEFAULT 3,
`accumulation_max_years` INT UNSIGNED NULL DEFAULT 3,
`career_max_times` INT UNSIGNED NULL,
`career_max_days` DECIMAL(5,1) NULL,
`deduct_from_salary` TINYINT(1) NOT NULL DEFAULT 0,
`legal_reference` VARCHAR(100) NULL,
`rules_json` JSON NULL,
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
`sort_order` INT UNSIGNED NOT NULL DEFAULT 0,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_leave_types_code` (`leave_code`),
INDEX `idx_hr_leave_types_active` (`is_active`),
CONSTRAINT `chk_hr_leave_gender` CHECK (`gender_restriction` IN ('male', 'female', 'all'))
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_leave_types_code` (`code`),
INDEX `idx_hr_leave_types_active` (`is_active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
",
'down' => "DROP TABLE IF EXISTS `hr_leave_types`",
......
......@@ -5,7 +5,7 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_leave_balances` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`employee_id` BIGINT UNSIGNED NOT NULL,
`employee_profile_id` BIGINT UNSIGNED NOT NULL,
`leave_type_id` BIGINT UNSIGNED NOT NULL,
`year` YEAR NOT NULL,
`entitled_days` DECIMAL(5,1) NOT NULL DEFAULT 0.0,
......@@ -18,11 +18,11 @@ return [
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_leave_bal` (`employee_id`, `leave_type_id`, `year`),
INDEX `idx_hr_leave_bal_employee` (`employee_id`),
UNIQUE KEY `uq_hr_leave_bal` (`employee_profile_id`, `leave_type_id`, `year`),
INDEX `idx_hr_leave_bal_employee` (`employee_profile_id`),
INDEX `idx_hr_leave_bal_year` (`year`),
INDEX `idx_hr_leave_bal_type` (`leave_type_id`),
CONSTRAINT `fk_hr_leave_bal_employee` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`),
CONSTRAINT `fk_hr_leave_bal_employee` FOREIGN KEY (`employee_profile_id`) REFERENCES `hr_employee_profiles`(`id`),
CONSTRAINT `fk_hr_leave_bal_type` FOREIGN KEY (`leave_type_id`) REFERENCES `hr_leave_types`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
",
......
......@@ -5,7 +5,7 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_leave_requests` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`employee_id` BIGINT UNSIGNED NOT NULL,
`employee_profile_id` BIGINT UNSIGNED NOT NULL,
`leave_type_id` BIGINT UNSIGNED NOT NULL,
`start_date` DATE NOT NULL,
`end_date` DATE NOT NULL,
......@@ -28,13 +28,13 @@ return [
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
INDEX `idx_hr_leave_req_employee` (`employee_id`),
INDEX `idx_hr_leave_req_employee` (`employee_profile_id`),
INDEX `idx_hr_leave_req_type` (`leave_type_id`),
INDEX `idx_hr_leave_req_dates` (`start_date`, `end_date`),
INDEX `idx_hr_leave_req_status` (`status`),
INDEX `idx_hr_leave_req_workflow` (`workflow_instance_id`),
INDEX `idx_hr_leave_req_archived` (`is_archived`),
CONSTRAINT `fk_hr_leave_req_employee` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`),
CONSTRAINT `fk_hr_leave_req_employee` FOREIGN KEY (`employee_profile_id`) REFERENCES `hr_employee_profiles`(`id`),
CONSTRAINT `fk_hr_leave_req_type` FOREIGN KEY (`leave_type_id`) REFERENCES `hr_leave_types`(`id`),
CONSTRAINT `fk_hr_leave_req_approver` FOREIGN KEY (`approved_by`) REFERENCES `employees`(`id`) ON DELETE SET NULL,
CONSTRAINT `fk_hr_leave_req_workflow` FOREIGN KEY (`workflow_instance_id`) REFERENCES `workflow_instances`(`id`) ON DELETE SET NULL,
......
......@@ -5,24 +5,28 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_holidays` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`holiday_date` DATE NULL,
`name_ar` VARCHAR(200) NOT NULL,
`name_en` VARCHAR(200) NULL,
`holiday_type` VARCHAR(20) NOT NULL COMMENT 'national, religious, company',
`religion_specific` VARCHAR(20) NOT NULL DEFAULT 'all' COMMENT 'all, muslim, christian',
`date` DATE NULL,
`duration_days` INT UNSIGNED NOT NULL DEFAULT 1,
`is_recurring` TINYINT(1) NOT NULL DEFAULT 0,
`recurring_month` INT UNSIGNED NULL,
`recurring_day` INT UNSIGNED NULL,
`religion` VARCHAR(20) NULL DEFAULT NULL COMMENT 'null=all, muslim, christian',
`year` YEAR NULL,
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
INDEX `idx_hr_holidays_date` (`holiday_date`),
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
INDEX `idx_hr_holidays_date` (`date`),
INDEX `idx_hr_holidays_year` (`year`),
INDEX `idx_hr_holidays_type` (`holiday_type`),
CONSTRAINT `chk_hr_holidays_type` CHECK (`holiday_type` IN ('national', 'religious', 'company')),
CONSTRAINT `chk_hr_holidays_religion` CHECK (`religion_specific` IN ('all', 'muslim', 'christian'))
CONSTRAINT `chk_hr_holidays_type` CHECK (`holiday_type` IN ('national', 'religious', 'company'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
",
'down' => "DROP TABLE IF EXISTS `hr_holidays`",
......
......@@ -5,7 +5,7 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_disciplinary_actions` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`employee_id` BIGINT UNSIGNED NOT NULL,
`employee_profile_id` BIGINT UNSIGNED NOT NULL,
`action_number` VARCHAR(30) NOT NULL,
`incident_date` DATE NOT NULL,
`incident_description` TEXT NOT NULL,
......@@ -38,12 +38,12 @@ return [
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_disciplinary_number` (`action_number`),
INDEX `idx_hr_disc_employee` (`employee_id`),
INDEX `idx_hr_disc_employee` (`employee_profile_id`),
INDEX `idx_hr_disc_status` (`status`),
INDEX `idx_hr_disc_type` (`penalty_type`),
INDEX `idx_hr_disc_date` (`incident_date`),
INDEX `idx_hr_disc_archived` (`is_archived`),
CONSTRAINT `fk_hr_disc_employee` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`),
CONSTRAINT `fk_hr_disc_employee` FOREIGN KEY (`employee_profile_id`) REFERENCES `hr_employee_profiles`(`id`),
CONSTRAINT `chk_hr_disc_penalty` CHECK (`penalty_type` IN ('warning', 'wage_deduction', 'raise_postponement', 'suspension', 'demotion', 'termination')),
CONSTRAINT `chk_hr_disc_status` CHECK (`status` IN ('investigation', 'pending_decision', 'decided', 'appealed', 'appeal_reviewed', 'enforced', 'cancelled')),
CONSTRAINT `chk_hr_disc_deduction_days` CHECK (`wage_deduction_days` IS NULL OR `wage_deduction_days` <= 5),
......
......@@ -5,20 +5,22 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_employee_loans` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`employee_id` BIGINT UNSIGNED NOT NULL,
`employee_profile_id` BIGINT UNSIGNED NOT NULL,
`loan_number` VARCHAR(30) NOT NULL,
`loan_type` VARCHAR(30) NOT NULL COMMENT 'salary_advance, personal_loan, emergency_loan',
`amount` DECIMAL(15,2) NOT NULL,
`loan_amount` DECIMAL(15,2) NOT NULL,
`installment_amount` DECIMAL(15,2) NOT NULL,
`total_installments` INT UNSIGNED NOT NULL,
`number_of_installments` INT UNSIGNED NOT NULL,
`paid_installments` INT UNSIGNED NOT NULL DEFAULT 0,
`remaining_amount` DECIMAL(15,2) NOT NULL,
`interest_rate` DECIMAL(5,2) NOT NULL DEFAULT 0.00,
`start_deduction_month` DATE NOT NULL,
`status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending, approved, active, completed, cancelled',
`request_date` DATE NULL,
`start_deduction_date` DATE NOT NULL,
`reason` TEXT NULL,
`status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending, approved, disbursed, repaying, settled, rejected, cancelled',
`approved_by` BIGINT UNSIGNED NULL,
`approved_at` TIMESTAMP NULL DEFAULT NULL,
`disbursement_date` DATE NULL,
`disbursed_date` DATE NULL,
`workflow_instance_id` BIGINT UNSIGNED NULL,
`notes` TEXT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
......@@ -29,13 +31,13 @@ return [
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_loans_number` (`loan_number`),
INDEX `idx_hr_loans_employee` (`employee_id`),
INDEX `idx_hr_loans_employee` (`employee_profile_id`),
INDEX `idx_hr_loans_status` (`status`),
INDEX `idx_hr_loans_archived` (`is_archived`),
CONSTRAINT `fk_hr_loans_employee` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`),
CONSTRAINT `fk_hr_loans_employee` FOREIGN KEY (`employee_profile_id`) REFERENCES `hr_employee_profiles`(`id`),
CONSTRAINT `chk_hr_loans_type` CHECK (`loan_type` IN ('salary_advance', 'personal_loan', 'emergency_loan')),
CONSTRAINT `chk_hr_loans_status` CHECK (`status` IN ('pending', 'approved', 'active', 'completed', 'cancelled')),
CONSTRAINT `chk_hr_loans_amount` CHECK (`amount` > 0)
CONSTRAINT `chk_hr_loans_status` CHECK (`status` IN ('pending', 'approved', 'disbursed', 'repaying', 'settled', 'rejected', 'cancelled')),
CONSTRAINT `chk_hr_loans_amount` CHECK (`loan_amount` > 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
",
'down' => "DROP TABLE IF EXISTS `hr_employee_loans`",
......
......@@ -7,7 +7,7 @@ return [
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`loan_id` BIGINT UNSIGNED NOT NULL,
`installment_number` INT UNSIGNED NOT NULL,
`due_month` DATE NOT NULL,
`due_date` DATE NOT NULL,
`amount` DECIMAL(15,2) NOT NULL,
`status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending, deducted, skipped, cancelled',
`payroll_run_id` BIGINT UNSIGNED NULL,
......@@ -16,7 +16,7 @@ return [
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_hr_loan_inst_loan` (`loan_id`),
INDEX `idx_hr_loan_inst_month` (`due_month`),
INDEX `idx_hr_loan_inst_month` (`due_date`),
INDEX `idx_hr_loan_inst_status` (`status`),
CONSTRAINT `fk_hr_loan_inst_loan` FOREIGN KEY (`loan_id`) REFERENCES `hr_employee_loans`(`id`) ON DELETE CASCADE,
CONSTRAINT `chk_hr_loan_inst_status` CHECK (`status` IN ('pending', 'deducted', 'skipped', 'cancelled')),
......
......@@ -6,42 +6,47 @@ return [
CREATE TABLE IF NOT EXISTS `hr_payroll_runs` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`period_id` BIGINT UNSIGNED NOT NULL,
`employee_id` BIGINT UNSIGNED NOT NULL,
`employee_profile_id` BIGINT UNSIGNED NOT NULL,
`employee_number` VARCHAR(20) NOT NULL,
`department_id` BIGINT UNSIGNED NULL,
`job_title_id` BIGINT UNSIGNED NULL,
`basic_salary` DECIMAL(15,2) NOT NULL,
`working_days` INT UNSIGNED NOT NULL DEFAULT 0,
`actual_working_days` INT UNSIGNED NOT NULL DEFAULT 0,
`absent_days` INT UNSIGNED NOT NULL DEFAULT 0,
`leave_days` DECIMAL(5,1) NOT NULL DEFAULT 0.0,
`overtime_hours` DECIMAL(6,2) NOT NULL DEFAULT 0.00,
`overtime_amount` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`gross_earnings` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`total_allowances` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`overtime_amount` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`total_deductions` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`net_salary` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`employee_insurance` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`employer_insurance` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`income_tax` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`insurance_employee` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`insurance_employer` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`tax_amount` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`loan_deduction` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`penalty_deduction` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`absence_deduction` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`other_deductions` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`other_earnings` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`calculation_json` JSON NOT NULL,
`net_salary` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`working_days` INT UNSIGNED NOT NULL DEFAULT 0,
`present_days` INT UNSIGNED NOT NULL DEFAULT 0,
`absent_days` INT UNSIGNED NOT NULL DEFAULT 0,
`leave_days` DECIMAL(5,1) NOT NULL DEFAULT 0.0,
`overtime_hours` DECIMAL(6,2) NOT NULL DEFAULT 0.00,
`calculation_json` JSON NULL,
`status` VARCHAR(20) NOT NULL DEFAULT 'calculated' COMMENT 'calculated, approved, paid, cancelled',
`payment_method` VARCHAR(20) NULL COMMENT 'bank_transfer, cash, check',
`payment_reference` VARCHAR(100) NULL,
`paid_at` TIMESTAMP NULL DEFAULT NULL,
`notes` TEXT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_payroll_run` (`period_id`, `employee_id`),
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_payroll_run` (`period_id`, `employee_profile_id`),
INDEX `idx_hr_payroll_run_period` (`period_id`),
INDEX `idx_hr_payroll_run_employee` (`employee_id`),
INDEX `idx_hr_payroll_run_employee` (`employee_profile_id`),
INDEX `idx_hr_payroll_run_status` (`status`),
CONSTRAINT `fk_hr_payroll_run_period` FOREIGN KEY (`period_id`) REFERENCES `hr_payroll_periods`(`id`),
CONSTRAINT `fk_hr_payroll_run_employee` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`),
CONSTRAINT `chk_hr_payroll_run_status` CHECK (`status` IN ('calculated', 'approved', 'paid', 'cancelled')),
CONSTRAINT `chk_hr_payroll_run_gross` CHECK (`gross_earnings` >= 0),
CONSTRAINT `chk_hr_payroll_run_net` CHECK (`net_salary` >= 0)
......
......@@ -8,8 +8,8 @@ return [
`payroll_run_id` BIGINT UNSIGNED NOT NULL,
`component_id` BIGINT UNSIGNED NULL,
`component_code` VARCHAR(30) NOT NULL,
`component_name_ar` VARCHAR(200) NOT NULL,
`component_type` VARCHAR(20) NOT NULL COMMENT 'earning, deduction',
`name_ar` VARCHAR(200) NOT NULL,
`type` VARCHAR(20) NOT NULL COMMENT 'earning, deduction',
`amount` DECIMAL(15,2) NOT NULL,
`is_taxable` TINYINT(1) NOT NULL DEFAULT 1,
`is_insurable` TINYINT(1) NOT NULL DEFAULT 1,
......@@ -18,7 +18,7 @@ return [
INDEX `idx_hr_payroll_comp_run` (`payroll_run_id`),
INDEX `idx_hr_payroll_comp_code` (`component_code`),
CONSTRAINT `fk_hr_payroll_comp_run` FOREIGN KEY (`payroll_run_id`) REFERENCES `hr_payroll_runs`(`id`) ON DELETE CASCADE,
CONSTRAINT `chk_hr_payroll_comp_type` CHECK (`component_type` IN ('earning', 'deduction'))
CONSTRAINT `chk_hr_payroll_comp_type` CHECK (`type` IN ('earning', 'deduction'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
",
'down' => "DROP TABLE IF EXISTS `hr_payroll_components_log`",
......
......@@ -5,29 +5,31 @@ return [
'up' => "
CREATE TABLE IF NOT EXISTS `hr_insurance_records` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`employee_id` BIGINT UNSIGNED NOT NULL,
`employee_profile_id` BIGINT UNSIGNED NOT NULL,
`period_id` BIGINT UNSIGNED NOT NULL,
`payroll_run_id` BIGINT UNSIGNED NULL,
`insurable_salary` DECIMAL(15,2) NOT NULL,
`basic_salary_portion` DECIMAL(15,2) NOT NULL,
`variable_salary_portion` DECIMAL(15,2) NOT NULL,
`employee_share_basic` DECIMAL(15,2) NOT NULL,
`employee_share_variable` DECIMAL(15,2) NOT NULL,
`employer_share_basic` DECIMAL(15,2) NOT NULL,
`employer_share_variable` DECIMAL(15,2) NOT NULL,
`basic_insurable_salary` DECIMAL(15,2) NOT NULL,
`variable_insurable_salary` DECIMAL(15,2) NOT NULL,
`employee_basic_share` DECIMAL(15,2) NOT NULL,
`employee_variable_share` DECIMAL(15,2) NOT NULL,
`total_employee_share` DECIMAL(15,2) NOT NULL,
`employer_basic_share` DECIMAL(15,2) NOT NULL,
`employer_variable_share` DECIMAL(15,2) NOT NULL,
`total_employer_share` DECIMAL(15,2) NOT NULL,
`total_contribution` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`insurance_number` VARCHAR(20) NULL,
`notes` TEXT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_insurance_emp_period` (`employee_id`, `period_id`),
INDEX `idx_hr_insurance_employee` (`employee_id`),
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_insurance_emp_period` (`employee_profile_id`, `period_id`),
INDEX `idx_hr_insurance_employee` (`employee_profile_id`),
INDEX `idx_hr_insurance_period` (`period_id`),
INDEX `idx_hr_insurance_payroll` (`payroll_run_id`),
CONSTRAINT `fk_hr_insurance_employee` FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`),
CONSTRAINT `fk_hr_insurance_period` FOREIGN KEY (`period_id`) REFERENCES `hr_payroll_periods`(`id`),
CONSTRAINT `fk_hr_insurance_payroll` FOREIGN KEY (`payroll_run_id`) REFERENCES `hr_payroll_runs`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
......
......@@ -8,21 +8,25 @@ return [
`employee_id` BIGINT UNSIGNED NOT NULL,
`period_id` BIGINT UNSIGNED NOT NULL,
`payroll_run_id` BIGINT UNSIGNED NULL,
`month` INT UNSIGNED NULL,
`gross_taxable_monthly` DECIMAL(15,2) NOT NULL,
`annual_taxable_income` DECIMAL(15,2) NOT NULL,
`personal_exemption` DECIMAL(15,2) NOT NULL,
`insurance_exemption` DECIMAL(15,2) NOT NULL,
`net_taxable_income` DECIMAL(15,2) NOT NULL,
`annual_tax` DECIMAL(15,2) NOT NULL,
`monthly_tax` DECIMAL(15,2) NOT NULL,
`ytd_tax` DECIMAL(15,2) NOT NULL,
`ytd_taxable_income` DECIMAL(15,2) NOT NULL,
`calculation_json` JSON NOT NULL,
`annual_taxable_income` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`personal_exemption` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`insurance_exemption` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`net_taxable_income` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`annual_tax` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`monthly_tax` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`ytd_tax` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`ytd_taxable_income` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`calculation_json` JSON NULL,
`notes` TEXT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_hr_tax_emp_period` (`employee_id`, `period_id`),
INDEX `idx_hr_tax_employee` (`employee_id`),
INDEX `idx_hr_tax_period` (`period_id`),
......
......@@ -233,11 +233,14 @@ return function (Database $db): void {
continue;
}
$db->insert('system_config', [
'config_key' => $cfg['config_key'],
'config_value' => $cfg['config_value'],
'description' => $cfg['description'],
'created_at' => $ts,
'updated_at' => $ts,
'config_key' => $cfg['config_key'],
'config_value' => $cfg['config_value'],
'config_type' => is_numeric($cfg['config_value']) ? (str_contains($cfg['config_value'], '.') ? 'float' : 'integer') : (str_starts_with($cfg['config_value'], '[') ? 'json' : 'string'),
'group_name' => 'hr',
'description_ar' => $cfg['description'],
'is_editable' => 1,
'created_at' => $ts,
'updated_at' => $ts,
]);
}
};
......@@ -83,15 +83,14 @@ return function (Database $db): void {
}
$db->insert('sms_templates', [
'template_code' => $tpl['template_code'],
'name_ar' => $tpl['name_ar'],
'name_en' => $tpl['name_en'],
'body_ar' => $tpl['body_ar'],
'channel' => $tpl['channel'],
'event_trigger' => $tpl['event_trigger'],
'is_active' => 1,
'created_at' => $ts,
'updated_at' => $ts,
'template_code' => $tpl['template_code'],
'name_ar' => $tpl['name_ar'],
'name_en' => $tpl['name_en'],
'message_template_ar' => $tpl['body_ar'],
'trigger_event' => $tpl['event_trigger'],
'is_active' => 1,
'created_at' => $ts,
'updated_at' => $ts,
]);
}
};
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