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
1e79566a
Commit
1e79566a
authored
Apr 07, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 11 files via Son of Anton
parent
2ed42d50
Changes
11
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
1289 additions
and
0 deletions
+1289
-0
ArchiveController.php
app/Modules/Archive/Controllers/ArchiveController.php
+125
-0
ArchiveSnapshot.php
app/Modules/Archive/Models/ArchiveSnapshot.php
+107
-0
MembershipNumberChain.php
app/Modules/Archive/Models/MembershipNumberChain.php
+59
-0
Routes.php
app/Modules/Archive/Routes.php
+11
-0
ArchiveService.php
app/Modules/Archive/Services/ArchiveService.php
+397
-0
compare.php
app/Modules/Archive/Views/compare.php
+166
-0
index.php
app/Modules/Archive/Views/index.php
+123
-0
show.php
app/Modules/Archive/Views/show.php
+233
-0
bootstrap.php
app/Modules/Archive/bootstrap.php
+22
-0
Phase_04_001_create_archive_snapshots_table.php
...igrations/Phase_04_001_create_archive_snapshots_table.php
+24
-0
Phase_04_002_create_membership_number_chain_table.php
...ons/Phase_04_002_create_membership_number_chain_table.php
+22
-0
No files found.
app/Modules/Archive/Controllers/ArchiveController.php
0 → 100644
View file @
1e79566a
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Archive\Controllers
;
use
App\Core\Controller
;
use
App\Core\Request
;
use
App\Core\Response
;
use
App\Modules\Archive\Models\ArchiveSnapshot
;
use
App\Modules\Archive\Services\ArchiveService
;
class
ArchiveController
extends
Controller
{
public
function
index
(
Request
$request
)
:
Response
{
$filters
=
[
'entity_type'
=>
$request
->
get
(
'entity_type'
,
''
),
'membership_number'
=>
$request
->
get
(
'membership_number'
,
''
),
'snapshot_reason'
=>
$request
->
get
(
'snapshot_reason'
,
''
),
'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
=
ArchiveSnapshot
::
search
(
$filters
,
25
,
$page
);
$entityTypes
=
ArchiveSnapshot
::
getDistinctEntityTypes
();
$reasons
=
ArchiveSnapshot
::
getDistinctReasons
();
return
$this
->
view
(
'Archive.Views.index'
,
[
'rows'
=>
$result
[
'data'
],
'pagination'
=>
$result
[
'pagination'
],
'filters'
=>
$filters
,
'entityTypes'
=>
$entityTypes
,
'reasons'
=>
$reasons
,
]);
}
public
function
show
(
Request
$request
,
string
$id
)
:
Response
{
$snapshot
=
ArchiveService
::
getSnapshot
((
int
)
$id
);
if
(
!
$snapshot
)
{
return
$this
->
redirect
(
'/archive'
)
->
withError
(
'اللقطة الأرشيفية غير موجودة'
);
}
// Get other snapshots for same entity for navigation
$otherSnapshots
=
ArchiveSnapshot
::
getForEntity
(
$snapshot
[
'entity_type'
],
(
int
)
$snapshot
[
'entity_id'
]);
return
$this
->
view
(
'Archive.Views.show'
,
[
'snapshot'
=>
$snapshot
,
'otherSnapshots'
=>
$otherSnapshots
,
]);
}
public
function
compare
(
Request
$request
,
string
$id1
,
string
$id2
)
:
Response
{
try
{
$diff
=
ArchiveService
::
compareSnapshots
((
int
)
$id1
,
(
int
)
$id2
);
}
catch
(
\RuntimeException
$e
)
{
return
$this
->
redirect
(
'/archive'
)
->
withError
(
$e
->
getMessage
());
}
$snap1
=
ArchiveService
::
getSnapshot
((
int
)
$id1
);
$snap2
=
ArchiveService
::
getSnapshot
((
int
)
$id2
);
return
$this
->
view
(
'Archive.Views.compare'
,
[
'diff'
=>
$diff
,
'snap1'
=>
$snap1
,
'snap2'
=>
$snap2
,
]);
}
public
function
entitySnapshots
(
Request
$request
,
string
$type
,
string
$id
)
:
Response
{
$snapshots
=
ArchiveService
::
getSnapshots
(
$type
,
(
int
)
$id
);
return
$this
->
view
(
'Archive.Views.index'
,
[
'rows'
=>
$snapshots
,
'pagination'
=>
[
'last_page'
=>
1
,
'current_page'
=>
1
],
'filters'
=>
[
'entity_type'
=>
$type
,
'search'
=>
''
,
'membership_number'
=>
''
,
'snapshot_reason'
=>
''
,
'date_from'
=>
''
,
'date_to'
=>
''
],
'entityTypes'
=>
ArchiveSnapshot
::
getDistinctEntityTypes
(),
'reasons'
=>
ArchiveSnapshot
::
getDistinctReasons
(),
'entityFilter'
=>
[
'type'
=>
$type
,
'id'
=>
(
int
)
$id
],
]);
}
public
function
numberChain
(
Request
$request
,
string
$number
)
:
Response
{
$chain
=
ArchiveService
::
getNumberChain
(
$number
);
$snapshots
=
ArchiveSnapshot
::
getByMembershipNumber
(
$number
);
return
$this
->
view
(
'Archive.Views.show'
,
[
'snapshot'
=>
null
,
'otherSnapshots'
=>
$snapshots
,
'numberChain'
=>
$chain
,
'membershipNumber'
=>
$number
,
'isChainView'
=>
true
,
]);
}
public
function
takeManual
(
Request
$request
)
:
Response
{
$entityType
=
trim
((
string
)
$request
->
post
(
'entity_type'
,
''
));
$entityId
=
(
int
)
$request
->
post
(
'entity_id'
,
0
);
$reason
=
trim
((
string
)
$request
->
post
(
'reason'
,
'manual'
));
$notes
=
trim
((
string
)
$request
->
post
(
'notes'
,
''
));
if
(
$entityType
===
''
||
$entityId
<=
0
)
{
return
$this
->
redirect
(
'/archive'
)
->
withError
(
'بيانات غير مكتملة'
);
}
try
{
$snapshotId
=
ArchiveService
::
takeSnapshot
(
$entityType
,
$entityId
,
$reason
,
$notes
?:
null
);
return
$this
->
redirect
(
"/archive/
{
$snapshotId
}
"
)
->
withSuccess
(
'تم أخذ اللقطة الأرشيفية بنجاح'
);
}
catch
(
\RuntimeException
$e
)
{
return
$this
->
redirect
(
'/archive'
)
->
withError
(
'فشل أخذ اللقطة: '
.
$e
->
getMessage
());
}
}
}
\ No newline at end of file
app/Modules/Archive/Models/ArchiveSnapshot.php
0 → 100644
View file @
1e79566a
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Archive\Models
;
use
App\Core\App
;
use
App\Core\Pagination
;
class
ArchiveSnapshot
{
public
static
function
create
(
array
$data
)
:
int
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
insert
(
'archive_snapshots'
,
$data
);
}
public
static
function
find
(
int
$id
)
:
?
array
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
selectOne
(
"SELECT * FROM archive_snapshots WHERE id = ?"
,
[
$id
]);
}
public
static
function
getForEntity
(
string
$entityType
,
int
$entityId
)
:
array
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
select
(
"SELECT * FROM archive_snapshots WHERE entity_type = ? AND entity_id = ? ORDER BY snapshot_taken_at DESC"
,
[
$entityType
,
$entityId
]
);
}
public
static
function
getByMembershipNumber
(
string
$number
)
:
array
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
select
(
"SELECT * FROM archive_snapshots WHERE membership_number = ? ORDER BY snapshot_taken_at DESC"
,
[
$number
]
);
}
public
static
function
search
(
array
$filters
,
int
$perPage
=
25
,
int
$page
=
1
)
:
array
{
$db
=
App
::
getInstance
()
->
db
();
$where
=
'1=1'
;
$params
=
[];
if
(
!
empty
(
$filters
[
'entity_type'
]))
{
$where
.=
' AND a.entity_type = ?'
;
$params
[]
=
$filters
[
'entity_type'
];
}
if
(
!
empty
(
$filters
[
'membership_number'
]))
{
$where
.=
' AND a.membership_number = ?'
;
$params
[]
=
$filters
[
'membership_number'
];
}
if
(
!
empty
(
$filters
[
'snapshot_reason'
]))
{
$where
.=
' AND a.snapshot_reason = ?'
;
$params
[]
=
$filters
[
'snapshot_reason'
];
}
if
(
!
empty
(
$filters
[
'date_from'
]))
{
$where
.=
' AND a.snapshot_taken_at >= ?'
;
$params
[]
=
$filters
[
'date_from'
]
.
' 00:00:00'
;
}
if
(
!
empty
(
$filters
[
'date_to'
]))
{
$where
.=
' AND a.snapshot_taken_at <= ?'
;
$params
[]
=
$filters
[
'date_to'
]
.
' 23:59:59'
;
}
if
(
!
empty
(
$filters
[
'search'
]))
{
$where
.=
' AND (a.membership_number LIKE ? OR a.entity_type LIKE ? OR a.notes LIKE ?)'
;
$s
=
'%'
.
$filters
[
'search'
]
.
'%'
;
$params
[]
=
$s
;
$params
[]
=
$s
;
$params
[]
=
$s
;
}
$countRow
=
$db
->
selectOne
(
"SELECT COUNT(*) as cnt FROM archive_snapshots a WHERE
{
$where
}
"
,
$params
);
$total
=
(
int
)
(
$countRow
[
'cnt'
]
??
0
);
$offset
=
(
$page
-
1
)
*
$perPage
;
$rows
=
$db
->
select
(
"SELECT a.* FROM archive_snapshots a WHERE
{
$where
}
ORDER BY a.snapshot_taken_at DESC LIMIT
{
$perPage
}
OFFSET
{
$offset
}
"
,
$params
);
$pagination
=
Pagination
::
paginate
(
$total
,
$perPage
,
$page
);
return
[
'data'
=>
$rows
,
'pagination'
=>
$pagination
];
}
public
static
function
getDistinctEntityTypes
()
:
array
{
$db
=
App
::
getInstance
()
->
db
();
$rows
=
$db
->
select
(
"SELECT DISTINCT entity_type FROM archive_snapshots ORDER BY entity_type"
);
return
array_column
(
$rows
,
'entity_type'
);
}
public
static
function
getDistinctReasons
()
:
array
{
$db
=
App
::
getInstance
()
->
db
();
$rows
=
$db
->
select
(
"SELECT DISTINCT snapshot_reason FROM archive_snapshots ORDER BY snapshot_reason"
);
return
array_column
(
$rows
,
'snapshot_reason'
);
}
}
\ No newline at end of file
app/Modules/Archive/Models/MembershipNumberChain.php
0 → 100644
View file @
1e79566a
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Archive\Models
;
use
App\Core\App
;
class
MembershipNumberChain
{
public
static
function
create
(
array
$data
)
:
int
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
insert
(
'membership_number_chain'
,
$data
);
}
public
static
function
find
(
int
$id
)
:
?
array
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
selectOne
(
"SELECT * FROM membership_number_chain WHERE id = ?"
,
[
$id
]);
}
public
static
function
getChainForNumber
(
string
$membershipNumber
)
:
array
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
select
(
"SELECT * FROM membership_number_chain WHERE membership_number = ? ORDER BY held_from ASC"
,
[
$membershipNumber
]
);
}
public
static
function
getCurrentHolder
(
string
$membershipNumber
)
:
?
array
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
selectOne
(
"SELECT * FROM membership_number_chain WHERE membership_number = ? AND held_until IS NULL ORDER BY held_from DESC LIMIT 1"
,
[
$membershipNumber
]
);
}
public
static
function
getHoldersForEntity
(
string
$entityType
,
int
$entityId
)
:
array
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
select
(
"SELECT * FROM membership_number_chain WHERE holder_entity_type = ? AND holder_entity_id = ? ORDER BY held_from DESC"
,
[
$entityType
,
$entityId
]
);
}
public
static
function
endCurrentHolder
(
string
$membershipNumber
)
:
void
{
$db
=
App
::
getInstance
()
->
db
();
$db
->
update
(
'membership_number_chain'
,
[
'held_until'
=>
date
(
'Y-m-d H:i:s'
)],
'`membership_number` = ? AND `held_until` IS NULL'
,
[
$membershipNumber
]
);
}
}
\ No newline at end of file
app/Modules/Archive/Routes.php
0 → 100644
View file @
1e79566a
<?php
declare
(
strict_types
=
1
);
return
[
[
'GET'
,
'/archive'
,
'Archive\Controllers\ArchiveController@index'
,
[
'auth'
],
'report.view_audit'
],
[
'GET'
,
'/archive/{id:\d+}'
,
'Archive\Controllers\ArchiveController@show'
,
[
'auth'
],
'report.view_audit'
],
[
'GET'
,
'/archive/compare/{id1:\d+}/{id2:\d+}'
,
'Archive\Controllers\ArchiveController@compare'
,
[
'auth'
],
'report.view_audit'
],
[
'GET'
,
'/archive/entity/{type}/{id:\d+}'
,
'Archive\Controllers\ArchiveController@entitySnapshots'
,
[
'auth'
],
'report.view_audit'
],
[
'GET'
,
'/archive/number-chain/{number}'
,
'Archive\Controllers\ArchiveController@numberChain'
,
[
'auth'
],
'report.view_audit'
],
[
'POST'
,
'/archive/snapshot'
,
'Archive\Controllers\ArchiveController@takeManual'
,
[
'auth'
,
'csrf'
],
'settings.edit'
],
];
\ No newline at end of file
app/Modules/Archive/Services/ArchiveService.php
0 → 100644
View file @
1e79566a
This diff is collapsed.
Click to expand it.
app/Modules/Archive/Views/compare.php
0 → 100644
View file @
1e79566a
This diff is collapsed.
Click to expand it.
app/Modules/Archive/Views/index.php
0 → 100644
View file @
1e79566a
<?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=
"/archive"
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=
"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=
"snapshot_reason"
class=
"form-select"
style=
"min-width:120px;"
>
<option
value=
""
>
الكل
</option>
<?php
foreach
(
$reasons
as
$r
)
:
?>
<option
value=
"
<?=
e
(
$r
)
?>
"
<?=
(
$filters
[
'snapshot_reason'
]
??
''
)
===
$r
?
'selected'
:
''
?>
>
<?=
e
(
$r
)
?>
</option>
<?php
endforeach
;
?>
</select>
</div>
<div>
<label
class=
"form-label"
style=
"font-size:12px;"
>
رقم العضوية
</label>
<input
type=
"text"
name=
"membership_number"
value=
"
<?=
e
(
$filters
[
'membership_number'
]
??
''
)
?>
"
placeholder=
"رقم العضوية"
class=
"form-input"
style=
"min-width:120px;"
>
</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=
"/archive"
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>
بواسطة
</th>
<th>
ملاحظات
</th>
<th>
الإجراءات
</th>
</tr>
</thead>
<tbody>
<?php
foreach
(
$rows
as
$r
)
:
?>
<tr>
<td>
<?=
(
int
)
$r
[
'id'
]
?>
</td>
<td
style=
"font-size:12px;white-space:nowrap;"
>
<?=
e
(
$r
[
'snapshot_taken_at'
])
?>
</td>
<td><code
style=
"font-size:12px;"
>
<?=
e
(
$r
[
'entity_type'
])
?>
</code></td>
<td>
<?=
(
int
)
$r
[
'entity_id'
]
?>
</td>
<td>
<?php
if
(
$r
[
'membership_number'
])
:
?>
<a
href=
"/archive/number-chain/
<?=
urlencode
(
$r
[
'membership_number'
])
?>
"
style=
"color:#0D7377;font-weight:600;"
>
<?=
e
(
$r
[
'membership_number'
])
?>
</a>
<?php
else
:
?>
—
<?php
endif
;
?>
</td>
<td>
<?php
$reasonColors
=
[
'transfer'
=>
'#0284C7'
,
'separation'
=>
'#D97706'
,
'divorce'
=>
'#DC2626'
,
'death'
=>
'#6B7280'
,
'waiver'
=>
'#7C3AED'
,
'status_change'
=>
'#059669'
,
'data_correction'
=>
'#0D7377'
,
'manual'
=>
'#9CA3AF'
,
];
$color
=
$reasonColors
[
$r
[
'snapshot_reason'
]]
??
'#6B7280'
;
?>
<span
style=
"color:
<?=
$color
?>
;font-weight:600;font-size:13px;"
>
<?=
e
(
$r
[
'snapshot_reason'
])
?>
</span>
</td>
<td
style=
"font-size:13px;"
>
<?=
$r
[
'snapshot_taken_by'
]
?
'#'
.
(
int
)
$r
[
'snapshot_taken_by'
]
:
'النظام'
?>
</td>
<td
style=
"font-size:12px;color:#6B7280;max-width:200px;overflow:hidden;text-overflow:ellipsis;"
>
<?=
e
(
mb_substr
(
$r
[
'notes'
]
??
''
,
0
,
80
))
?:
'—'
?>
</td>
<td>
<div
style=
"display:flex;gap:5px;"
>
<a
href=
"/archive/
<?=
(
int
)
$r
[
'id'
]
?>
"
class=
"btn btn-sm btn-outline"
>
عرض
</a>
</div>
</td>
</tr>
<?php
endforeach
;
?>
<?php
if
(
empty
(
$rows
))
:
?>
<tr><td
colspan=
"9"
style=
"text-align:center;padding:40px;color:#6B7280;"
>
لا توجد لقطات أرشيفية
</td></tr>
<?php
endif
;
?>
</tbody>
</table>
</div>
<?php
if
(
isset
(
$pagination
[
'last_page'
])
&&
$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'
]
??
false
)
:
?>
<li><a
href=
"?page=
<?=
$pagination
[
'prev_page'
]
?>
&q=
<?=
urlencode
(
$filters
[
'search'
]
??
''
)
?>
&entity_type=
<?=
urlencode
(
$filters
[
'entity_type'
]
??
''
)
?>
&snapshot_reason=
<?=
urlencode
(
$filters
[
'snapshot_reason'
]
??
''
)
?>
&membership_number=
<?=
urlencode
(
$filters
[
'membership_number'
]
??
''
)
?>
&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'
]
??
''
)
?>
&entity_type=
<?=
urlencode
(
$filters
[
'entity_type'
]
??
''
)
?>
&snapshot_reason=
<?=
urlencode
(
$filters
[
'snapshot_reason'
]
??
''
)
?>
&membership_number=
<?=
urlencode
(
$filters
[
'membership_number'
]
??
''
)
?>
&date_from=
<?=
urlencode
(
$filters
[
'date_from'
]
??
''
)
?>
&date_to=
<?=
urlencode
(
$filters
[
'date_to'
]
??
''
)
?>
"
class=
"btn btn-sm
<?=
$p
===
(
$pagination
[
'current_page'
]
??
1
)
?
'btn-primary'
:
'btn-outline'
?>
"
>
<?=
$p
?>
</a></li>
<?php
endif
;
?>
<?php
endforeach
;
?>
<?php
if
(
$pagination
[
'has_next'
]
??
false
)
:
?>
<li><a
href=
"?page=
<?=
$pagination
[
'next_page'
]
?>
&q=
<?=
urlencode
(
$filters
[
'search'
]
??
''
)
?>
&entity_type=
<?=
urlencode
(
$filters
[
'entity_type'
]
??
''
)
?>
&snapshot_reason=
<?=
urlencode
(
$filters
[
'snapshot_reason'
]
??
''
)
?>
&membership_number=
<?=
urlencode
(
$filters
[
'membership_number'
]
??
''
)
?>
&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/Archive/Views/show.php
0 → 100644
View file @
1e79566a
This diff is collapsed.
Click to expand it.
app/Modules/Archive/bootstrap.php
0 → 100644
View file @
1e79566a
<?php
declare
(
strict_types
=
1
);
use
App\Core\Registries\MenuRegistry
;
use
App\Core\Registries\PermissionRegistry
;
// Add archive as a child under the existing branches_settings menu
// We check if the menu already exists and add our child to it
$existing
=
MenuRegistry
::
get
(
'branches_settings'
);
if
(
$existing
)
{
$children
=
$existing
[
'children'
]
??
[];
$children
[]
=
[
'label_ar'
=>
'الأرشيف'
,
'label_en'
=>
'Archive'
,
'route'
=>
'/archive'
,
'permission'
=>
'report.view_audit'
,
'order'
=>
4
];
$existing
[
'children'
]
=
$children
;
MenuRegistry
::
register
(
'branches_settings'
,
$existing
);
}
PermissionRegistry
::
register
(
'archive'
,
[
'archive.view'
=>
[
'ar'
=>
'عرض الأرشيف'
,
'en'
=>
'View Archive'
],
'archive.take_snapshot'
=>
[
'ar'
=>
'أخذ لقطة أرشيفية'
,
'en'
=>
'Take Archive Snapshot'
],
'archive.compare'
=>
[
'ar'
=>
'مقارنة اللقطات'
,
'en'
=>
'Compare Snapshots'
],
'archive.number_chain'
=>
[
'ar'
=>
'سلسلة أرقام العضوية'
,
'en'
=>
'Number Chain'
],
]);
\ No newline at end of file
database/migrations/Phase_04_001_create_archive_snapshots_table.php
0 → 100644
View file @
1e79566a
<?php
declare
(
strict_types
=
1
);
return
[
'up'
=>
"
CREATE TABLE IF NOT EXISTS `archive_snapshots` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`entity_type` VARCHAR(100) NOT NULL,
`entity_id` BIGINT UNSIGNED NOT NULL,
`membership_number` VARCHAR(20) NULL,
`snapshot_reason` VARCHAR(50) NOT NULL,
`full_data_json` JSON NOT NULL,
`related_data_json` JSON NULL,
`snapshot_taken_by` BIGINT UNSIGNED NULL,
`snapshot_taken_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`notes` TEXT NULL,
INDEX `idx_archive_entity` (`entity_type`, `entity_id`),
INDEX `idx_archive_membership` (`membership_number`),
INDEX `idx_archive_date` (`snapshot_taken_at`),
INDEX `idx_archive_reason` (`snapshot_reason`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
"
,
'down'
=>
"DROP TABLE IF EXISTS `archive_snapshots`"
,
];
\ No newline at end of file
database/migrations/Phase_04_002_create_membership_number_chain_table.php
0 → 100644
View file @
1e79566a
<?php
declare
(
strict_types
=
1
);
return
[
'up'
=>
"
CREATE TABLE IF NOT EXISTS `membership_number_chain` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`membership_number` VARCHAR(20) NOT NULL,
`holder_type` VARCHAR(50) NOT NULL,
`holder_entity_type` VARCHAR(100) NOT NULL,
`holder_entity_id` BIGINT UNSIGNED NOT NULL,
`previous_holder_id` BIGINT UNSIGNED NULL,
`held_from` DATETIME NOT NULL,
`held_until` DATETIME NULL DEFAULT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_number_chain_number` (`membership_number`),
INDEX `idx_number_chain_holder` (`holder_entity_type`, `holder_entity_id`),
CONSTRAINT `fk_number_chain_prev` FOREIGN KEY (`previous_holder_id`) REFERENCES `membership_number_chain`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
"
,
'down'
=>
"DROP TABLE IF EXISTS `membership_number_chain`"
,
];
\ 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