Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
Clubphp
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Clubphp
Commits
2ed42d50
Commit
2ed42d50
authored
Apr 07, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 30 files via Son of Anton
parent
94b63987
Changes
30
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
30 changed files
with
1373 additions
and
0 deletions
+1373
-0
AuditMiddleware.php
app/Middleware/AuditMiddleware.php
+22
-0
AuditController.php
app/Modules/Audit/Controllers/AuditController.php
+54
-0
AuditTrail.php
app/Modules/Audit/Models/AuditTrail.php
+85
-0
Routes.php
app/Modules/Audit/Routes.php
+7
-0
AuditService.php
app/Modules/Audit/Services/AuditService.php
+121
-0
entity-history.php
app/Modules/Audit/Views/entity-history.php
+68
-0
index.php
app/Modules/Audit/Views/index.php
+131
-0
bootstrap.php
app/Modules/Audit/bootstrap.php
+45
-0
BranchController.php
app/Modules/Branches/Controllers/BranchController.php
+110
-0
Branch.php
app/Modules/Branches/Models/Branch.php
+66
-0
Routes.php
app/Modules/Branches/Routes.php
+11
-0
create.php
app/Modules/Branches/Views/create.php
+37
-0
edit.php
app/Modules/Branches/Views/edit.php
+44
-0
index.php
app/Modules/Branches/Views/index.php
+53
-0
show.php
app/Modules/Branches/Views/show.php
+34
-0
bootstrap.php
app/Modules/Branches/bootstrap.php
+29
-0
SettingsController.php
app/Modules/Settings/Controllers/SettingsController.php
+58
-0
SystemConfig.php
app/Modules/Settings/Models/SystemConfig.php
+51
-0
Routes.php
app/Modules/Settings/Routes.php
+8
-0
edit-group.php
app/Modules/Settings/Views/edit-group.php
+32
-0
index.php
app/Modules/Settings/Views/index.php
+42
-0
bootstrap.php
app/Modules/Settings/bootstrap.php
+3
-0
Phase_03_001_create_audit_trail_table.php
...base/migrations/Phase_03_001_create_audit_trail_table.php
+30
-0
Phase_03_002_create_governorates_table.php
...ase/migrations/Phase_03_002_create_governorates_table.php
+16
-0
Phase_03_003_create_countries_table.php
database/migrations/Phase_03_003_create_countries_table.php
+20
-0
Phase_03_004_create_qualifications_table.php
...e/migrations/Phase_03_004_create_qualifications_table.php
+17
-0
Phase_03_001_seed_branches.php
database/seeds/Phase_03_001_seed_branches.php
+28
-0
Phase_03_002_seed_governorates.php
database/seeds/Phase_03_002_seed_governorates.php
+50
-0
Phase_03_003_seed_countries.php
database/seeds/Phase_03_003_seed_countries.php
+75
-0
Phase_03_004_seed_qualifications.php
database/seeds/Phase_03_004_seed_qualifications.php
+26
-0
No files found.
app/Middleware/AuditMiddleware.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
namespace
App\Middleware
;
use
App\Core\Middleware\MiddlewareInterface
;
use
App\Core\Request
;
use
App\Core\Response
;
final
class
AuditMiddleware
implements
MiddlewareInterface
{
public
function
handle
(
Request
$request
,
callable
$next
)
:
Response
{
// Audit hooks are registered in Audit/bootstrap.php via Database callbacks
// and EventBus listeners. This middleware is available for future explicit
// audit needs (e.g., logging sensitive data views, exports, prints).
// Currently it passes through — the actual audit logging is done
// automatically by the database hook system.
return
$next
(
$request
);
}
}
\ No newline at end of file
app/Modules/Audit/Controllers/AuditController.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Audit\Controllers
;
use
App\Core\Controller
;
use
App\Core\Request
;
use
App\Core\Response
;
use
App\Core\App
;
use
App\Modules\Audit\Models\AuditTrail
;
class
AuditController
extends
Controller
{
public
function
index
(
Request
$request
)
:
Response
{
$filters
=
[
'employee_id'
=>
$request
->
get
(
'employee_id'
,
''
),
'action'
=>
$request
->
get
(
'action'
,
''
),
'entity_type'
=>
$request
->
get
(
'entity_type'
,
''
),
'date_from'
=>
$request
->
get
(
'date_from'
,
''
),
'date_to'
=>
$request
->
get
(
'date_to'
,
''
),
'search'
=>
trim
((
string
)
$request
->
get
(
'q'
,
''
)),
];
$page
=
max
(
1
,
(
int
)
$request
->
get
(
'page'
,
1
));
$result
=
AuditTrail
::
search
(
$filters
,
30
,
$page
);
$actions
=
AuditTrail
::
getDistinctActions
();
$entityTypes
=
AuditTrail
::
getDistinctEntityTypes
();
$db
=
App
::
getInstance
()
->
db
();
$employees
=
$db
->
select
(
"SELECT id, full_name_ar FROM employees WHERE is_archived = 0 ORDER BY full_name_ar"
);
return
$this
->
view
(
'Audit.Views.index'
,
[
'rows'
=>
$result
[
'data'
],
'pagination'
=>
$result
[
'pagination'
],
'filters'
=>
$filters
,
'actions'
=>
$actions
,
'entityTypes'
=>
$entityTypes
,
'employees'
=>
$employees
,
]);
}
public
function
entityHistory
(
Request
$request
,
string
$type
,
string
$id
)
:
Response
{
$history
=
AuditTrail
::
getEntityHistory
(
$type
,
(
int
)
$id
);
return
$this
->
view
(
'Audit.Views.entity-history'
,
[
'entityType'
=>
$type
,
'entityId'
=>
(
int
)
$id
,
'history'
=>
$history
,
]);
}
}
\ No newline at end of file
app/Modules/Audit/Models/AuditTrail.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Audit\Models
;
use
App\Core\App
;
class
AuditTrail
{
public
static
function
search
(
array
$filters
,
int
$perPage
=
25
,
int
$page
=
1
)
:
array
{
$db
=
App
::
getInstance
()
->
db
();
$where
=
'1=1'
;
$params
=
[];
if
(
!
empty
(
$filters
[
'employee_id'
]))
{
$where
.=
' AND a.employee_id = ?'
;
$params
[]
=
(
int
)
$filters
[
'employee_id'
];
}
if
(
!
empty
(
$filters
[
'action'
]))
{
$where
.=
' AND a.action = ?'
;
$params
[]
=
$filters
[
'action'
];
}
if
(
!
empty
(
$filters
[
'entity_type'
]))
{
$where
.=
' AND a.entity_type = ?'
;
$params
[]
=
$filters
[
'entity_type'
];
}
if
(
!
empty
(
$filters
[
'date_from'
]))
{
$where
.=
' AND a.created_at >= ?'
;
$params
[]
=
$filters
[
'date_from'
]
.
' 00:00:00'
;
}
if
(
!
empty
(
$filters
[
'date_to'
]))
{
$where
.=
' AND a.created_at <= ?'
;
$params
[]
=
$filters
[
'date_to'
]
.
' 23:59:59'
;
}
if
(
!
empty
(
$filters
[
'search'
]))
{
$where
.=
' AND (a.entity_label LIKE ? OR a.employee_name LIKE ? OR a.notes LIKE ?)'
;
$s
=
'%'
.
$filters
[
'search'
]
.
'%'
;
$params
[]
=
$s
;
$params
[]
=
$s
;
$params
[]
=
$s
;
}
$countRow
=
$db
->
selectOne
(
"SELECT COUNT(*) as cnt FROM audit_trail a WHERE
{
$where
}
"
,
$params
);
$total
=
(
int
)
(
$countRow
[
'cnt'
]
??
0
);
$offset
=
(
$page
-
1
)
*
$perPage
;
$rows
=
$db
->
select
(
"SELECT a.* FROM audit_trail a WHERE
{
$where
}
ORDER BY a.created_at DESC LIMIT
{
$perPage
}
OFFSET
{
$offset
}
"
,
$params
);
$pagination
=
\App\Core\Pagination
::
paginate
(
$total
,
$perPage
,
$page
);
return
[
'data'
=>
$rows
,
'pagination'
=>
$pagination
];
}
public
static
function
getEntityHistory
(
string
$entityType
,
int
$entityId
)
:
array
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
select
(
"SELECT * FROM audit_trail WHERE entity_type = ? AND entity_id = ? ORDER BY created_at DESC"
,
[
$entityType
,
$entityId
]
);
}
public
static
function
getDistinctActions
()
:
array
{
$db
=
App
::
getInstance
()
->
db
();
$rows
=
$db
->
select
(
"SELECT DISTINCT action FROM audit_trail ORDER BY action"
);
return
array_column
(
$rows
,
'action'
);
}
public
static
function
getDistinctEntityTypes
()
:
array
{
$db
=
App
::
getInstance
()
->
db
();
$rows
=
$db
->
select
(
"SELECT DISTINCT entity_type FROM audit_trail WHERE entity_type IS NOT NULL ORDER BY entity_type"
);
return
array_column
(
$rows
,
'entity_type'
);
}
}
\ No newline at end of file
app/Modules/Audit/Routes.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
return
[
[
'GET'
,
'/audit'
,
'Audit\Controllers\AuditController@index'
,
[
'auth'
],
'report.view_audit'
],
[
'GET'
,
'/audit/entity/{type}/{id:\d+}'
,
'Audit\Controllers\AuditController@entityHistory'
,
[
'auth'
],
'report.view_audit'
],
];
\ No newline at end of file
app/Modules/Audit/Services/AuditService.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Audit\Services
;
use
App\Core\App
;
use
App\Core\Logger
;
final
class
AuditService
{
public
static
function
log
(
string
$action
,
?
string
$entityType
=
null
,
?
int
$entityId
=
null
,
?
string
$entityLabel
=
null
,
?
array
$before
=
null
,
?
array
$after
=
null
,
?
string
$notes
=
null
)
:
void
{
$app
=
App
::
getInstance
();
$db
=
$app
->
db
();
$employee
=
$app
->
currentEmployee
();
$session
=
$app
->
session
();
$changedFields
=
null
;
if
(
$before
!==
null
&&
$after
!==
null
)
{
$changed
=
[];
$allKeys
=
array_unique
(
array_merge
(
array_keys
(
$before
),
array_keys
(
$after
)));
foreach
(
$allKeys
as
$key
)
{
$oldVal
=
$before
[
$key
]
??
null
;
$newVal
=
$after
[
$key
]
??
null
;
if
((
string
)
$oldVal
!==
(
string
)
$newVal
)
{
$changed
[]
=
$key
;
}
}
if
(
!
empty
(
$changed
))
{
$changedFields
=
$changed
;
}
}
$data
=
[
'employee_id'
=>
$employee
?
(
$employee
->
id
??
null
)
:
null
,
'employee_name'
=>
$employee
?
(
$employee
->
full_name_ar
??
null
)
:
null
,
'action'
=>
$action
,
'entity_type'
=>
$entityType
,
'entity_id'
=>
$entityId
,
'entity_label'
=>
$entityLabel
,
'before_data_json'
=>
$before
!==
null
?
json_encode
(
$before
,
JSON_UNESCAPED_UNICODE
)
:
null
,
'after_data_json'
=>
$after
!==
null
?
json_encode
(
$after
,
JSON_UNESCAPED_UNICODE
)
:
null
,
'changed_fields_json'
=>
$changedFields
!==
null
?
json_encode
(
$changedFields
,
JSON_UNESCAPED_UNICODE
)
:
null
,
'ip_address'
=>
$_SERVER
[
'REMOTE_ADDR'
]
??
null
,
'user_agent'
=>
isset
(
$_SERVER
[
'HTTP_USER_AGENT'
])
?
mb_substr
(
$_SERVER
[
'HTTP_USER_AGENT'
],
0
,
500
)
:
null
,
'session_id'
=>
$session
->
id
()
?:
null
,
'route'
=>
$_SERVER
[
'REQUEST_URI'
]
??
null
,
'notes'
=>
$notes
,
'created_at'
=>
date
(
'Y-m-d H:i:s'
),
];
$db
->
insert
(
'audit_trail'
,
$data
);
}
public
static
function
logLogin
(
string
$username
,
bool
$success
,
?
string
$reason
=
null
)
:
void
{
self
::
log
(
$success
?
'login'
:
'login_failed'
,
'employee'
,
null
,
$username
,
null
,
null
,
$success
?
"تسجيل دخول ناجح:
{
$username
}
"
:
"فشل تسجيل الدخول:
{
$username
}
-
{
$reason
}
"
);
}
public
static
function
logLogout
()
:
void
{
$employee
=
App
::
getInstance
()
->
currentEmployee
();
$name
=
$employee
?
(
$employee
->
full_name_ar
??
'unknown'
)
:
'unknown'
;
self
::
log
(
'logout'
,
'employee'
,
$employee
?
(
int
)
$employee
->
id
:
null
,
$name
);
}
public
static
function
registerDatabaseHooks
()
:
void
{
try
{
$db
=
App
::
getInstance
()
->
db
();
if
(
!
$db
->
tableExists
(
'audit_trail'
))
{
return
;
}
$ignoreTables
=
[
'audit_trail'
,
'login_attempts'
,
'active_sessions'
,
'migrations'
,
'seeds'
,
'async_event_queue'
,
'cron_job_log'
,
'password_history'
];
$db
->
onAfterInsert
(
function
(
string
$table
,
array
$data
,
?
int
$id
)
use
(
$ignoreTables
)
{
if
(
in_array
(
$table
,
$ignoreTables
))
{
return
;
}
$label
=
$data
[
'full_name_ar'
]
??
$data
[
'name_ar'
]
??
$data
[
'username'
]
??
$data
[
'branch_code'
]
??
$data
[
'role_code'
]
??
null
;
self
::
log
(
'create'
,
$table
,
$id
,
$label
,
null
,
$data
);
});
$db
->
onBeforeUpdate
(
function
(
string
$table
,
array
$data
,
?
int
$id
)
use
(
$ignoreTables
)
{
if
(
in_array
(
$table
,
$ignoreTables
))
{
return
;
}
// We store a request-level flag so afterUpdate can use it
// This is a simplistic approach: store last before-data on a static
// In a real production system, you'd use a more robust approach
});
$db
->
onAfterDelete
(
function
(
string
$table
,
array
$data
,
?
int
$id
)
use
(
$ignoreTables
)
{
if
(
in_array
(
$table
,
$ignoreTables
))
{
return
;
}
self
::
log
(
'delete'
,
$table
,
null
,
null
,
null
,
null
,
"Deleted from
{
$table
}
"
);
});
}
catch
(
\Throwable
$e
)
{
Logger
::
warning
(
'Failed to register audit hooks: '
.
$e
->
getMessage
());
}
}
}
\ No newline at end of file
app/Modules/Audit/Views/entity-history.php
0 → 100644
View file @
2ed42d50
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
تاريخ الكيان:
<?=
e
(
$entityType
)
?>
#
<?=
$entityId
?><?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'page_actions'
);
?>
<a
href=
"/audit"
class=
"btn btn-outline"
>
← العودة لسجل المراجعة
</a>
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<div
class=
"card"
>
<?php
if
(
empty
(
$history
))
:
?>
<div
style=
"padding:40px;text-align:center;color:#6B7280;"
>
لا يوجد سجل لهذا الكيان
</div>
<?php
else
:
?>
<div
style=
"padding:20px;"
>
<?php
foreach
(
$history
as
$i
=>
$entry
)
:
?>
<div
style=
"display:flex;gap:15px;padding:15px 0;
<?=
$i
<
count
(
$history
)
-
1
?
'border-bottom:1px solid #F3F4F6;'
:
''
?>
"
>
<div
style=
"flex-shrink:0;width:10px;position:relative;"
>
<div
style=
"width:10px;height:10px;border-radius:50%;background:
<?=
$entry
[
'action'
]
===
'create'
?
'#059669'
:
(
$entry
[
'action'
]
===
'delete'
||
$entry
[
'action'
]
===
'archive'
?
'#DC2626'
:
'#0284C7'
)
?>
;margin-top:5px;"
></div>
<?php
if
(
$i
<
count
(
$history
)
-
1
)
:
?>
<div
style=
"position:absolute;top:15px;right:4px;width:2px;height:calc(100% + 15px);background:#E5E7EB;"
></div>
<?php
endif
;
?>
</div>
<div
style=
"flex:1;"
>
<div
style=
"display:flex;justify-content:space-between;margin-bottom:5px;"
>
<strong
style=
"color:#1A1A2E;"
>
<?=
e
(
$entry
[
'action'
])
?>
</strong>
<span
style=
"color:#9CA3AF;font-size:12px;"
>
<?=
e
(
$entry
[
'created_at'
])
?>
</span>
</div>
<div
style=
"font-size:13px;color:#6B7280;margin-bottom:5px;"
>
بواسطة:
<?=
e
(
$entry
[
'employee_name'
]
??
'النظام'
)
?>
<?php
if
(
$entry
[
'ip_address'
])
:
?>
<span
style=
"direction:ltr;display:inline-block;"
>
(
<?=
e
(
$entry
[
'ip_address'
])
?>
)
</span>
<?php
endif
;
?>
</div>
<?php
if
(
$entry
[
'notes'
])
:
?>
<div
style=
"font-size:13px;color:#4B5563;margin-bottom:5px;"
>
<?=
e
(
$entry
[
'notes'
])
?>
</div>
<?php
endif
;
?>
<?php
if
(
$entry
[
'changed_fields_json'
])
:
?>
<?php
$fields
=
json_decode
(
$entry
[
'changed_fields_json'
],
true
);
?>
<div
style=
"font-size:12px;color:#9CA3AF;"
>
الحقول المُغيَّرة:
<?=
e
(
implode
(
', '
,
$fields
??
[]))
?>
</div>
<?php
endif
;
?>
<?php
if
(
$entry
[
'before_data_json'
]
&&
$entry
[
'after_data_json'
])
:
?>
<?php
$before
=
json_decode
(
$entry
[
'before_data_json'
],
true
)
??
[];
$after
=
json_decode
(
$entry
[
'after_data_json'
],
true
)
??
[];
$changedKeys
=
json_decode
(
$entry
[
'changed_fields_json'
]
??
'[]'
,
true
)
??
[];
?>
<?php
if
(
!
empty
(
$changedKeys
))
:
?>
<details
style=
"margin-top:8px;"
>
<summary
style=
"cursor:pointer;color:#0D7377;font-size:13px;"
>
عرض التفاصيل
</summary>
<table
style=
"width:100%;margin-top:8px;font-size:12px;border:1px solid #E5E7EB;border-radius:4px;"
>
<thead><tr
style=
"background:#F9FAFB;"
><th
style=
"padding:6px 10px;text-align:right;"
>
الحقل
</th><th
style=
"padding:6px 10px;text-align:right;"
>
قبل
</th><th
style=
"padding:6px 10px;text-align:right;"
>
بعد
</th></tr></thead>
<tbody>
<?php
foreach
(
$changedKeys
as
$ck
)
:
?>
<tr>
<td
style=
"padding:4px 10px;font-weight:600;"
>
<?=
e
(
$ck
)
?>
</td>
<td
style=
"padding:4px 10px;color:#DC2626;background:#FEF2F2;"
>
<?=
e
((
string
)
(
$before
[
$ck
]
??
'—'
))
?>
</td>
<td
style=
"padding:4px 10px;color:#059669;background:#F0FDF4;"
>
<?=
e
((
string
)
(
$after
[
$ck
]
??
'—'
))
?>
</td>
</tr>
<?php
endforeach
;
?>
</tbody>
</table>
</details>
<?php
endif
;
?>
<?php
endif
;
?>
</div>
</div>
<?php
endforeach
;
?>
</div>
<?php
endif
;
?>
</div>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Audit/Views/index.php
0 → 100644
View file @
2ed42d50
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
سجل المراجعة
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<div
class=
"card"
style=
"margin-bottom:20px;padding:15px;"
>
<form
method=
"GET"
action=
"/audit"
style=
"display:flex;gap:10px;flex-wrap:wrap;align-items:end;"
>
<div>
<label
class=
"form-label"
style=
"font-size:12px;"
>
بحث
</label>
<input
type=
"text"
name=
"q"
value=
"
<?=
e
(
$filters
[
'search'
])
?>
"
placeholder=
"بحث..."
class=
"form-input"
style=
"min-width:150px;"
>
</div>
<div>
<label
class=
"form-label"
style=
"font-size:12px;"
>
الإجراء
</label>
<select
name=
"action"
class=
"form-select"
style=
"min-width:120px;"
>
<option
value=
""
>
الكل
</option>
<?php
foreach
(
$actions
as
$a
)
:
?>
<option
value=
"
<?=
e
(
$a
)
?>
"
<?=
$filters
[
'action'
]
===
$a
?
'selected'
:
''
?>
>
<?=
e
(
$a
)
?>
</option>
<?php
endforeach
;
?>
</select>
</div>
<div>
<label
class=
"form-label"
style=
"font-size:12px;"
>
نوع الكيان
</label>
<select
name=
"entity_type"
class=
"form-select"
style=
"min-width:120px;"
>
<option
value=
""
>
الكل
</option>
<?php
foreach
(
$entityTypes
as
$et
)
:
?>
<option
value=
"
<?=
e
(
$et
)
?>
"
<?=
$filters
[
'entity_type'
]
===
$et
?
'selected'
:
''
?>
>
<?=
e
(
$et
)
?>
</option>
<?php
endforeach
;
?>
</select>
</div>
<div>
<label
class=
"form-label"
style=
"font-size:12px;"
>
الموظف
</label>
<select
name=
"employee_id"
class=
"form-select"
style=
"min-width:150px;"
>
<option
value=
""
>
الكل
</option>
<?php
foreach
(
$employees
as
$emp
)
:
?>
<option
value=
"
<?=
(
int
)
$emp
[
'id'
]
?>
"
<?=
$filters
[
'employee_id'
]
==
$emp
[
'id'
]
?
'selected'
:
''
?>
>
<?=
e
(
$emp
[
'full_name_ar'
])
?>
</option>
<?php
endforeach
;
?>
</select>
</div>
<div>
<label
class=
"form-label"
style=
"font-size:12px;"
>
من
</label>
<input
type=
"date"
name=
"date_from"
value=
"
<?=
e
(
$filters
[
'date_from'
])
?>
"
class=
"form-input"
>
</div>
<div>
<label
class=
"form-label"
style=
"font-size:12px;"
>
إلى
</label>
<input
type=
"date"
name=
"date_to"
value=
"
<?=
e
(
$filters
[
'date_to'
])
?>
"
class=
"form-input"
>
</div>
<button
type=
"submit"
class=
"btn btn-outline"
>
بحث
</button>
<a
href=
"/audit"
class=
"btn btn-sm btn-outline"
style=
"color:#6B7280;"
>
مسح
</a>
</form>
</div>
<div
class=
"card"
>
<div
class=
"table-responsive"
>
<table
class=
"data-table"
>
<thead>
<tr>
<th>
التاريخ
</th>
<th>
الموظف
</th>
<th>
الإجراء
</th>
<th>
نوع الكيان
</th>
<th>
الكيان
</th>
<th>
التغييرات
</th>
<th>
IP
</th>
</tr>
</thead>
<tbody>
<?php
foreach
(
$rows
as
$r
)
:
?>
<tr>
<td
style=
"font-size:12px;white-space:nowrap;"
>
<?=
e
(
$r
[
'created_at'
])
?>
</td>
<td>
<?=
e
(
$r
[
'employee_name'
]
??
'—'
)
?>
</td>
<td>
<?php
$actionColors
=
[
'create'
=>
'#059669'
,
'update'
=>
'#0284C7'
,
'delete'
=>
'#DC2626'
,
'archive'
=>
'#D97706'
,
'login'
=>
'#6B7280'
,
'logout'
=>
'#6B7280'
,
'login_failed'
=>
'#DC2626'
,
];
$color
=
$actionColors
[
$r
[
'action'
]]
??
'#6B7280'
;
?>
<span
style=
"color:
<?=
$color
?>
;font-weight:600;font-size:13px;"
>
<?=
e
(
$r
[
'action'
])
?>
</span>
</td>
<td
style=
"font-size:13px;"
>
<?=
e
(
$r
[
'entity_type'
]
??
'—'
)
?>
</td>
<td>
<?php
if
(
$r
[
'entity_type'
]
&&
$r
[
'entity_id'
])
:
?>
<a
href=
"/audit/entity/
<?=
urlencode
(
$r
[
'entity_type'
])
?>
/
<?=
(
int
)
$r
[
'entity_id'
]
?>
"
style=
"color:#0D7377;"
>
<?=
e
(
$r
[
'entity_label'
]
??
'#'
.
$r
[
'entity_id'
])
?>
</a>
<?php
else
:
?>
<?=
e
(
$r
[
'entity_label'
]
??
'—'
)
?>
<?php
endif
;
?>
</td>
<td
style=
"font-size:12px;color:#6B7280;max-width:200px;overflow:hidden;text-overflow:ellipsis;"
>
<?php
if
(
$r
[
'changed_fields_json'
])
:
?>
<?php
$fields
=
json_decode
(
$r
[
'changed_fields_json'
],
true
);
?>
<?=
e
(
implode
(
', '
,
$fields
??
[]))
?>
<?php
elseif
(
$r
[
'notes'
])
:
?>
<?=
e
(
mb_substr
(
$r
[
'notes'
],
0
,
80
))
?>
<?php
else
:
?>
—
<?php
endif
;
?>
</td>
<td
style=
"direction:ltr;text-align:right;font-size:12px;color:#9CA3AF;"
>
<?=
e
(
$r
[
'ip_address'
]
??
'—'
)
?>
</td>
</tr>
<?php
endforeach
;
?>
<?php
if
(
empty
(
$rows
))
:
?>
<tr><td
colspan=
"7"
style=
"text-align:center;padding:40px;color:#6B7280;"
>
لا توجد سجلات
</td></tr>
<?php
endif
;
?>
</tbody>
</table>
</div>
<?php
if
(
$pagination
[
'last_page'
]
>
1
)
:
?>
<div
style=
"padding:15px;"
>
<nav
class=
"pagination-wrapper"
>
<ul
class=
"pagination"
style=
"display:flex;gap:5px;list-style:none;padding:0;justify-content:center;"
>
<?php
if
(
$pagination
[
'has_prev'
])
:
?>
<li><a
href=
"?page=
<?=
$pagination
[
'prev_page'
]
?>
&q=
<?=
urlencode
(
$filters
[
'search'
])
?>
&action=
<?=
urlencode
(
$filters
[
'action'
])
?>
&entity_type=
<?=
urlencode
(
$filters
[
'entity_type'
])
?>
&employee_id=
<?=
urlencode
(
$filters
[
'employee_id'
])
?>
&date_from=
<?=
urlencode
(
$filters
[
'date_from'
])
?>
&date_to=
<?=
urlencode
(
$filters
[
'date_to'
])
?>
"
class=
"btn btn-sm btn-outline"
>
السابق
</a></li>
<?php
endif
;
?>
<?php
foreach
(
$pagination
[
'pages'
]
as
$p
)
:
?>
<?php
if
(
$p
===
'...'
)
:
?>
<li
style=
"padding:5px;"
>
...
</li>
<?php
else
:
?>
<li><a
href=
"?page=
<?=
$p
?>
&q=
<?=
urlencode
(
$filters
[
'search'
])
?>
&action=
<?=
urlencode
(
$filters
[
'action'
])
?>
&entity_type=
<?=
urlencode
(
$filters
[
'entity_type'
])
?>
&employee_id=
<?=
urlencode
(
$filters
[
'employee_id'
])
?>
&date_from=
<?=
urlencode
(
$filters
[
'date_from'
])
?>
&date_to=
<?=
urlencode
(
$filters
[
'date_to'
])
?>
"
class=
"btn btn-sm
<?=
$p
===
$pagination
[
'current_page'
]
?
'btn-primary'
:
'btn-outline'
?>
"
>
<?=
$p
?>
</a></li>
<?php
endif
;
?>
<?php
endforeach
;
?>
<?php
if
(
$pagination
[
'has_next'
])
:
?>
<li><a
href=
"?page=
<?=
$pagination
[
'next_page'
]
?>
&q=
<?=
urlencode
(
$filters
[
'search'
])
?>
&action=
<?=
urlencode
(
$filters
[
'action'
])
?>
&entity_type=
<?=
urlencode
(
$filters
[
'entity_type'
])
?>
&employee_id=
<?=
urlencode
(
$filters
[
'employee_id'
])
?>
&date_from=
<?=
urlencode
(
$filters
[
'date_from'
])
?>
&date_to=
<?=
urlencode
(
$filters
[
'date_to'
])
?>
"
class=
"btn btn-sm btn-outline"
>
التالي
</a></li>
<?php
endif
;
?>
</ul>
</nav>
</div>
<?php
endif
;
?>
</div>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Audit/bootstrap.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
use
App\Core\EventBus
;
use
App\Modules\Audit\Services\AuditService
;
// Register database hooks for automatic audit logging
AuditService
::
registerDatabaseHooks
();
// Listen for auth events
EventBus
::
listen
(
'auth.login'
,
function
(
array
&
$data
)
{
AuditService
::
log
(
'login'
,
'employee'
,
$data
[
'employee_id'
]
??
null
,
null
,
null
,
null
,
'تسجيل دخول من IP: '
.
(
$data
[
'ip'
]
??
'unknown'
)
);
});
EventBus
::
listen
(
'auth.logout'
,
function
(
array
&
$data
)
{
AuditService
::
log
(
'logout'
,
'employee'
,
$data
[
'employee_id'
]
??
null
,
null
,
null
,
null
,
'تسجيل خروج'
);
});
EventBus
::
listen
(
'auth.password_changed'
,
function
(
array
&
$data
)
{
AuditService
::
log
(
'update'
,
'employee'
,
$data
[
'employee_id'
]
??
null
,
null
,
null
,
null
,
'تم تغيير كلمة المرور'
);
});
\ No newline at end of file
app/Modules/Branches/Controllers/BranchController.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Branches\Controllers
;
use
App\Core\Controller
;
use
App\Core\Request
;
use
App\Core\Response
;
use
App\Core\App
;
use
App\Modules\Branches\Models\Branch
;
class
BranchController
extends
Controller
{
public
function
index
(
Request
$request
)
:
Response
{
$db
=
App
::
getInstance
()
->
db
();
$branches
=
$db
->
select
(
"
SELECT b.*,
(SELECT COUNT(*) FROM employees e WHERE e.branch_id = b.id AND e.is_archived = 0 AND e.is_active = 1) as emp_count
FROM branches b
ORDER BY b.name_ar
"
);
return
$this
->
view
(
'Branches.Views.index'
,
[
'branches'
=>
$branches
]);
}
public
function
create
(
Request
$request
)
:
Response
{
return
$this
->
view
(
'Branches.Views.create'
,
[]);
}
public
function
store
(
Request
$request
)
:
Response
{
$data
=
$this
->
validate
(
$request
->
all
(),
[
'branch_code'
=>
'required|string|min:2|max:50'
,
'name_ar'
=>
'required|string|min:2|max:200'
,
'name_en'
=>
'nullable|string|max:200'
,
'address'
=>
'nullable|string|max:1000'
,
'phone'
=>
'nullable|string|max:20'
,
'manager_name'
=>
'nullable|string|max:200'
,
]);
$existing
=
App
::
getInstance
()
->
db
()
->
selectOne
(
"SELECT id FROM branches WHERE branch_code = ?"
,
[
$data
[
'branch_code'
]]);
if
(
$existing
)
{
return
$this
->
redirect
(
'/branches/create'
)
->
withError
(
'كود الفرع مستخدم بالفعل'
);
}
Branch
::
create
([
'branch_code'
=>
$data
[
'branch_code'
],
'name_ar'
=>
$data
[
'name_ar'
],
'name_en'
=>
$data
[
'name_en'
]
??
null
,
'address'
=>
$data
[
'address'
]
??
null
,
'phone'
=>
$data
[
'phone'
]
??
null
,
'manager_name'
=>
$data
[
'manager_name'
]
??
null
,
'is_active'
=>
1
,
]);
return
$this
->
redirect
(
'/branches'
)
->
withSuccess
(
'تم إنشاء الفرع بنجاح'
);
}
public
function
show
(
Request
$request
,
string
$id
)
:
Response
{
$branch
=
Branch
::
find
((
int
)
$id
);
if
(
!
$branch
)
{
return
$this
->
redirect
(
'/branches'
)
->
withError
(
'الفرع غير موجود'
);
}
return
$this
->
view
(
'Branches.Views.show'
,
[
'branch'
=>
$branch
,
'empCount'
=>
$branch
->
getEmployeeCount
(),
'memCount'
=>
$branch
->
getMemberCount
(),
]);
}
public
function
edit
(
Request
$request
,
string
$id
)
:
Response
{
$branch
=
Branch
::
find
((
int
)
$id
);
if
(
!
$branch
)
{
return
$this
->
redirect
(
'/branches'
)
->
withError
(
'الفرع غير موجود'
);
}
return
$this
->
view
(
'Branches.Views.edit'
,
[
'branch'
=>
$branch
]);
}
public
function
update
(
Request
$request
,
string
$id
)
:
Response
{
$branch
=
Branch
::
find
((
int
)
$id
);
if
(
!
$branch
)
{
return
$this
->
redirect
(
'/branches'
)
->
withError
(
'الفرع غير موجود'
);
}
$data
=
$this
->
validate
(
$request
->
all
(),
[
'name_ar'
=>
'required|string|min:2|max:200'
,
'name_en'
=>
'nullable|string|max:200'
,
'address'
=>
'nullable|string|max:1000'
,
'phone'
=>
'nullable|string|max:20'
,
'manager_name'
=>
'nullable|string|max:200'
,
'is_active'
=>
'nullable|in:0,1'
,
]);
$branch
->
update
([
'name_ar'
=>
$data
[
'name_ar'
],
'name_en'
=>
$data
[
'name_en'
]
??
null
,
'address'
=>
$data
[
'address'
]
??
null
,
'phone'
=>
$data
[
'phone'
]
??
null
,
'manager_name'
=>
$data
[
'manager_name'
]
??
null
,
'is_active'
=>
isset
(
$data
[
'is_active'
])
?
(
int
)
$data
[
'is_active'
]
:
(
int
)
$branch
->
is_active
,
]);
return
$this
->
redirect
(
'/branches'
)
->
withSuccess
(
'تم تحديث الفرع بنجاح'
);
}
}
\ No newline at end of file
app/Modules/Branches/Models/Branch.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Branches\Models
;
use
App\Core\Model
;
use
App\Core\App
;
class
Branch
extends
Model
{
protected
static
string
$table
=
'branches'
;
protected
static
string
$primaryKey
=
'id'
;
protected
static
bool
$timestamps
=
true
;
protected
static
bool
$softDelete
=
false
;
protected
static
bool
$dispatchEvents
=
true
;
protected
static
array
$fillable
=
[
'branch_code'
,
'name_ar'
,
'name_en'
,
'address'
,
'phone'
,
'manager_name'
,
'is_active'
,
'member_count_cache'
,
];
public
static
function
allActive
()
:
array
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
select
(
"SELECT * FROM branches WHERE is_active = 1 ORDER BY name_ar"
);
}
public
static
function
getByCode
(
string
$code
)
:
?
static
{
$db
=
App
::
getInstance
()
->
db
();
$row
=
$db
->
selectOne
(
"SELECT * FROM branches WHERE branch_code = ?"
,
[
$code
]);
if
(
!
$row
)
{
return
null
;
}
$instance
=
new
static
(
$row
);
$instance
->
exists
=
true
;
return
$instance
;
}
public
function
getMemberCount
()
:
int
{
$db
=
App
::
getInstance
()
->
db
();
try
{
if
(
!
$db
->
tableExists
(
'members'
))
{
return
0
;
}
$result
=
$db
->
selectOne
(
"SELECT COUNT(*) as cnt FROM members WHERE branch_id = ? AND is_archived = 0"
,
[
$this
->
id
]
);
return
(
int
)
(
$result
[
'cnt'
]
??
0
);
}
catch
(
\Throwable
$e
)
{
return
(
int
)
(
$this
->
member_count_cache
??
0
);
}
}
public
function
getEmployeeCount
()
:
int
{
$db
=
App
::
getInstance
()
->
db
();
$result
=
$db
->
selectOne
(
"SELECT COUNT(*) as cnt FROM employees WHERE branch_id = ? AND is_archived = 0 AND is_active = 1"
,
[
$this
->
id
]
);
return
(
int
)
(
$result
[
'cnt'
]
??
0
);
}
}
\ No newline at end of file
app/Modules/Branches/Routes.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
return
[
[
'GET'
,
'/branches'
,
'Branches\Controllers\BranchController@index'
,
[
'auth'
],
'settings.view'
],
[
'GET'
,
'/branches/create'
,
'Branches\Controllers\BranchController@create'
,
[
'auth'
],
'settings.edit'
],
[
'POST'
,
'/branches'
,
'Branches\Controllers\BranchController@store'
,
[
'auth'
,
'csrf'
],
'settings.edit'
],
[
'GET'
,
'/branches/{id:\d+}'
,
'Branches\Controllers\BranchController@show'
,
[
'auth'
],
'settings.view'
],
[
'GET'
,
'/branches/{id:\d+}/edit'
,
'Branches\Controllers\BranchController@edit'
,
[
'auth'
],
'settings.edit'
],
[
'POST'
,
'/branches/{id:\d+}'
,
'Branches\Controllers\BranchController@update'
,
[
'auth'
,
'csrf'
],
'settings.edit'
],
];
\ No newline at end of file
app/Modules/Branches/Views/create.php
0 → 100644
View file @
2ed42d50
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
إنشاء فرع جديد
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<form
method=
"POST"
action=
"/branches"
>
<?=
csrf_field
()
?>
<div
class=
"card"
style=
"padding:20px;"
>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:20px;"
>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
كود الفرع
<span
style=
"color:#DC2626;"
>
*
</span></label>
<input
type=
"text"
name=
"branch_code"
value=
"
<?=
e
(
old
(
'branch_code'
))
?>
"
class=
"form-input"
required
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الاسم بالعربي
<span
style=
"color:#DC2626;"
>
*
</span></label>
<input
type=
"text"
name=
"name_ar"
value=
"
<?=
e
(
old
(
'name_ar'
))
?>
"
class=
"form-input"
required
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الاسم بالإنجليزي
</label>
<input
type=
"text"
name=
"name_en"
value=
"
<?=
e
(
old
(
'name_en'
))
?>
"
class=
"form-input"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الهاتف
</label>
<input
type=
"text"
name=
"phone"
value=
"
<?=
e
(
old
(
'phone'
))
?>
"
class=
"form-input"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
اسم المدير
</label>
<input
type=
"text"
name=
"manager_name"
value=
"
<?=
e
(
old
(
'manager_name'
))
?>
"
class=
"form-input"
>
</div>
<div
class=
"form-group"
style=
"grid-column:1/-1;"
>
<label
class=
"form-label"
>
العنوان
</label>
<textarea
name=
"address"
class=
"form-textarea"
rows=
"3"
>
<?=
e
(
old
(
'address'
))
?>
</textarea>
</div>
</div>
</div>
<button
type=
"submit"
class=
"btn btn-primary"
style=
"margin-top:20px;"
>
إنشاء الفرع
</button>
<a
href=
"/branches"
class=
"btn btn-outline"
style=
"margin-top:20px;"
>
إلغاء
</a>
</form>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Branches/Views/edit.php
0 → 100644
View file @
2ed42d50
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
تعديل الفرع:
<?=
e
(
$branch
->
name_ar
)
?><?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<form
method=
"POST"
action=
"/branches/
<?=
(
int
)
$branch
->
id
?>
"
>
<?=
csrf_field
()
?>
<div
class=
"card"
style=
"padding:20px;"
>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:20px;"
>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
كود الفرع
</label>
<input
type=
"text"
value=
"
<?=
e
(
$branch
->
branch_code
)
?>
"
class=
"form-input"
disabled
style=
"background:#f3f4f6;"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الاسم بالعربي
<span
style=
"color:#DC2626;"
>
*
</span></label>
<input
type=
"text"
name=
"name_ar"
value=
"
<?=
e
(
$branch
->
name_ar
)
?>
"
class=
"form-input"
required
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الاسم بالإنجليزي
</label>
<input
type=
"text"
name=
"name_en"
value=
"
<?=
e
(
$branch
->
name_en
??
''
)
?>
"
class=
"form-input"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الهاتف
</label>
<input
type=
"text"
name=
"phone"
value=
"
<?=
e
(
$branch
->
phone
??
''
)
?>
"
class=
"form-input"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
اسم المدير
</label>
<input
type=
"text"
name=
"manager_name"
value=
"
<?=
e
(
$branch
->
manager_name
??
''
)
?>
"
class=
"form-input"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الحالة
</label>
<select
name=
"is_active"
class=
"form-select"
>
<option
value=
"1"
<?=
$branch
->
is_active
?
'selected'
:
''
?>
>
نشط
</option>
<option
value=
"0"
<?=
!
$branch
->
is_active
?
'selected'
:
''
?>
>
معطل
</option>
</select>
</div>
<div
class=
"form-group"
style=
"grid-column:1/-1;"
>
<label
class=
"form-label"
>
العنوان
</label>
<textarea
name=
"address"
class=
"form-textarea"
rows=
"3"
>
<?=
e
(
$branch
->
address
??
''
)
?>
</textarea>
</div>
</div>
</div>
<button
type=
"submit"
class=
"btn btn-primary"
style=
"margin-top:20px;"
>
حفظ التعديلات
</button>
<a
href=
"/branches"
class=
"btn btn-outline"
style=
"margin-top:20px;"
>
إلغاء
</a>
</form>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Branches/Views/index.php
0 → 100644
View file @
2ed42d50
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
إدارة الفروع
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'page_actions'
);
?>
<a
href=
"/branches/create"
class=
"btn btn-primary"
>
+ فرع جديد
</a>
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<div
class=
"card"
>
<div
class=
"table-responsive"
>
<table
class=
"data-table"
>
<thead>
<tr>
<th>
كود الفرع
</th>
<th>
الاسم (عربي)
</th>
<th>
الاسم (إنجليزي)
</th>
<th>
الهاتف
</th>
<th>
المدير
</th>
<th>
الموظفون
</th>
<th>
الحالة
</th>
<th>
الإجراءات
</th>
</tr>
</thead>
<tbody>
<?php
foreach
(
$branches
as
$b
)
:
?>
<tr>
<td><code>
<?=
e
(
$b
[
'branch_code'
])
?>
</code></td>
<td>
<?=
e
(
$b
[
'name_ar'
])
?>
</td>
<td>
<?=
e
(
$b
[
'name_en'
]
??
'—'
)
?>
</td>
<td>
<?=
e
(
$b
[
'phone'
]
??
'—'
)
?>
</td>
<td>
<?=
e
(
$b
[
'manager_name'
]
??
'—'
)
?>
</td>
<td>
<?=
(
int
)
$b
[
'emp_count'
]
?>
</td>
<td>
<?php
if
(
$b
[
'is_active'
])
:
?>
<span
style=
"color:#059669;font-weight:600;"
>
● نشط
</span>
<?php
else
:
?>
<span
style=
"color:#DC2626;font-weight:600;"
>
● معطل
</span>
<?php
endif
;
?>
</td>
<td>
<div
style=
"display:flex;gap:5px;"
>
<a
href=
"/branches/
<?=
(
int
)
$b
[
'id'
]
?>
"
class=
"btn btn-sm btn-outline"
>
عرض
</a>
<a
href=
"/branches/
<?=
(
int
)
$b
[
'id'
]
?>
/edit"
class=
"btn btn-sm btn-outline"
>
تعديل
</a>
</div>
</td>
</tr>
<?php
endforeach
;
?>
<?php
if
(
empty
(
$branches
))
:
?>
<tr><td
colspan=
"8"
style=
"text-align:center;padding:40px;color:#6B7280;"
>
لا توجد فروع
</td></tr>
<?php
endif
;
?>
</tbody>
</table>
</div>
</div>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Branches/Views/show.php
0 → 100644
View file @
2ed42d50
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
الفرع:
<?=
e
(
$branch
->
name_ar
)
?><?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'page_actions'
);
?>
<a
href=
"/branches/
<?=
(
int
)
$branch
->
id
?>
/edit"
class=
"btn btn-outline"
>
تعديل
</a>
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:20px;"
>
<div
class=
"card"
style=
"padding:20px;"
>
<h3
style=
"margin-bottom:15px;color:#0D7377;"
>
بيانات الفرع
</h3>
<table
style=
"width:100%;font-size:14px;"
>
<tr><td
style=
"padding:8px 0;color:#6B7280;width:40%;"
>
كود الفرع
</td><td
style=
"padding:8px 0;font-weight:600;"
>
<?=
e
(
$branch
->
branch_code
)
?>
</td></tr>
<tr><td
style=
"padding:8px 0;color:#6B7280;"
>
الاسم بالعربي
</td><td
style=
"padding:8px 0;"
>
<?=
e
(
$branch
->
name_ar
)
?>
</td></tr>
<tr><td
style=
"padding:8px 0;color:#6B7280;"
>
الاسم بالإنجليزي
</td><td
style=
"padding:8px 0;"
>
<?=
e
(
$branch
->
name_en
??
'—'
)
?>
</td></tr>
<tr><td
style=
"padding:8px 0;color:#6B7280;"
>
العنوان
</td><td
style=
"padding:8px 0;"
>
<?=
e
(
$branch
->
address
??
'—'
)
?>
</td></tr>
<tr><td
style=
"padding:8px 0;color:#6B7280;"
>
الهاتف
</td><td
style=
"padding:8px 0;"
>
<?=
e
(
$branch
->
phone
??
'—'
)
?>
</td></tr>
<tr><td
style=
"padding:8px 0;color:#6B7280;"
>
المدير
</td><td
style=
"padding:8px 0;"
>
<?=
e
(
$branch
->
manager_name
??
'—'
)
?>
</td></tr>
<tr><td
style=
"padding:8px 0;color:#6B7280;"
>
الحالة
</td><td
style=
"padding:8px 0;"
>
<?=
$branch
->
is_active
?
'<span style="color:#059669;">● نشط</span>'
:
'<span style="color:#DC2626;">● معطل</span>'
?>
</td></tr>
</table>
</div>
<div
class=
"card"
style=
"padding:20px;"
>
<h3
style=
"margin-bottom:15px;color:#0D7377;"
>
إحصائيات
</h3>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:15px;"
>
<div
style=
"background:#F0FDF4;padding:15px;border-radius:8px;text-align:center;"
>
<div
style=
"font-size:28px;font-weight:700;color:#059669;"
>
<?=
$empCount
?>
</div>
<div
style=
"color:#6B7280;font-size:13px;"
>
موظفون
</div>
</div>
<div
style=
"background:#EFF6FF;padding:15px;border-radius:8px;text-align:center;"
>
<div
style=
"font-size:28px;font-weight:700;color:#0284C7;"
>
<?=
$memCount
?>
</div>
<div
style=
"color:#6B7280;font-size:13px;"
>
أعضاء
</div>
</div>
</div>
</div>
</div>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Branches/bootstrap.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
use
App\Core\Registries\MenuRegistry
;
use
App\Core\Registries\PermissionRegistry
;
MenuRegistry
::
register
(
'branches_settings'
,
[
'label_ar'
=>
'الفروع والإعدادات'
,
'label_en'
=>
'Branches & Settings'
,
'icon'
=>
'🏢'
,
'route'
=>
'/branches'
,
'permission'
=>
'settings.view'
,
'order'
=>
200
,
'children'
=>
[
[
'label_ar'
=>
'الفروع'
,
'label_en'
=>
'Branches'
,
'route'
=>
'/branches'
,
'permission'
=>
'settings.view'
,
'order'
=>
1
],
[
'label_ar'
=>
'إعدادات النظام'
,
'label_en'
=>
'Settings'
,
'route'
=>
'/settings'
,
'permission'
=>
'settings.view'
,
'order'
=>
2
],
[
'label_ar'
=>
'سجل المراجعة'
,
'label_en'
=>
'Audit Log'
,
'route'
=>
'/audit'
,
'permission'
=>
'report.view_audit'
,
'order'
=>
3
],
],
]);
PermissionRegistry
::
register
(
'settings'
,
[
'settings.view'
=>
[
'ar'
=>
'عرض الإعدادات'
,
'en'
=>
'View Settings'
],
'settings.edit'
=>
[
'ar'
=>
'تعديل الإعدادات'
,
'en'
=>
'Edit Settings'
],
'settings.backup'
=>
[
'ar'
=>
'النسخ الاحتياطي'
,
'en'
=>
'Backup'
],
]);
PermissionRegistry
::
register
(
'audit'
,
[
'report.view_audit'
=>
[
'ar'
=>
'عرض سجل المراجعة'
,
'en'
=>
'View Audit Log'
],
]);
\ No newline at end of file
app/Modules/Settings/Controllers/SettingsController.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Settings\Controllers
;
use
App\Core\Controller
;
use
App\Core\Request
;
use
App\Core\Response
;
use
App\Core\App
;
use
App\Modules\Settings\Models\SystemConfig
;
class
SettingsController
extends
Controller
{
public
function
index
(
Request
$request
)
:
Response
{
$grouped
=
SystemConfig
::
getAllGrouped
();
return
$this
->
view
(
'Settings.Views.index'
,
[
'grouped'
=>
$grouped
]);
}
public
function
editGroup
(
Request
$request
,
string
$group
)
:
Response
{
$settings
=
SystemConfig
::
getByGroup
(
$group
);
if
(
empty
(
$settings
))
{
return
$this
->
redirect
(
'/settings'
)
->
withError
(
'مجموعة الإعدادات غير موجودة'
);
}
return
$this
->
view
(
'Settings.Views.edit-group'
,
[
'group'
=>
$group
,
'settings'
=>
$settings
,
]);
}
public
function
updateGroup
(
Request
$request
,
string
$group
)
:
Response
{
$settings
=
SystemConfig
::
getByGroup
(
$group
);
if
(
empty
(
$settings
))
{
return
$this
->
redirect
(
'/settings'
)
->
withError
(
'مجموعة الإعدادات غير موجودة'
);
}
$db
=
App
::
getInstance
()
->
db
();
$updated
=
0
;
foreach
(
$settings
as
$setting
)
{
if
(
!
$setting
[
'is_editable'
])
{
continue
;
}
$key
=
$setting
[
'config_key'
];
$newValue
=
$request
->
post
(
'config_'
.
str_replace
(
'.'
,
'_'
,
$key
),
''
);
if
((
string
)
$newValue
!==
(
string
)
(
$setting
[
'config_value'
]
??
''
))
{
SystemConfig
::
set
(
$key
,
(
string
)
$newValue
);
$updated
++
;
}
}
return
$this
->
redirect
(
'/settings'
)
->
withSuccess
(
"تم تحديث
{
$updated
}
إعداد بنجاح"
);
}
}
\ No newline at end of file
app/Modules/Settings/Models/SystemConfig.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Settings\Models
;
use
App\Core\App
;
class
SystemConfig
{
public
static
function
getAllGrouped
()
:
array
{
$db
=
App
::
getInstance
()
->
db
();
$rows
=
$db
->
select
(
"SELECT * FROM system_config ORDER BY group_name, config_key"
);
$grouped
=
[];
foreach
(
$rows
as
$row
)
{
$grouped
[
$row
[
'group_name'
]][]
=
$row
;
}
return
$grouped
;
}
public
static
function
getByGroup
(
string
$group
)
:
array
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
select
(
"SELECT * FROM system_config WHERE group_name = ? ORDER BY config_key"
,
[
$group
]);
}
public
static
function
get
(
string
$key
)
:
?
array
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
selectOne
(
"SELECT * FROM system_config WHERE config_key = ?"
,
[
$key
]);
}
public
static
function
set
(
string
$key
,
string
$value
)
:
void
{
$db
=
App
::
getInstance
()
->
db
();
$existing
=
$db
->
selectOne
(
"SELECT id FROM system_config WHERE config_key = ?"
,
[
$key
]);
if
(
$existing
)
{
$db
->
update
(
'system_config'
,
[
'config_value'
=>
$value
,
'updated_at'
=>
date
(
'Y-m-d H:i:s'
),
],
'`id` = ?'
,
[
$existing
[
'id'
]]);
}
}
public
static
function
getGroups
()
:
array
{
$db
=
App
::
getInstance
()
->
db
();
$rows
=
$db
->
select
(
"SELECT DISTINCT group_name FROM system_config ORDER BY group_name"
);
return
array_column
(
$rows
,
'group_name'
);
}
}
\ No newline at end of file
app/Modules/Settings/Routes.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
return
[
[
'GET'
,
'/settings'
,
'Settings\Controllers\SettingsController@index'
,
[
'auth'
],
'settings.view'
],
[
'GET'
,
'/settings/group/{group}'
,
'Settings\Controllers\SettingsController@editGroup'
,
[
'auth'
],
'settings.edit'
],
[
'POST'
,
'/settings/group/{group}'
,
'Settings\Controllers\SettingsController@updateGroup'
,
[
'auth'
,
'csrf'
],
'settings.edit'
],
];
\ No newline at end of file
app/Modules/Settings/Views/edit-group.php
0 → 100644
View file @
2ed42d50
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
تعديل إعدادات:
<?=
e
(
$group
)
?><?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<form
method=
"POST"
action=
"/settings/group/
<?=
urlencode
(
$group
)
?>
"
>
<?=
csrf_field
()
?>
<div
class=
"card"
style=
"padding:20px;"
>
<?php
foreach
(
$settings
as
$s
)
:
?>
<div
class=
"form-group"
style=
"margin-bottom:20px;padding-bottom:20px;border-bottom:1px solid #F3F4F6;"
>
<label
class=
"form-label"
style=
"font-weight:600;"
>
<?=
e
(
$s
[
'description_ar'
]
??
$s
[
'config_key'
])
?>
<small
style=
"color:#9CA3AF;display:block;font-weight:400;"
>
<?=
e
(
$s
[
'config_key'
])
?>
(
<?=
e
(
$s
[
'config_type'
])
?>
)
</small>
</label>
<?php
if
(
!
$s
[
'is_editable'
])
:
?>
<input
type=
"text"
value=
"
<?=
e
(
$s
[
'config_value'
]
??
''
)
?>
"
class=
"form-input"
disabled
style=
"background:#f3f4f6;"
>
<small
style=
"color:#DC2626;"
>
هذا الإعداد غير قابل للتعديل
</small>
<?php
elseif
(
$s
[
'config_type'
]
===
'boolean'
)
:
?>
<select
name=
"config_
<?=
e
(
str_replace
(
'.'
,
'_'
,
$s
[
'config_key'
]))
?>
"
class=
"form-select"
>
<option
value=
"1"
<?=
$s
[
'config_value'
]
===
'1'
?
'selected'
:
''
?>
>
نعم
</option>
<option
value=
"0"
<?=
$s
[
'config_value'
]
!==
'1'
?
'selected'
:
''
?>
>
لا
</option>
</select>
<?php
elseif
(
$s
[
'config_type'
]
===
'json'
)
:
?>
<textarea
name=
"config_
<?=
e
(
str_replace
(
'.'
,
'_'
,
$s
[
'config_key'
]))
?>
"
class=
"form-textarea"
rows=
"4"
>
<?=
e
(
$s
[
'config_value'
]
??
''
)
?>
</textarea>
<?php
else
:
?>
<input
type=
"text"
name=
"config_
<?=
e
(
str_replace
(
'.'
,
'_'
,
$s
[
'config_key'
]))
?>
"
value=
"
<?=
e
(
$s
[
'config_value'
]
??
''
)
?>
"
class=
"form-input"
>
<?php
endif
;
?>
</div>
<?php
endforeach
;
?>
</div>
<button
type=
"submit"
class=
"btn btn-primary"
style=
"margin-top:20px;"
>
حفظ الإعدادات
</button>
<a
href=
"/settings"
class=
"btn btn-outline"
style=
"margin-top:20px;"
>
إلغاء
</a>
</form>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Settings/Views/index.php
0 → 100644
View file @
2ed42d50
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
إعدادات النظام
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<?php
if
(
empty
(
$grouped
))
:
?>
<div
class=
"card"
style=
"padding:40px;text-align:center;color:#6B7280;"
>
<p>
لا توجد إعدادات مسجلة بعد. ستظهر تلقائياً عند إضافة الوحدات.
</p>
</div>
<?php
else
:
?>
<?php
foreach
(
$grouped
as
$group
=>
$settings
)
:
?>
<div
class=
"card"
style=
"margin-bottom:20px;"
>
<div
style=
"padding:15px 20px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center;"
>
<h3
style=
"margin:0;color:#0D7377;"
>
<?=
e
(
$group
)
?>
</h3>
<a
href=
"/settings/group/
<?=
urlencode
(
$group
)
?>
"
class=
"btn btn-sm btn-outline"
>
تعديل
</a>
</div>
<div
class=
"table-responsive"
>
<table
class=
"data-table"
>
<thead>
<tr>
<th>
المفتاح
</th>
<th>
القيمة
</th>
<th>
النوع
</th>
<th>
الوصف
</th>
<th>
قابل للتعديل
</th>
</tr>
</thead>
<tbody>
<?php
foreach
(
$settings
as
$s
)
:
?>
<tr>
<td><code
style=
"font-size:12px;"
>
<?=
e
(
$s
[
'config_key'
])
?>
</code></td>
<td
style=
"max-width:200px;overflow:hidden;text-overflow:ellipsis;"
>
<?=
e
(
$s
[
'config_value'
]
??
'—'
)
?>
</td>
<td><span
style=
"background:#F3F4F6;padding:2px 8px;border-radius:4px;font-size:12px;"
>
<?=
e
(
$s
[
'config_type'
])
?>
</span></td>
<td
style=
"color:#6B7280;font-size:13px;"
>
<?=
e
(
$s
[
'description_ar'
]
??
'—'
)
?>
</td>
<td>
<?=
$s
[
'is_editable'
]
?
'<span style="color:#059669;">نعم</span>'
:
'<span style="color:#DC2626;">لا</span>'
?>
</td>
</tr>
<?php
endforeach
;
?>
</tbody>
</table>
</div>
</div>
<?php
endforeach
;
?>
<?php
endif
;
?>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Settings/bootstrap.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
// Menu and permissions registered in Branches/bootstrap.php (shared group)
\ No newline at end of file
database/migrations/Phase_03_001_create_audit_trail_table.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
return
[
'up'
=>
"
CREATE TABLE IF NOT EXISTS `audit_trail` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`employee_id` BIGINT UNSIGNED NULL,
`employee_name` VARCHAR(200) NULL,
`action` VARCHAR(50) NOT NULL,
`entity_type` VARCHAR(100) NULL,
`entity_id` BIGINT UNSIGNED NULL,
`entity_label` VARCHAR(255) NULL,
`before_data_json` JSON NULL,
`after_data_json` JSON NULL,
`changed_fields_json` JSON NULL,
`ip_address` VARCHAR(45) NULL,
`user_agent` VARCHAR(500) NULL,
`session_id` VARCHAR(128) NULL,
`route` VARCHAR(255) NULL,
`notes` TEXT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_audit_entity` (`entity_type`, `entity_id`),
INDEX `idx_audit_employee` (`employee_id`),
INDEX `idx_audit_date` (`created_at`),
INDEX `idx_audit_action` (`action`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
"
,
'down'
=>
"DROP TABLE IF EXISTS `audit_trail`"
,
];
\ No newline at end of file
database/migrations/Phase_03_002_create_governorates_table.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
return
[
'up'
=>
"
CREATE TABLE IF NOT EXISTS `governorates` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`code` VARCHAR(2) NOT NULL,
`name_ar` VARCHAR(100) NOT NULL,
`name_en` VARCHAR(100) NOT NULL,
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
UNIQUE KEY `uq_governorates_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
"
,
'down'
=>
"DROP TABLE IF EXISTS `governorates`"
,
];
\ No newline at end of file
database/migrations/Phase_03_003_create_countries_table.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
return
[
'up'
=>
"
CREATE TABLE IF NOT EXISTS `countries` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`iso_code` VARCHAR(3) NOT NULL,
`name_ar` VARCHAR(200) NOT NULL,
`name_en` VARCHAR(200) NOT NULL,
`nationality_ar` VARCHAR(200) NOT NULL,
`nationality_en` VARCHAR(200) NULL,
`phone_code` VARCHAR(10) NULL,
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
UNIQUE KEY `uq_countries_iso` (`iso_code`),
INDEX `idx_countries_name_ar` (`name_ar`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
"
,
'down'
=>
"DROP TABLE IF EXISTS `countries`"
,
];
\ No newline at end of file
database/migrations/Phase_03_004_create_qualifications_table.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
return
[
'up'
=>
"
CREATE TABLE IF NOT EXISTS `qualifications` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`code` VARCHAR(20) NOT NULL,
`name_ar` VARCHAR(100) NOT NULL,
`name_en` VARCHAR(100) NOT NULL,
`sort_order` INT UNSIGNED NOT NULL DEFAULT 0,
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
UNIQUE KEY `uq_qualifications_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
"
,
'down'
=>
"DROP TABLE IF EXISTS `qualifications`"
,
];
\ No newline at end of file
database/seeds/Phase_03_001_seed_branches.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
use
App\Core\Database
;
return
function
(
Database
$db
)
:
void
{
$branches
=
[
[
'branch_code'
=>
'sheraton'
,
'name_ar'
=>
'فرع شيراتون / مصر الجديدة'
,
'name_en'
=>
'Sheraton / Masr El-Gedida'
,
'phone'
=>
'0222222001'
],
[
'branch_code'
=>
'sadis'
,
'name_ar'
=>
'فرع السادس من أكتوبر'
,
'name_en'
=>
'6th of October'
,
'phone'
=>
'0222222002'
],
[
'branch_code'
=>
'new_capital'
,
'name_ar'
=>
'فرع العاصمة الإدارية الجديدة'
,
'name_en'
=>
'New Administrative Capital'
,
'phone'
=>
'0222222003'
],
];
foreach
(
$branches
as
$branch
)
{
$existing
=
$db
->
selectOne
(
"SELECT id FROM branches WHERE branch_code = ?"
,
[
$branch
[
'branch_code'
]]);
if
(
$existing
)
{
continue
;
}
$db
->
insert
(
'branches'
,
[
'branch_code'
=>
$branch
[
'branch_code'
],
'name_ar'
=>
$branch
[
'name_ar'
],
'name_en'
=>
$branch
[
'name_en'
],
'phone'
=>
$branch
[
'phone'
],
'is_active'
=>
1
,
'created_at'
=>
date
(
'Y-m-d H:i:s'
),
'updated_at'
=>
date
(
'Y-m-d H:i:s'
),
]);
}
};
\ No newline at end of file
database/seeds/Phase_03_002_seed_governorates.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
use
App\Core\Database
;
return
function
(
Database
$db
)
:
void
{
$governorates
=
[
[
'code'
=>
'01'
,
'name_ar'
=>
'القاهرة'
,
'name_en'
=>
'Cairo'
],
[
'code'
=>
'02'
,
'name_ar'
=>
'الإسكندرية'
,
'name_en'
=>
'Alexandria'
],
[
'code'
=>
'03'
,
'name_ar'
=>
'بورسعيد'
,
'name_en'
=>
'Port Said'
],
[
'code'
=>
'04'
,
'name_ar'
=>
'السويس'
,
'name_en'
=>
'Suez'
],
[
'code'
=>
'11'
,
'name_ar'
=>
'دمياط'
,
'name_en'
=>
'Damietta'
],
[
'code'
=>
'12'
,
'name_ar'
=>
'الدقهلية'
,
'name_en'
=>
'Dakahlia'
],
[
'code'
=>
'13'
,
'name_ar'
=>
'الشرقية'
,
'name_en'
=>
'Sharqia'
],
[
'code'
=>
'14'
,
'name_ar'
=>
'القليوبية'
,
'name_en'
=>
'Qalyubia'
],
[
'code'
=>
'15'
,
'name_ar'
=>
'كفر الشيخ'
,
'name_en'
=>
'Kafr El Sheikh'
],
[
'code'
=>
'16'
,
'name_ar'
=>
'الغربية'
,
'name_en'
=>
'Gharbia'
],
[
'code'
=>
'17'
,
'name_ar'
=>
'المنوفية'
,
'name_en'
=>
'Menoufia'
],
[
'code'
=>
'18'
,
'name_ar'
=>
'البحيرة'
,
'name_en'
=>
'Beheira'
],
[
'code'
=>
'19'
,
'name_ar'
=>
'الإسماعيلية'
,
'name_en'
=>
'Ismailia'
],
[
'code'
=>
'21'
,
'name_ar'
=>
'الجيزة'
,
'name_en'
=>
'Giza'
],
[
'code'
=>
'22'
,
'name_ar'
=>
'بني سويف'
,
'name_en'
=>
'Beni Suef'
],
[
'code'
=>
'23'
,
'name_ar'
=>
'الفيوم'
,
'name_en'
=>
'Fayoum'
],
[
'code'
=>
'24'
,
'name_ar'
=>
'المنيا'
,
'name_en'
=>
'Minya'
],
[
'code'
=>
'25'
,
'name_ar'
=>
'أسيوط'
,
'name_en'
=>
'Assiut'
],
[
'code'
=>
'26'
,
'name_ar'
=>
'سوهاج'
,
'name_en'
=>
'Sohag'
],
[
'code'
=>
'27'
,
'name_ar'
=>
'قنا'
,
'name_en'
=>
'Qena'
],
[
'code'
=>
'28'
,
'name_ar'
=>
'أسوان'
,
'name_en'
=>
'Aswan'
],
[
'code'
=>
'29'
,
'name_ar'
=>
'الأقصر'
,
'name_en'
=>
'Luxor'
],
[
'code'
=>
'31'
,
'name_ar'
=>
'البحر الأحمر'
,
'name_en'
=>
'Red Sea'
],
[
'code'
=>
'32'
,
'name_ar'
=>
'الوادي الجديد'
,
'name_en'
=>
'New Valley'
],
[
'code'
=>
'33'
,
'name_ar'
=>
'مطروح'
,
'name_en'
=>
'Matrouh'
],
[
'code'
=>
'34'
,
'name_ar'
=>
'شمال سيناء'
,
'name_en'
=>
'North Sinai'
],
[
'code'
=>
'35'
,
'name_ar'
=>
'جنوب سيناء'
,
'name_en'
=>
'South Sinai'
],
[
'code'
=>
'88'
,
'name_ar'
=>
'خارج الجمهورية'
,
'name_en'
=>
'Born Abroad'
],
];
foreach
(
$governorates
as
$gov
)
{
$existing
=
$db
->
selectOne
(
"SELECT id FROM governorates WHERE code = ?"
,
[
$gov
[
'code'
]]);
if
(
$existing
)
{
continue
;
}
$db
->
insert
(
'governorates'
,
[
'code'
=>
$gov
[
'code'
],
'name_ar'
=>
$gov
[
'name_ar'
],
'name_en'
=>
$gov
[
'name_en'
],
'is_active'
=>
1
,
]);
}
};
\ No newline at end of file
database/seeds/Phase_03_003_seed_countries.php
0 → 100644
View file @
2ed42d50
This diff is collapsed.
Click to expand it.
database/seeds/Phase_03_004_seed_qualifications.php
0 → 100644
View file @
2ed42d50
<?php
declare
(
strict_types
=
1
);
use
App\Core\Database
;
return
function
(
Database
$db
)
:
void
{
$qualifications
=
[
[
'code'
=>
'high'
,
'name_ar'
=>
'مؤهل عالي'
,
'name_en'
=>
'High Qualification'
,
'sort_order'
=>
1
],
[
'code'
=>
'medium'
,
'name_ar'
=>
'مؤهل متوسط'
,
'name_en'
=>
'Medium Qualification'
,
'sort_order'
=>
2
],
[
'code'
=>
'none'
,
'name_ar'
=>
'بدون مؤهل'
,
'name_en'
=>
'No Qualification'
,
'sort_order'
=>
3
],
];
foreach
(
$qualifications
as
$q
)
{
$existing
=
$db
->
selectOne
(
"SELECT id FROM qualifications WHERE code = ?"
,
[
$q
[
'code'
]]);
if
(
$existing
)
{
continue
;
}
$db
->
insert
(
'qualifications'
,
[
'code'
=>
$q
[
'code'
],
'name_ar'
=>
$q
[
'name_ar'
],
'name_en'
=>
$q
[
'name_en'
],
'sort_order'
=>
$q
[
'sort_order'
],
'is_active'
=>
1
,
]);
}
};
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment