Commit 5b2dbb02 authored by DevPilot's avatar DevPilot

feat(sa): أداة تكرار الرقم القومي للاعبين + منع التكرار من أصله

المشكلة اللي اتبلّغت: نفس اللاعب متسجّل مرتين بنفس الرقم القومي — مرة
كعضو ومرة كغير عضو. اتأكدت من الداتا الحية: فعلاً فيه 7 أرقام قومية
مكررة، واحد منهم متكرر 4 مرات. شاشة تسجيل اللاعبين ما كانش فيها أي فحص
للتكرار، والـ index على العمود non-unique.

المنع:
- PlayerController::store() و ::update() بقوا يرفضوا أي رقم قومي مسجّل
  للاعب تاني، ويقولوا اسم اللاعب ورقمه عشان الموظف يفتح ملفه بدل ما
  يسجّله من جديد.

التنضيف (شاشة جديدة /sa/players/duplicates):
- بتعرض كل مجموعة مكررة وجوّاها اللاعبين، وقدّام كل نسخة عدد السجلات
  المرتبطة بيها (اشتراكات، حضور، بطاقات، مستندات...) عشان الموظف يعرف
  النسخة اللي عليها شغل فعلي.
- بتفرّق بين «نفس الاسم» و«أسماء مختلفة» وبتحذّر: مش كل تكرار هو نفس
  الشخص — أحياناً حد كتب رقم قومي غلط لشخص تاني.
- إجراءين: دمج (بينقل كل السجلات للنسخة المختارة ويأرشف الباقي)، أو
  تفريغ الرقم القومي لو اتضح إنهم أشخاص مختلفين.
- الدمج كله في transaction، ومفيش حذف نهائي — النسخ المدموجة بتتأرشف
  بملاحظة توضّح اتدمجت في مين.

ملاحظة مهمة في التنفيذ: في الداتابيز 45 عمود اسمه player_id، لكن كتير
منهم بيشاور على جدول players بتاع «شؤون اللاعبين» — جدول تاني خالص.
فالدمج بيشتغل على قايمة جداول مكتوبة بإيد ومتأكد منها، مش على اسم العمود،
عشان ما نخربش بيانات موديول تاني.

كمان: مسمّى «إيداع ورقة قبض برسم التحصيل» في قيود الأوراق التجارية اتغيّر
لـ «تسليم ورقة قبض للبنك للتحصيل» — «برسم التحصيل» مصطلح بنكي معناه
«عشان يتحصّل»، بس بتتقري كأن فيه رسم اتخصم.
parent 1031b8c0
...@@ -39,7 +39,9 @@ final class InstrumentPostingService ...@@ -39,7 +39,9 @@ final class InstrumentPostingService
private const LEGS = [ private const LEGS = [
// Received instruments // Received instruments
'instrument:deposited' => [ 'instrument:deposited' => [
'label' => 'إيداع ورقة قبض برسم التحصيل', // «برسم التحصيل» مصطلح بنكي معناه إن الشيك اتسلّم للبنك عشان يحصّله،
// مش إن فيه رسم اتخصم. اتكتبت بوضوح عشان محدش يفهمها غلط.
'label' => 'تسليم ورقة قبض للبنك للتحصيل',
'counter' => 'instrument:under_collection_account', // Dr شيكات تحت التحصيل 'counter' => 'instrument:under_collection_account', // Dr شيكات تحت التحصيل
'line' => 'instrument:notes_receivable', // Cr أوراق قبض 'line' => 'instrument:notes_receivable', // Cr أوراق قبض
'inflow' => true, 'inflow' => true,
......
...@@ -180,6 +180,15 @@ class PlayerController extends Controller ...@@ -180,6 +180,15 @@ class PlayerController extends Controller
if ($nationalId !== '' && strlen($nationalId) > 14) { if ($nationalId !== '' && strlen($nationalId) > 14) {
$errors[] = 'الرقم القومي يجب ألا يتجاوز 14 رقم'; $errors[] = 'الرقم القومي يجب ألا يتجاوز 14 رقم';
} }
// نفس الرقم القومي ما ينفعش يتسجّل لأكتر من لاعب — ده اللي كان بيخلي
// نفس الشخص يتسجّل مرتين، مرة كعضو ومرة كغير عضو.
if ($nationalId !== '') {
$dupePlayer = \App\Modules\SportsActivity\Services\PlayerDuplicateService::findByNationalId($nationalId, null);
if ($dupePlayer) {
$errors[] = 'الرقم القومي مسجّل بالفعل للاعب: ' . $dupePlayer['full_name_ar']
. ' (رقم ' . $dupePlayer['id'] . ') — افتح ملفه بدل ما تسجّله من جديد';
}
}
if ($phone !== '' && mb_strlen($phone) > 30) { if ($phone !== '' && mb_strlen($phone) > 30) {
$errors[] = 'رقم الهاتف يجب ألا يتجاوز 30 حرف'; $errors[] = 'رقم الهاتف يجب ألا يتجاوز 30 حرف';
} }
...@@ -447,6 +456,15 @@ class PlayerController extends Controller ...@@ -447,6 +456,15 @@ class PlayerController extends Controller
if ($nationalId !== '' && strlen($nationalId) > 14) { if ($nationalId !== '' && strlen($nationalId) > 14) {
$errors[] = 'الرقم القومي يجب ألا يتجاوز 14 رقم'; $errors[] = 'الرقم القومي يجب ألا يتجاوز 14 رقم';
} }
// نفس الرقم القومي ما ينفعش يتسجّل لأكتر من لاعب — ده اللي كان بيخلي
// نفس الشخص يتسجّل مرتين، مرة كعضو ومرة كغير عضو.
if ($nationalId !== '') {
$dupePlayer = \App\Modules\SportsActivity\Services\PlayerDuplicateService::findByNationalId($nationalId, (int) $id);
if ($dupePlayer) {
$errors[] = 'الرقم القومي مسجّل بالفعل للاعب: ' . $dupePlayer['full_name_ar']
. ' (رقم ' . $dupePlayer['id'] . ') — افتح ملفه بدل ما تسجّله من جديد';
}
}
if ($phone !== '' && mb_strlen($phone) > 30) { if ($phone !== '' && mb_strlen($phone) > 30) {
$errors[] = 'رقم الهاتف يجب ألا يتجاوز 30 حرف'; $errors[] = 'رقم الهاتف يجب ألا يتجاوز 30 حرف';
} }
......
<?php
declare(strict_types=1);
namespace App\Modules\SportsActivity\Controllers;
use App\Core\App;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Modules\SportsActivity\Services\PlayerDuplicateService;
class PlayerDuplicateController extends Controller
{
public function index(Request $request): Response
{
$this->authorize('sa.player.view');
return $this->view('SportsActivity.Views.players.duplicates', [
'groups' => PlayerDuplicateService::findDuplicates(),
]);
}
public function merge(Request $request): Response
{
$this->authorize('sa.player.manage');
$keepId = (int) $request->post('keep_id', 0);
$mergeIds = (array) ($request->post('merge_ids') ?? []);
if ($keepId <= 0) {
return $this->redirect('/sa/players/duplicates')->withError('لم يتم تحديد النسخة التي سيتم الاحتفاظ بها');
}
$employee = App::getInstance()->currentEmployee();
$result = PlayerDuplicateService::merge($keepId, $mergeIds, $employee ? (int) $employee->id : null);
if (!$result['success']) {
return $this->redirect('/sa/players/duplicates')->withError($result['error']);
}
$n = count($result['merged']);
$moved = (int) ($result['moved_total'] ?? 0);
return $this->redirect('/sa/players/duplicates')
->withSuccess("تم دمج {$n} نسخة في اللاعب رقم {$keepId} — ونُقل {$moved} سجل مرتبط");
}
public function clearNid(Request $request, string $id): Response
{
$this->authorize('sa.player.manage');
$employee = App::getInstance()->currentEmployee();
$result = PlayerDuplicateService::clearNationalId((int) $id, $employee ? (int) $employee->id : null);
if (!$result['success']) {
return $this->redirect('/sa/players/duplicates')->withError($result['error']);
}
return $this->redirect('/sa/players/duplicates')
->withSuccess('تم تفريغ الرقم القومي — افتح ملف اللاعب وأدخل الرقم الصحيح');
}
}
...@@ -47,6 +47,9 @@ return [ ...@@ -47,6 +47,9 @@ return [
// Players // Players
['GET', '/sa/players', 'SportsActivity\Controllers\PlayerController@index', ['auth'], 'sa.player.view'], ['GET', '/sa/players', 'SportsActivity\Controllers\PlayerController@index', ['auth'], 'sa.player.view'],
['GET', '/sa/players/duplicates', 'SportsActivity\Controllers\PlayerDuplicateController@index', ['auth'], 'sa.player.view'],
['POST', '/sa/players/duplicates/merge', 'SportsActivity\Controllers\PlayerDuplicateController@merge', ['auth', 'csrf'], 'sa.player.manage'],
['POST', '/sa/players/duplicates/{id:\d+}/clear-nid', 'SportsActivity\Controllers\PlayerDuplicateController@clearNid',['auth', 'csrf'], 'sa.player.manage'],
['GET', '/sa/players/create', 'SportsActivity\Controllers\PlayerController@create', ['auth'], 'sa.player.manage'], ['GET', '/sa/players/create', 'SportsActivity\Controllers\PlayerController@create', ['auth'], 'sa.player.manage'],
['POST', '/sa/players', 'SportsActivity\Controllers\PlayerController@store', ['auth', 'csrf'], 'sa.player.manage'], ['POST', '/sa/players', 'SportsActivity\Controllers\PlayerController@store', ['auth', 'csrf'], 'sa.player.manage'],
['GET', '/sa/players/{id:\d+}', 'SportsActivity\Controllers\PlayerController@show', ['auth'], 'sa.player.view'], ['GET', '/sa/players/{id:\d+}', 'SportsActivity\Controllers\PlayerController@show', ['auth'], 'sa.player.view'],
......
<?php
declare(strict_types=1);
namespace App\Modules\SportsActivity\Services;
use App\Core\App;
use App\Core\Logger;
/**
* تكرار اللاعبين بنفس الرقم القومي في sa_players.
*
* الشاشة القديمة ما كانتش بتتأكد إن الرقم القومي مستخدم قبل كده، فنفس
* الشخص ممكن يتسجّل أكتر من مرة — مرة كعضو ومرة كغير عضو مثلاً — وكل
* نسخة ليها اشتراكاتها وحضورها وبطاقتها لوحدها.
*
* مهم: مش كل تكرار هو نفس الشخص. أحياناً حد كتب رقم قومي غلط لشخص تاني،
* فبيبان إن الاتنين ليهم نفس الرقم وهما مختلفين. عشان كده الأداة بتعرض
* الأسماء وبتسيب القرار للموظف: يدمج، أو يصلّح الرقم الغلط.
*/
final class PlayerDuplicateService
{
/**
* الجداول اللي بتشاور على sa_players فعلاً.
*
* متكتوبة بإيد عن قصد: في الداتابيز أعمدة كتير اسمها player_id بس
* بتشاور على جدول players بتاع «شؤون اللاعبين» — جدول تاني خالص.
* لو دمجنا على أساس اسم العمود هنخرّب بيانات موديول تاني.
*/
private const REFERENCING_TABLES = [
// عليها FK حقيقي
'sa_attendance',
'sa_booking_participants',
'sa_gate_access_log',
'sa_group_players',
'sa_player_cards',
'sa_player_documents',
'sa_registrations',
'sa_subscriptions',
'sa_waitlist',
// من غير FK بس تابعة للنشاط الرياضي بوضوح
'sa_game_tickets',
'sa_locker_rentals',
'sa_makeup_sessions',
'sa_pool_tickets',
'sa_training_attendance',
];
/** مجموعات الأرقام القومية المكررة، وجوه كل مجموعة اللاعبين بتفاصيلهم. */
public static function findDuplicates(): array
{
$db = App::getInstance()->db();
$nids = $db->select(
"SELECT national_id, COUNT(*) AS c
FROM sa_players
WHERE national_id IS NOT NULL AND national_id <> '' AND is_archived = 0
GROUP BY national_id
HAVING c > 1
ORDER BY c DESC, national_id ASC"
);
$groups = [];
foreach ($nids as $row) {
$players = $db->select(
"SELECT id, full_name_ar, player_type, member_id, registration_serial,
phone, date_of_birth, gender, card_status, medical_status, created_at
FROM sa_players
WHERE national_id = ? AND is_archived = 0
ORDER BY id ASC",
[$row['national_id']]
);
foreach ($players as &$p) {
$p['links'] = self::countLinks((int) $p['id']);
}
unset($p);
// أسماء مختلفة = على الأرجح رقم قومي متكتب غلط، مش نفس الشخص
$names = array_unique(array_map(
static fn ($p) => self::normalizeName((string) ($p['full_name_ar'] ?? '')),
$players
));
$groups[] = [
'national_id' => $row['national_id'],
'count' => (int) $row['c'],
'players' => $players,
'same_name' => count($names) === 1,
];
}
return $groups;
}
/** عدد السجلات المرتبطة بلاعب في كل جدول — بيوضّح مين النسخة اللي عليها شغل. */
public static function countLinks(int $playerId): array
{
$db = App::getInstance()->db();
$out = ['total' => 0, 'by_table' => []];
foreach (self::REFERENCING_TABLES as $table) {
try {
$row = $db->selectOne("SELECT COUNT(*) AS n FROM `{$table}` WHERE player_id = ?", [$playerId]);
$n = (int) ($row['n'] ?? 0);
if ($n > 0) {
$out['by_table'][$table] = $n;
$out['total'] += $n;
}
} catch (\Throwable $e) {
// جدول مش موجود في النسخة دي — عادي، بنكمّل
continue;
}
}
return $out;
}
/**
* دمج نسخ لاعب مكررة في نسخة واحدة.
*
* كل السجلات المرتبطة بتتحوّل للنسخة اللي هتفضل، والباقي بيتأرشف مع
* ملاحظة توضّح الدمج. مفيش حذف نهائي — عشان يفضل في أثر لو حصل غلط.
*/
public static function merge(int $keepId, array $mergeIds, ?int $employeeId = null): array
{
$db = App::getInstance()->db();
$mergeIds = array_values(array_unique(array_filter(
array_map('intval', $mergeIds),
static fn ($id) => $id > 0 && $id !== $keepId
)));
if (!$mergeIds) {
return ['success' => false, 'error' => 'لم يتم اختيار أي نسخة للدمج'];
}
$keep = $db->selectOne("SELECT * FROM sa_players WHERE id = ? AND is_archived = 0", [$keepId]);
if (!$keep) {
return ['success' => false, 'error' => 'اللاعب المختار للاحتفاظ غير موجود'];
}
$placeholders = implode(',', array_fill(0, count($mergeIds), '?'));
$losers = $db->select(
"SELECT id, full_name_ar, national_id FROM sa_players WHERE id IN ({$placeholders}) AND is_archived = 0",
$mergeIds
);
if (count($losers) !== count($mergeIds)) {
return ['success' => false, 'error' => 'إحدى النسخ المختارة غير موجودة أو مؤرشفة بالفعل'];
}
// كلهم لازم يكونوا بنفس الرقم القومي — ضمان إننا مش بندمج ناس مختلفين
foreach ($losers as $l) {
if ((string) $l['national_id'] !== (string) $keep['national_id']) {
return ['success' => false, 'error' => 'لا يمكن الدمج: الأرقام القومية غير متطابقة'];
}
}
$moved = [];
$db->beginTransaction();
try {
foreach (self::REFERENCING_TABLES as $table) {
try {
$n = 0;
foreach ($mergeIds as $lid) {
$stmt = $db->query("UPDATE `{$table}` SET player_id = ? WHERE player_id = ?", [$keepId, $lid]);
$n += $stmt->rowCount();
}
if ($n > 0) {
$moved[$table] = $n;
}
} catch (\Throwable $e) {
continue; // جدول مش موجود
}
}
$now = date('Y-m-d H:i:s');
foreach ($losers as $l) {
$db->update('sa_players', [
'is_archived' => 1,
'archived_at' => $now,
'archived_by' => $employeeId,
'national_id' => null, // نفضّي الرقم عشان ما يفضلش مكرر
'notes' => 'تم دمجه في اللاعب رقم ' . $keepId . ' بتاريخ ' . $now,
'updated_at' => $now,
], '`id` = ?', [(int) $l['id']]);
}
$db->commit();
} catch (\Throwable $e) {
$db->rollBack();
Logger::error('PlayerDuplicateService::merge failed: ' . $e->getMessage());
return ['success' => false, 'error' => 'فشل الدمج: ' . $e->getMessage()];
}
Logger::info("PlayerDuplicateService: merged " . implode(',', $mergeIds) . " into {$keepId}", $moved);
return [
'success' => true,
'kept' => $keepId,
'merged' => $mergeIds,
'moved' => $moved,
'moved_total' => array_sum($moved),
];
}
/** تفريغ الرقم القومي من نسخة واحدة — لما يتضح إنهم أشخاص مختلفين. */
public static function clearNationalId(int $playerId, ?int $employeeId = null): array
{
$db = App::getInstance()->db();
$player = $db->selectOne("SELECT id, full_name_ar FROM sa_players WHERE id = ? AND is_archived = 0", [$playerId]);
if (!$player) {
return ['success' => false, 'error' => 'اللاعب غير موجود'];
}
$db->update('sa_players', [
'national_id' => null,
'updated_at' => date('Y-m-d H:i:s'),
'updated_by' => $employeeId,
], '`id` = ?', [$playerId]);
Logger::info("PlayerDuplicateService: cleared national_id for player #{$playerId}");
return ['success' => true, 'name' => $player['full_name_ar']];
}
/** هل الرقم القومي ده مستخدم مع لاعب تاني؟ بيستخدم في التحقق قبل الحفظ. */
public static function findByNationalId(string $nationalId, ?int $excludeId = null): ?array
{
$nationalId = trim($nationalId);
if ($nationalId === '') {
return null;
}
$db = App::getInstance()->db();
$sql = "SELECT id, full_name_ar, player_type, registration_serial
FROM sa_players WHERE national_id = ? AND is_archived = 0";
$params = [$nationalId];
if ($excludeId !== null && $excludeId > 0) {
$sql .= ' AND id <> ?';
$params[] = $excludeId;
}
return $db->selectOne($sql . ' LIMIT 1', $params);
}
private static function normalizeName(string $name): string
{
$name = preg_replace('/\s+/u', ' ', trim($name)) ?? '';
return str_replace(['أ', 'إ', 'آ', 'ى', 'ة'], ['ا', 'ا', 'ا', 'ي', 'ه'], $name);
}
}
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>تكرار الرقم القومي<?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/sa/players" class="btn btn-outline">رجوع للاعبين</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<?php
$typeLabels = ['member' => 'عضو', 'non_member' => 'غير عضو'];
$tableLabels = [
'sa_attendance' => 'حضور',
'sa_booking_participants'=> 'مشاركات حجوزات',
'sa_gate_access_log' => 'دخول البوابة',
'sa_group_players' => 'اشتراك مجموعات',
'sa_player_cards' => 'بطاقات',
'sa_player_documents' => 'مستندات',
'sa_registrations' => 'تسجيلات',
'sa_subscriptions' => 'اشتراكات',
'sa_waitlist' => 'قوائم انتظار',
'sa_game_tickets' => 'تذاكر ألعاب',
'sa_locker_rentals' => 'خزائن',
'sa_makeup_sessions' => 'حصص تعويضية',
'sa_pool_tickets' => 'تذاكر حمام سباحة',
'sa_training_attendance' => 'حضور تدريب',
];
?>
<div class="card" style="margin-bottom:14px;border-right:3px solid <?= empty($groups) ? '#059669' : '#D97706' ?>;">
<div style="padding:15px 20px;">
<h3 style="margin:0;font-size:16px;">لاعبون بنفس الرقم القومي</h3>
<p style="margin:8px 0 0;color:#6B7280;font-size:12.5px;line-height:1.9;">
شاشة تسجيل اللاعبين كانت بتقبل نفس الرقم القومي أكتر من مرة، فنفس الشخص ممكن يكون
متسجّل مرتين — مرة كعضو ومرة كغير عضو مثلاً — وكل نسخة ليها اشتراكاتها وحضورها
وبطاقتها لوحدها. ده اتقفل دلوقتي، والشاشة دي بتنضّف اللي اتسجّل قبل كده.
</p>
<p style="margin:6px 0 0;color:#92400E;font-size:12.5px;line-height:1.9;background:#FFFBEB;padding:9px 12px;border-radius:7px;">
<strong>مهم:</strong> مش كل تكرار هو نفس الشخص. أحياناً حد يكون كتب رقم قومي غلط
لشخص تاني. بُصّ على الأسماء الأول: لو نفس الشخص → <strong>ادمج</strong>،
ولو أشخاص مختلفين → <strong>فرّغ الرقم الغلط</strong> وبعدين صحّحه من ملف اللاعب.
</p>
</div>
</div>
<?php if (empty($groups)): ?>
<div class="card" style="padding:40px;text-align:center;color:#059669;">
<div style="font-size:15px;font-weight:600;">مفيش أي تكرار — كل اللاعبين بأرقام قومية مختلفة</div>
</div>
<?php else: ?>
<?php foreach ($groups as $g): ?>
<div class="card" style="margin-bottom:16px;">
<div style="padding:13px 20px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;">
<div>
<span style="font-size:12px;color:#6B7280;">الرقم القومي:</span>
<code style="direction:ltr;display:inline-block;font-size:13px;font-weight:700;"><?= e($g['national_id']) ?></code>
<span style="margin-inline-start:10px;background:#FEF3C7;color:#92400E;padding:2px 10px;border-radius:20px;font-size:11.5px;font-weight:700;">
<?= (int) $g['count'] ?> نسخ
</span>
</div>
<span style="font-size:12px;<?= $g['same_name'] ? 'color:#059669;' : 'color:#DC2626;font-weight:600;' ?>">
<?= $g['same_name'] ? 'نفس الاسم — على الأرجح نفس الشخص' : 'أسماء مختلفة — راجع كويس قبل الدمج' ?>
</span>
</div>
<form method="POST" action="/sa/players/duplicates/merge">
<?= csrf_field() ?>
<div class="table-responsive">
<table class="data-table" style="width:100%;font-size:12.5px;">
<thead>
<tr>
<th style="width:90px;">احتفظ به</th>
<th style="width:70px;">ادمجه</th>
<th>الاسم</th>
<th>النوع</th>
<th>مسلسل</th>
<th>تاريخ التسجيل</th>
<th>السجلات المرتبطة</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($g['players'] as $i => $p): ?>
<tr>
<td style="text-align:center;">
<input type="radio" name="keep_id" value="<?= (int) $p['id'] ?>"
<?= $i === 0 ? 'checked' : '' ?> required>
</td>
<td style="text-align:center;">
<input type="checkbox" name="merge_ids[]" value="<?= (int) $p['id'] ?>"
class="dup-merge" data-id="<?= (int) $p['id'] ?>">
</td>
<td>
<a href="/sa/players/<?= (int) $p['id'] ?>" style="font-weight:600;"><?= e($p['full_name_ar'] ?? '—') ?></a>
<span style="color:#9CA3AF;font-size:11px;">#<?= (int) $p['id'] ?></span>
</td>
<td><?= e($typeLabels[$p['player_type'] ?? ''] ?? ($p['player_type'] ?? '—')) ?></td>
<td style="direction:ltr;text-align:right;"><?= e($p['registration_serial'] ?? '—') ?></td>
<td style="direction:ltr;text-align:right;font-size:11.5px;"><?= e(substr((string) ($p['created_at'] ?? ''), 0, 10) ?: '—') ?></td>
<td>
<?php if (($p['links']['total'] ?? 0) === 0): ?>
<span style="color:#9CA3AF;">لا شيء</span>
<?php else: ?>
<strong style="color:#0D7377;"><?= (int) $p['links']['total'] ?></strong>
<span style="color:#6B7280;font-size:11px;">
(<?php
$parts = [];
foreach ($p['links']['by_table'] as $t => $n) {
$parts[] = ($tableLabels[$t] ?? $t) . ' ' . $n;
}
echo e(implode('، ', $parts));
?>)
</span>
<?php endif; ?>
</td>
<td style="white-space:nowrap;">
<?php if (can('sa.player.manage')): ?>
<button type="submit" formaction="/sa/players/duplicates/<?= (int) $p['id'] ?>/clear-nid"
class="btn btn-sm btn-outline" style="font-size:11px;"
onclick="return confirm('تفريغ الرقم القومي لـ <?= e($p['full_name_ar'] ?? '') ?>؟ هتحتاج تدخل الرقم الصحيح بعدين من ملف اللاعب.');">
شخص مختلف
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if (can('sa.player.manage')): ?>
<div style="padding:13px 20px;border-top:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;">
<span style="font-size:12px;color:#6B7280;">
اختر النسخة اللي هتفضل (يُفضَّل اللي عليها سجلات أكتر)، وعلّم على اللي هتتدمج فيها.
</span>
<button type="submit" class="btn btn-primary" style="font-size:12.5px;"
onclick="return confirm('تأكيد الدمج؟ كل السجلات المرتبطة هتتحوّل للنسخة المختارة، والباقي هيتأرشف.');">
دمج المحدد
</button>
</div>
<?php endif; ?>
</form>
</div>
<?php endforeach; ?>
<script>
// ما ينفعش تدمج النسخة اللي إنت مختارها للاحتفاظ في نفسها
document.querySelectorAll('form').forEach(function (form) {
function sync() {
var keep = form.querySelector('input[name="keep_id"]:checked');
var keepVal = keep ? keep.value : null;
form.querySelectorAll('.dup-merge').forEach(function (cb) {
var same = cb.getAttribute('data-id') === keepVal;
cb.disabled = same;
if (same) cb.checked = false;
cb.closest('tr').style.opacity = same ? '0.55' : '';
});
}
form.querySelectorAll('input[name="keep_id"]').forEach(function (r) {
r.addEventListener('change', sync);
});
sync();
});
</script>
<?php endif; ?>
<?php $__template->endSection(); ?>
...@@ -25,6 +25,7 @@ MenuRegistry::register('sports_activity', [ ...@@ -25,6 +25,7 @@ MenuRegistry::register('sports_activity', [
['label_ar' => 'المرافق', 'label_en' => 'Facilities', 'route' => '/sa/facilities', 'permission' => 'sa.facility.view', 'order' => 7], ['label_ar' => 'المرافق', 'label_en' => 'Facilities', 'route' => '/sa/facilities', 'permission' => 'sa.facility.view', 'order' => 7],
['label_ar' => 'المدربين', 'label_en' => 'Coaches', 'route' => '/sa/coaches', 'permission' => 'sa.coach.view', 'order' => 8], ['label_ar' => 'المدربين', 'label_en' => 'Coaches', 'route' => '/sa/coaches', 'permission' => 'sa.coach.view', 'order' => 8],
['label_ar' => 'اللاعبين', 'label_en' => 'Players', 'route' => '/sa/players', 'permission' => 'sa.player.view', 'order' => 9], ['label_ar' => 'اللاعبين', 'label_en' => 'Players', 'route' => '/sa/players', 'permission' => 'sa.player.view', 'order' => 9],
['label_ar' => 'تكرار الرقم القومي', 'label_en' => 'Duplicate IDs', 'route' => '/sa/players/duplicates', 'permission' => 'sa.player.view', 'order' => 10],
['label_ar' => 'تعيين اللاعبين', 'label_en' => 'Assignments', 'route' => '/sa/assignments', 'permission' => 'sa.player.assign', 'order' => 9.5], ['label_ar' => 'تعيين اللاعبين', 'label_en' => 'Assignments', 'route' => '/sa/assignments', 'permission' => 'sa.player.assign', 'order' => 9.5],
['label_ar' => 'الكروت', 'label_en' => 'Cards', 'route' => '/sa/cards', 'permission' => 'sa.card.view', 'order' => 10], ['label_ar' => 'الكروت', 'label_en' => 'Cards', 'route' => '/sa/cards', 'permission' => 'sa.card.view', 'order' => 10],
['label_ar' => 'الأكاديميات', 'label_en' => 'Academies', 'route' => '/sa/academies', 'permission' => 'sa.academy.view', 'order' => 11], ['label_ar' => 'الأكاديميات', 'label_en' => 'Academies', 'route' => '/sa/academies', 'permission' => 'sa.academy.view', 'order' => 11],
......
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