Commit 6a8cdf08 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Test This Wizard

parent 62b40017
<?php
declare(strict_types=1);
namespace App\Modules\Members\Controllers;
use App\Core\App;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Modules\Members\Services\RetroactiveMembershipService;
class RetroactiveWizardController extends Controller
{
private static function isSuperAdmin(): bool
{
$employee = App::getInstance()->currentEmployee();
if (!$employee) return false;
$db = App::getInstance()->db();
$row = $db->selectOne(
"SELECT 1 FROM employee_roles er JOIN roles r ON r.id = er.role_id WHERE er.employee_id = ? AND r.role_code = 'super_admin' AND er.is_active = 1 LIMIT 1",
[(int) $employee->id]
);
return $row !== null;
}
public function index(Request $request): Response
{
if (!self::isSuperAdmin()) {
throw new \RuntimeException('هذه الأداة متاحة فقط لمدير النظام', 403);
}
$db = App::getInstance()->db();
$branches = $db->select("SELECT id, name_ar FROM branches WHERE is_active = 1 ORDER BY name_ar");
$qualifications = $db->select("SELECT id, name_ar FROM qualifications WHERE is_active = 1 ORDER BY sort_order");
return $this->view('Members.Views.retroactive-wizard', [
'branches' => $branches,
'qualifications' => $qualifications,
]);
}
public function store(Request $request): Response
{
if (!self::isSuperAdmin()) {
throw new \RuntimeException('هذه الأداة متاحة فقط لمدير النظام', 403);
}
$data = $this->collectWizardData($request);
$result = RetroactiveMembershipService::createRetroactiveMember($data);
if (!$result['success']) {
return $this->redirect('/members/retroactive-wizard')
->withError('فشل إنشاء العضوية: ' . ($result['error'] ?? 'خطأ غير معروف'));
}
$memberId = $result['data']['member_id'];
return $this->redirect('/members/' . $memberId)
->withSuccess('تم إنشاء العضوية بأثر رجعي بنجاح — رقم العضوية: ' . $result['data']['membership_number']);
}
public function preview(Request $request): Response
{
if (!self::isSuperAdmin()) {
return $this->json(['error' => 'غير مصرح'], 403);
}
$data = $this->collectWizardData($request);
$preview = [
'member_name' => $data['full_name_ar'] ?? '',
'join_date' => $data['join_date'] ?? '',
'membership_type' => $data['membership_type'] ?? 'working',
'membership_value'=> $data['membership_value'] ?? '0.00',
'payment_method' => $data['membership_payment_method'] ?? 'cash',
'form_fee' => $data['form_fee_amount'] ?? '0.00',
'spouses_count' => count($data['spouses'] ?? []),
'children_count' => count($data['children'] ?? []),
'subscriptions' => $data['subscriptions'] ?? [],
'violations' => $data['violations'] ?? [],
'has_installment' => ($data['membership_payment_method'] ?? 'cash') === 'installment',
'installment_plan'=> $data['installment_plan'] ?? null,
];
return $this->json(['success' => true, 'preview' => $preview]);
}
private function collectWizardData(Request $request): array
{
$post = $_POST;
$data = [
// Step 1: Basic member info
'full_name_ar' => trim($post['full_name_ar'] ?? ''),
'full_name_en' => trim($post['full_name_en'] ?? '') ?: null,
'national_id' => trim($post['national_id'] ?? '') ?: null,
'passport_number' => trim($post['passport_number'] ?? '') ?: null,
'date_of_birth' => $post['date_of_birth'] ?? null,
'gender' => $post['gender'] ?? 'male',
'phone_mobile' => trim($post['phone_mobile'] ?? '') ?: null,
'phone_home' => trim($post['phone_home'] ?? '') ?: null,
'email' => trim($post['email'] ?? '') ?: null,
'branch_id' => (int) ($post['branch_id'] ?? 1),
'membership_type' => $post['membership_type'] ?? 'working',
'member_category' => $post['member_category'] ?? 'working_member',
'nationality' => $post['nationality'] ?? 'egyptian',
'qualification_id' => !empty($post['qualification_id']) ? (int) $post['qualification_id'] : null,
'occupation' => trim($post['occupation'] ?? '') ?: null,
'residence_address' => trim($post['residence_address'] ?? '') ?: null,
'area' => trim($post['area'] ?? '') ?: null,
'governorate' => trim($post['governorate'] ?? '') ?: null,
// Step 2: Dates & activation
'join_date' => $post['join_date'] ?? date('Y-m-d'),
'form_date' => $post['form_date'] ?? ($post['join_date'] ?? date('Y-m-d')),
// Step 3: Financial - form fee
'form_fee_amount' => $post['form_fee_amount'] ?? '505.00',
'form_fee_method' => $post['form_fee_method'] ?? 'cash',
'form_fee_check_number' => $post['form_fee_check_number'] ?? null,
'form_fee_check_bank' => $post['form_fee_check_bank'] ?? null,
'form_fee_check_date' => $post['form_fee_check_date'] ?? null,
// Step 3: Financial - membership value
'membership_value' => $post['membership_value'] ?? '150000.00',
'membership_payment_method' => $post['membership_payment_method'] ?? 'cash',
'membership_payment_date' => $post['membership_payment_date'] ?? ($post['join_date'] ?? date('Y-m-d')),
'membership_check_number' => $post['membership_check_number'] ?? null,
'membership_check_bank' => $post['membership_check_bank'] ?? null,
'membership_check_date' => $post['membership_check_date'] ?? null,
'membership_visa_reference' => $post['membership_visa_reference'] ?? null,
'membership_transfer_reference' => $post['membership_transfer_reference'] ?? null,
'membership_transfer_bank' => $post['membership_transfer_bank'] ?? null,
'discount_amount' => $post['discount_amount'] ?? '0.00',
'payment_method' => $post['membership_payment_method'] ?? 'cash',
];
// Step 4: Installment plan
if (($post['membership_payment_method'] ?? 'cash') === 'installment') {
$paidInstallments = [];
if (!empty($post['inst_paid_numbers'])) {
$numbers = explode(',', $post['inst_paid_numbers']);
foreach ($numbers as $num) {
$num = (int) trim($num);
if ($num > 0) {
$paidInstallments[] = [
'number' => $num,
'paid_date' => $post["inst_paid_date_{$num}"] ?? null,
'payment_method' => $post["inst_paid_method_{$num}"] ?? 'cash',
'check_number' => $post["inst_paid_check_{$num}"] ?? null,
'check_bank' => $post["inst_paid_bank_{$num}"] ?? null,
'check_date' => $post["inst_paid_checkdate_{$num}"] ?? null,
];
}
}
}
$cheques = [];
$chequeCount = (int) ($post['cheque_count'] ?? 0);
for ($i = 1; $i <= $chequeCount; $i++) {
if (!empty($post["cheque_number_{$i}"])) {
$cheques[] = [
'cheque_number' => $post["cheque_number_{$i}"],
'bank_name' => $post["cheque_bank_{$i}"] ?? '',
'cheque_date' => $post["cheque_date_{$i}"] ?? '',
'cheque_amount' => $post["cheque_amount_{$i}"] ?? '0.00',
];
}
}
$data['installment_plan'] = [
'total_amount' => $post['membership_value'] ?? '150000.00',
'down_payment' => $post['inst_down_payment'] ?? '37500.00',
'months' => (int) ($post['inst_months'] ?? 12),
'start_date' => $post['inst_start_date'] ?? ($post['join_date'] ?? date('Y-m-d')),
'interest_rate' => $post['inst_interest_rate'] ?? '22.00',
'down_payment_method' => $post['inst_down_method'] ?? 'cash',
'down_check_number' => $post['inst_down_check_number'] ?? null,
'down_check_bank' => $post['inst_down_check_bank'] ?? null,
'down_check_date' => $post['inst_down_check_date'] ?? null,
'paid_installments' => $paidInstallments,
'cheques' => $cheques,
];
}
// Step 5: Dependents
$data['spouses'] = [];
$spouseCount = (int) ($post['spouse_count'] ?? 0);
for ($i = 1; $i <= $spouseCount; $i++) {
if (!empty($post["spouse_name_{$i}"])) {
$data['spouses'][] = [
'full_name_ar' => $post["spouse_name_{$i}"],
'national_id' => $post["spouse_nid_{$i}"] ?? null,
'date_of_birth' => $post["spouse_dob_{$i}"] ?? null,
'phone_mobile' => $post["spouse_phone_{$i}"] ?? null,
'join_date' => $post["spouse_join_date_{$i}"] ?? ($post['join_date'] ?? date('Y-m-d')),
'addition_fee' => $post["spouse_fee_{$i}"] ?? '0.00',
'has_separate_fee'=> !empty($post["spouse_separate_fee_{$i}"]),
'payment_method' => $post["spouse_pay_method_{$i}"] ?? 'cash',
'payment_date' => $post["spouse_pay_date_{$i}"] ?? null,
];
}
}
$data['children'] = [];
$childCount = (int) ($post['child_count'] ?? 0);
for ($i = 1; $i <= $childCount; $i++) {
if (!empty($post["child_name_{$i}"])) {
$data['children'][] = [
'full_name_ar' => $post["child_name_{$i}"],
'national_id' => $post["child_nid_{$i}"] ?? null,
'date_of_birth' => $post["child_dob_{$i}"] ?? null,
'gender' => $post["child_gender_{$i}"] ?? 'male',
'join_date' => $post["child_join_date_{$i}"] ?? ($post['join_date'] ?? date('Y-m-d')),
'addition_fee' => $post["child_fee_{$i}"] ?? '0.00',
'has_separate_fee'=> !empty($post["child_separate_fee_{$i}"]),
'payment_method' => $post["child_pay_method_{$i}"] ?? 'cash',
'payment_date' => $post["child_pay_date_{$i}"] ?? null,
];
}
}
// Step 6: Subscriptions
$data['subscriptions'] = [];
$subCount = (int) ($post['subscription_count'] ?? 0);
for ($i = 1; $i <= $subCount; $i++) {
if (!empty($post["sub_year_{$i}"])) {
$data['subscriptions'][] = [
'financial_year' => $post["sub_year_{$i}"],
'person_type' => 'member',
'person_name' => $data['full_name_ar'],
'base_amount' => $post["sub_base_{$i}"] ?? '492.00',
'development_fee' => $post["sub_dev_fee_{$i}"] ?? '35.00',
'total_amount' => $post["sub_total_{$i}"] ?? '527.00',
'paid_amount' => $post["sub_paid_{$i}"] ?? '0.00',
'fine_amount' => $post["sub_fine_{$i}"] ?? '0.00',
'status' => $post["sub_status_{$i}"] ?? 'pending',
'paid_date' => $post["sub_paid_date_{$i}"] ?? null,
'payment_method' => $post["sub_pay_method_{$i}"] ?? 'cash',
'discount_amount' => $post["sub_discount_{$i}"] ?? '0.00',
];
}
}
// Step 7: Violations & Fines
$data['violations'] = [];
$violationCount = (int) ($post['violation_count'] ?? 0);
for ($i = 1; $i <= $violationCount; $i++) {
if (!empty($post["violation_desc_{$i}"])) {
$data['violations'][] = [
'violation_date' => $post["violation_date_{$i}"] ?? date('Y-m-d'),
'description' => $post["violation_desc_{$i}"],
'evidence_notes' => $post["violation_evidence_{$i}"] ?? null,
'fine_amount' => $post["violation_fine_amount_{$i}"] ?? '0.00',
'penalty_type' => $post["violation_penalty_type_{$i}"] ?? 'fine',
'fine_status' => $post["violation_fine_status_{$i}"] ?? 'imposed',
'fine_payment_method' => $post["violation_fine_method_{$i}"] ?? 'cash',
'fine_paid_date' => $post["violation_fine_paid_date_{$i}"] ?? null,
'suspension_from' => $post["violation_suspension_from_{$i}"] ?? null,
'suspension_to' => $post["violation_suspension_to_{$i}"] ?? null,
];
}
}
return $data;
}
}
......@@ -6,6 +6,10 @@ return [
['GET', '/members/create', 'Members\Controllers\MemberController@create', ['auth'], 'member.create'],
['POST', '/members', 'Members\Controllers\MemberController@store', ['auth', 'csrf'], 'member.create'],
['GET', '/members/search', 'Members\Controllers\MemberController@search', ['auth'], 'member.view'],
// Retroactive Wizard (super admin only)
['GET', '/members/retroactive-wizard', 'Members\Controllers\RetroactiveWizardController@index', ['auth'], 'member.create'],
['POST', '/members/retroactive-wizard', 'Members\Controllers\RetroactiveWizardController@store', ['auth', 'csrf'], 'member.create'],
['POST', '/members/retroactive-wizard/preview', 'Members\Controllers\RetroactiveWizardController@preview', ['auth', 'csrf'], 'member.create'],
['GET', '/members/{id}', 'Members\Controllers\MemberController@show', ['auth'], 'member.view'],
['GET', '/members/{id}/edit', 'Members\Controllers\MemberController@edit', ['auth'], 'member.edit'],
['POST', '/members/{id}', 'Members\Controllers\MemberController@update', ['auth', 'csrf'], 'member.edit'],
......
<?php
declare(strict_types=1);
namespace App\Modules\Members\Services;
use App\Core\App;
use App\Core\EventBus;
use App\Core\Logger;
final class RetroactiveMembershipService
{
public static function createRetroactiveMember(array $data): array
{
$db = App::getInstance()->db();
$employee = App::getInstance()->currentEmployee();
$empId = $employee ? (int) $employee->id : null;
$ts = date('Y-m-d H:i:s');
$db->beginTransaction();
try {
// Step 1: Create the member record with retroactive dates
$formNumber = (string) (MemberNumberGenerator::next() ?? 1);
$joinDate = $data['join_date'] ?? date('Y-m-d');
$formDate = $data['form_date'] ?? $joinDate;
$memberData = [
'full_name_ar' => $data['full_name_ar'],
'full_name_en' => $data['full_name_en'] ?? null,
'national_id' => $data['national_id'] ?? null,
'passport_number' => $data['passport_number'] ?? null,
'id_type' => $data['national_id'] ? 'national_id' : 'passport',
'date_of_birth' => $data['date_of_birth'] ?? null,
'gender' => $data['gender'] ?? 'male',
'phone_mobile' => $data['phone_mobile'] ?? null,
'phone_home' => $data['phone_home'] ?? null,
'email' => $data['email'] ?? null,
'branch_id' => (int) ($data['branch_id'] ?? 1),
'membership_type' => $data['membership_type'] ?? 'working',
'member_category' => $data['member_category'] ?? 'working_member',
'nationality' => $data['nationality'] ?? 'egyptian',
'form_number' => $formNumber,
'form_date' => $formDate,
'status' => 'active',
'membership_value' => $data['membership_value'] ?? '0.00',
'qualification_id' => isset($data['qualification_id']) ? (int) $data['qualification_id'] : null,
'occupation' => $data['occupation'] ?? null,
'residence_address' => $data['residence_address'] ?? null,
'area' => $data['area'] ?? null,
'governorate' => $data['governorate'] ?? null,
'payment_method' => $data['payment_method'] ?? 'cash',
'discount_amount' => $data['discount_amount'] ?? '0.00',
];
$memberId = $db->insert('members', array_merge($memberData, [
'created_at' => $formDate . ' 09:00:00',
'updated_at' => $ts,
'created_by' => $empId,
]));
// Assign membership number
$membershipNumber = MemberNumberGenerator::assign($memberId);
// Update activated_at to the join date
$db->update('members', [
'activated_at' => $joinDate . ' 09:00:00',
'activated_by_payment_id' => 0, // placeholder, updated after payment
], '`id` = ?', [$memberId]);
$result = [
'member_id' => $memberId,
'membership_number' => $membershipNumber,
'payments' => [],
'subscriptions' => [],
'installments' => null,
'fines' => [],
'dependents' => [],
];
// Step 2: Create form fee payment (retroactive)
if (!empty($data['form_fee_amount']) && bccomp($data['form_fee_amount'], '0', 2) > 0) {
$paymentId = self::createRetroactivePayment($memberId, [
'payment_type' => 'form_fee',
'amount' => $data['form_fee_amount'],
'payment_method' => $data['form_fee_method'] ?? 'cash',
'payment_date' => $formDate,
'check_number' => $data['form_fee_check_number'] ?? null,
'check_bank' => $data['form_fee_check_bank'] ?? null,
'check_date' => $data['form_fee_check_date'] ?? null,
'description' => 'رسوم استمارة — إدخال بأثر رجعي',
]);
$result['payments'][] = ['type' => 'form_fee', 'id' => $paymentId, 'date' => $formDate];
}
// Step 3: Create membership fee payment (retroactive)
$membershipPaymentId = null;
if (!empty($data['membership_value']) && bccomp($data['membership_value'], '0', 2) > 0) {
$paymentDate = $data['membership_payment_date'] ?? $joinDate;
$paymentMethod = $data['membership_payment_method'] ?? 'cash';
if ($paymentMethod === 'installment' && !empty($data['installment_plan'])) {
// Down payment
$downPayment = $data['installment_plan']['down_payment'] ?? '0.00';
$downPaymentDate = $data['installment_plan']['start_date'] ?? $joinDate;
$membershipPaymentId = self::createRetroactivePayment($memberId, [
'payment_type' => 'down_payment',
'amount' => $downPayment,
'payment_method' => $data['installment_plan']['down_payment_method'] ?? 'cash',
'payment_date' => $downPaymentDate,
'check_number' => $data['installment_plan']['down_check_number'] ?? null,
'check_bank' => $data['installment_plan']['down_check_bank'] ?? null,
'check_date' => $data['installment_plan']['down_check_date'] ?? null,
'description' => 'مقدم تقسيط — إدخال بأثر رجعي',
]);
$result['payments'][] = ['type' => 'down_payment', 'id' => $membershipPaymentId, 'date' => $downPaymentDate];
// Create installment plan
$installmentResult = self::createRetroactiveInstallmentPlan($memberId, $data['installment_plan'], $membershipPaymentId);
$result['installments'] = $installmentResult;
} else {
// Full payment (cash/check/visa/transfer)
$membershipPaymentId = self::createRetroactivePayment($memberId, [
'payment_type' => 'membership_fee',
'amount' => $data['membership_value'],
'payment_method' => $paymentMethod,
'payment_date' => $paymentDate,
'check_number' => $data['membership_check_number'] ?? null,
'check_bank' => $data['membership_check_bank'] ?? null,
'check_date' => $data['membership_check_date'] ?? null,
'visa_reference' => $data['membership_visa_reference'] ?? null,
'transfer_reference' => $data['membership_transfer_reference'] ?? null,
'transfer_bank' => $data['membership_transfer_bank'] ?? null,
'description' => 'قيمة العضوية — إدخال بأثر رجعي',
]);
$result['payments'][] = ['type' => 'membership_fee', 'id' => $membershipPaymentId, 'date' => $paymentDate];
}
// Update activated_by_payment_id
$db->update('members', [
'activated_by_payment_id' => $membershipPaymentId,
], '`id` = ?', [$memberId]);
}
// Step 4: Create dependents
if (!empty($data['spouses'])) {
foreach ($data['spouses'] as $spouse) {
$spouseId = $db->insert('spouses', [
'member_id' => $memberId,
'full_name_ar' => $spouse['full_name_ar'],
'national_id' => $spouse['national_id'] ?? null,
'date_of_birth' => $spouse['date_of_birth'] ?? null,
'phone_mobile' => $spouse['phone_mobile'] ?? null,
'status' => 'active',
'join_date' => $spouse['join_date'] ?? $joinDate,
'addition_fee' => $spouse['addition_fee'] ?? '0.00',
'activated_by_payment_id' => $membershipPaymentId,
'is_archived' => 0,
'created_at' => ($spouse['join_date'] ?? $joinDate) . ' 09:00:00',
'updated_at' => $ts,
]);
if (!empty($spouse['addition_fee']) && bccomp($spouse['addition_fee'], '0', 2) > 0 && !empty($spouse['has_separate_fee'])) {
$spPayId = self::createRetroactivePayment($memberId, [
'payment_type' => 'addition_fee',
'amount' => $spouse['addition_fee'],
'payment_method' => $spouse['payment_method'] ?? 'cash',
'payment_date' => $spouse['payment_date'] ?? $joinDate,
'related_entity_type' => 'spouses',
'related_entity_id' => $spouseId,
'description' => 'رسوم إضافة زوجة — إدخال بأثر رجعي',
]);
$db->update('spouses', ['activated_by_payment_id' => $spPayId], '`id` = ?', [$spouseId]);
}
$result['dependents'][] = ['type' => 'spouse', 'id' => $spouseId, 'name' => $spouse['full_name_ar']];
}
}
if (!empty($data['children'])) {
foreach ($data['children'] as $child) {
$childId = $db->insert('children', [
'member_id' => $memberId,
'full_name_ar' => $child['full_name_ar'],
'national_id' => $child['national_id'] ?? null,
'date_of_birth' => $child['date_of_birth'] ?? null,
'gender' => $child['gender'] ?? 'male',
'status' => 'active',
'join_date' => $child['join_date'] ?? $joinDate,
'addition_fee' => $child['addition_fee'] ?? '0.00',
'activated_by_payment_id' => $membershipPaymentId,
'is_archived' => 0,
'created_at' => ($child['join_date'] ?? $joinDate) . ' 09:00:00',
'updated_at' => $ts,
]);
if (!empty($child['addition_fee']) && bccomp($child['addition_fee'], '0', 2) > 0 && !empty($child['has_separate_fee'])) {
$chPayId = self::createRetroactivePayment($memberId, [
'payment_type' => 'addition_fee',
'amount' => $child['addition_fee'],
'payment_method' => $child['payment_method'] ?? 'cash',
'payment_date' => $child['payment_date'] ?? $joinDate,
'related_entity_type' => 'children',
'related_entity_id' => $childId,
'description' => 'رسوم إضافة ابن — إدخال بأثر رجعي',
]);
$db->update('children', ['activated_by_payment_id' => $chPayId], '`id` = ?', [$childId]);
}
$result['dependents'][] = ['type' => 'child', 'id' => $childId, 'name' => $child['full_name_ar']];
}
}
// Step 5: Create retroactive subscriptions
if (!empty($data['subscriptions'])) {
foreach ($data['subscriptions'] as $sub) {
$subId = self::createRetroactiveSubscription($memberId, $sub);
$result['subscriptions'][] = $subId;
}
}
// Step 6: Create retroactive fines/violations
if (!empty($data['violations'])) {
foreach ($data['violations'] as $violation) {
$fineResult = self::createRetroactiveViolation($memberId, $violation);
$result['fines'][] = $fineResult;
}
}
// Step 7: Handle renewals — mark subscription years as paid or overdue
if (!empty($data['renewal_history'])) {
foreach ($data['renewal_history'] as $renewal) {
self::processRetroactiveRenewal($memberId, $renewal);
}
}
$db->commit();
EventBus::dispatch('member.created', ['member_id' => $memberId, 'retroactive' => true]);
Logger::info("Retroactive member created", ['member_id' => $memberId, 'join_date' => $joinDate]);
return ['success' => true, 'data' => $result];
} catch (\Throwable $e) {
$db->rollBack();
Logger::error("Retroactive member creation failed: " . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
return ['success' => false, 'error' => $e->getMessage()];
}
}
private static function createRetroactivePayment(int $memberId, array $data): int
{
$db = App::getInstance()->db();
$employee = App::getInstance()->currentEmployee();
$empId = $employee ? (int) $employee->id : null;
$paymentDate = $data['payment_date'] ?? date('Y-m-d');
$receiptNumber = self::generateRetroReceiptNumber($paymentDate);
$paymentId = $db->insert('payments', [
'member_id' => $memberId,
'payment_type' => $data['payment_type'],
'amount' => $data['amount'],
'currency' => 'EGP',
'payment_method' => $data['payment_method'] ?? 'cash',
'check_number' => $data['check_number'] ?? null,
'check_bank' => $data['check_bank'] ?? null,
'check_date' => $data['check_date'] ?? null,
'check_status' => ($data['payment_method'] ?? 'cash') === 'check' ? 'cleared' : null,
'visa_reference' => $data['visa_reference'] ?? null,
'transfer_reference' => $data['transfer_reference'] ?? null,
'transfer_bank' => $data['transfer_bank'] ?? null,
'related_entity_type' => $data['related_entity_type'] ?? null,
'related_entity_id' => $data['related_entity_id'] ?? null,
'notes' => ($data['description'] ?? '') . ' [RETROACTIVE]',
'payment_date' => $paymentDate,
'received_by_employee_id' => $empId,
'is_voided' => 0,
'created_at' => $paymentDate . ' 09:00:00',
'updated_at' => date('Y-m-d H:i:s'),
'created_by' => $empId,
]);
$receiptId = $db->insert('receipts', [
'receipt_number' => $receiptNumber,
'member_id' => $memberId,
'payment_id' => $paymentId,
'receipt_type' => 'payment',
'amount' => $data['amount'],
'amount_in_words_ar' => '',
'description_ar' => $data['description'] ?? 'دفعة بأثر رجعي',
'issued_by_employee_id' => $empId,
'issued_at' => $paymentDate . ' 09:00:00',
'is_voided' => 0,
'print_count' => 0,
'created_at' => $paymentDate . ' 09:00:00',
]);
$db->update('payments', ['receipt_id' => $receiptId], '`id` = ?', [$paymentId]);
return $paymentId;
}
private static function createRetroactiveInstallmentPlan(int $memberId, array $plan, int $downPaymentId): array
{
$db = App::getInstance()->db();
$employee = App::getInstance()->currentEmployee();
$empId = $employee ? (int) $employee->id : null;
$totalAmount = $plan['total_amount'];
$downPayment = $plan['down_payment'];
$months = (int) ($plan['months'] ?? 12);
$startDate = $plan['start_date'] ?? date('Y-m-d');
$interestRate = $plan['interest_rate'] ?? '22.00';
$remaining = bcsub($totalAmount, $downPayment, 2);
$monthlyRate = bcdiv($interestRate, '1200', 10);
$monthlyPrincipal = bcdiv($remaining, (string) $months, 2);
$totalInterest = '0.00';
$runningBalance = $remaining;
$schedule = [];
for ($i = 1; $i <= $months; $i++) {
$interest = bcmul($runningBalance, $monthlyRate, 2);
$totalInterest = bcadd($totalInterest, $interest, 2);
$principal = ($i === $months) ? $runningBalance : $monthlyPrincipal;
$amount = bcadd($principal, $interest, 2);
$runningBalance = bcsub($runningBalance, $principal, 2);
if (bccomp($runningBalance, '0', 2) < 0) $runningBalance = '0.00';
$dueDate = date('Y-m-d', strtotime($startDate . " +{$i} months"));
$schedule[] = [
'number' => $i,
'due_date' => $dueDate,
'amount' => $amount,
'principal'=> $principal,
'interest' => $interest,
'remaining'=> $runningBalance,
];
}
$totalWithInterest = bcadd($remaining, $totalInterest, 2);
$avgMonthly = bcdiv($totalWithInterest, (string) $months, 2);
$planId = $db->insert('installment_plans', [
'member_id' => $memberId,
'related_entity_type' => 'members',
'related_entity_id' => $memberId,
'total_amount' => $totalAmount,
'down_payment' => $downPayment,
'remaining_balance' => $remaining,
'interest_rate' => $interestRate,
'total_interest' => $totalInterest,
'total_with_interest' => $totalWithInterest,
'number_of_months' => $months,
'monthly_payment' => $avgMonthly,
'start_date' => $startDate,
'down_payment_receipt' => $downPaymentId,
'status' => 'active',
'created_at' => $startDate . ' 09:00:00',
'updated_at' => date('Y-m-d H:i:s'),
'created_by' => $empId,
]);
// Create schedule items and process paid ones
$paidInstallments = $plan['paid_installments'] ?? [];
$paidCount = 0;
foreach ($schedule as $item) {
$isPaid = false;
$paidAt = null;
$paymentId = null;
foreach ($paidInstallments as $paid) {
if ((int) $paid['number'] === $item['number']) {
$isPaid = true;
$paidAt = $paid['paid_date'] ?? $item['due_date'];
break;
}
}
$status = 'pending';
if ($isPaid) {
$status = 'paid';
$paidCount++;
$paymentId = self::createRetroactivePayment($memberId, [
'payment_type' => 'installment',
'amount' => $item['amount'],
'payment_method' => $paid['payment_method'] ?? 'cash',
'payment_date' => $paidAt,
'check_number' => $paid['check_number'] ?? null,
'check_bank' => $paid['check_bank'] ?? null,
'check_date' => $paid['check_date'] ?? null,
'description' => "قسط رقم {$item['number']} — إدخال بأثر رجعي",
]);
} elseif (strtotime($item['due_date']) < time()) {
$status = 'overdue';
}
$db->insert('installment_schedule', [
'installment_plan_id' => $planId,
'installment_number' => $item['number'],
'due_date' => $item['due_date'],
'amount' => $item['amount'],
'principal' => $item['principal'],
'interest' => $item['interest'],
'remaining_after' => $item['remaining'],
'paid_amount' => $isPaid ? $item['amount'] : '0.00',
'payment_id' => $paymentId,
'status' => $status,
'paid_at' => $paidAt,
'created_at' => $startDate . ' 09:00:00',
'updated_at' => date('Y-m-d H:i:s'),
]);
}
// Create cheques if provided
if (!empty($plan['cheques'])) {
foreach ($plan['cheques'] as $cheque) {
$db->insert('installment_cheques', [
'installment_plan_id' => $planId,
'cheque_number' => $cheque['cheque_number'],
'bank_name' => $cheque['bank_name'],
'cheque_date' => $cheque['cheque_date'],
'cheque_amount' => $cheque['cheque_amount'],
'uploaded_by' => $empId,
'notes' => 'شيك بأثر رجعي',
'created_at' => date('Y-m-d H:i:s'),
]);
}
}
// If all paid, mark plan as completed
if ($paidCount >= $months) {
$db->update('installment_plans', ['status' => 'completed', 'updated_at' => date('Y-m-d H:i:s')], '`id` = ?', [$planId]);
}
return [
'plan_id' => $planId,
'months' => $months,
'paid_count' => $paidCount,
'status' => $paidCount >= $months ? 'completed' : 'active',
];
}
private static function createRetroactiveSubscription(int $memberId, array $sub): int
{
$db = App::getInstance()->db();
$employee = App::getInstance()->currentEmployee();
$empId = $employee ? (int) $employee->id : null;
$status = $sub['status'] ?? 'pending';
$paidAmount = $sub['paid_amount'] ?? '0.00';
$paymentId = null;
if ($status === 'paid' && bccomp($paidAmount, '0', 2) > 0) {
$paymentId = self::createRetroactivePayment($memberId, [
'payment_type' => 'annual_subscription',
'amount' => $paidAmount,
'payment_method' => $sub['payment_method'] ?? 'cash',
'payment_date' => $sub['paid_date'] ?? date('Y-m-d'),
'description' => "اشتراك سنوي {$sub['financial_year']} — إدخال بأثر رجعي",
]);
}
$subId = $db->insert('subscriptions', [
'member_id' => $memberId,
'financial_year' => $sub['financial_year'],
'person_type' => $sub['person_type'] ?? 'member',
'person_id' => $memberId,
'person_name' => $sub['person_name'] ?? '',
'base_amount' => $sub['base_amount'] ?? '0.00',
'development_fee' => $sub['development_fee'] ?? '35.00',
'discount_amount' => $sub['discount_amount'] ?? '0.00',
'total_amount' => $sub['total_amount'] ?? '0.00',
'paid_amount' => $paidAmount,
'fine_amount' => $sub['fine_amount'] ?? '0.00',
'status' => $status,
'payment_id' => $paymentId,
'paid_at' => ($status === 'paid') ? ($sub['paid_date'] ?? date('Y-m-d H:i:s')) : null,
'created_at' => ($sub['financial_year'] ?? date('Y')) . '-01-01 09:00:00',
'updated_at' => date('Y-m-d H:i:s'),
'created_by' => $empId,
]);
return $subId;
}
private static function createRetroactiveViolation(int $memberId, array $violation): array
{
$db = App::getInstance()->db();
$employee = App::getInstance()->currentEmployee();
$empId = $employee ? (int) $employee->id : null;
$violationId = $db->insert('violations', [
'member_id' => $memberId,
'violation_date' => $violation['violation_date'] ?? date('Y-m-d'),
'description' => $violation['description'] ?? 'مخالفة مسجلة بأثر رجعي',
'reported_by' => $empId,
'evidence_notes' => $violation['evidence_notes'] ?? null,
'status' => 'confirmed',
'created_at' => ($violation['violation_date'] ?? date('Y-m-d')) . ' 09:00:00',
'updated_at' => date('Y-m-d H:i:s'),
]);
$fineStatus = $violation['fine_status'] ?? 'imposed';
$finePaymentId = null;
if ($fineStatus === 'paid' && !empty($violation['fine_amount'])) {
$finePaymentId = self::createRetroactivePayment($memberId, [
'payment_type' => 'fine',
'amount' => $violation['fine_amount'],
'payment_method' => $violation['fine_payment_method'] ?? 'cash',
'payment_date' => $violation['fine_paid_date'] ?? date('Y-m-d'),
'description' => 'غرامة مخالفة — إدخال بأثر رجعي',
]);
}
$fineId = $db->insert('fines', [
'member_id' => $memberId,
'violation_id' => $violationId,
'fine_type' => 'violation_fine',
'amount' => $violation['fine_amount'] ?? '0.00',
'penalty_type' => $violation['penalty_type'] ?? 'fine',
'suspension_from'=> $violation['suspension_from'] ?? null,
'suspension_to' => $violation['suspension_to'] ?? null,
'status' => $fineStatus,
'paid_amount' => $fineStatus === 'paid' ? ($violation['fine_amount'] ?? '0.00') : '0.00',
'payment_id' => $finePaymentId,
'paid_at' => $fineStatus === 'paid' ? ($violation['fine_paid_date'] ?? date('Y-m-d H:i:s')) : null,
'created_at' => ($violation['violation_date'] ?? date('Y-m-d')) . ' 09:00:00',
'updated_at' => date('Y-m-d H:i:s'),
]);
return ['violation_id' => $violationId, 'fine_id' => $fineId, 'status' => $fineStatus];
}
private static function processRetroactiveRenewal(int $memberId, array $renewal): void
{
$db = App::getInstance()->db();
$financialYear = $renewal['financial_year'];
$status = $renewal['status'] ?? 'paid';
$existing = $db->selectOne(
"SELECT id FROM subscriptions WHERE member_id = ? AND financial_year = ? AND person_type = 'member'",
[$memberId, $financialYear]
);
if ($existing) {
$updateData = ['status' => $status, 'updated_at' => date('Y-m-d H:i:s')];
if ($status === 'paid') {
$updateData['paid_amount'] = $renewal['amount'] ?? '0.00';
$updateData['paid_at'] = $renewal['paid_date'] ?? date('Y-m-d H:i:s');
}
$db->update('subscriptions', $updateData, '`id` = ?', [(int) $existing['id']]);
}
}
private static function generateRetroReceiptNumber(string $date): string
{
$db = App::getInstance()->db();
$year = date('Y', strtotime($date));
$prefix = 'REC-' . $year . '-';
$last = $db->selectOne(
"SELECT receipt_number FROM receipts WHERE receipt_number LIKE ? ORDER BY id DESC LIMIT 1",
[$prefix . '%']
);
if ($last) {
$parts = explode('-', $last['receipt_number']);
$seq = (int) end($parts) + 1;
} else {
$seq = 1;
}
return $prefix . str_pad((string) $seq, 6, '0', STR_PAD_LEFT);
}
}
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>معالج إدخال عضوية بأثر رجعي<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<div id="retroWizard">
<!-- Progress Bar -->
<div class="card" style="margin-bottom:16px;padding:16px;">
<div style="display:flex;justify-content:space-between;align-items:center;position:relative;">
<div style="position:absolute;top:50%;left:40px;right:40px;height:3px;background:#E5E7EB;z-index:0;transform:translateY(-50%);"></div>
<div id="progressFill" style="position:absolute;top:50%;left:40px;height:3px;background:#2563EB;z-index:0;transform:translateY(-50%);width:0%;transition:width 0.3s;"></div>
<?php
$steps = ['البيانات الأساسية', 'التواريخ', 'المالية', 'الأقساط', 'الملحقون', 'الاشتراكات', 'المخالفات'];
foreach ($steps as $i => $label):
$stepNum = $i + 1;
?>
<div class="wizard-step-indicator" data-step="<?= $stepNum ?>" style="text-align:center;position:relative;z-index:1;cursor:pointer;">
<div id="stepCircle<?= $stepNum ?>" style="width:40px;height:40px;border-radius:50%;background:#E5E7EB;color:#9CA3AF;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;margin:0 auto;transition:all 0.3s;">
<?= $stepNum ?>
</div>
<div id="stepLabel<?= $stepNum ?>" style="font-size:10px;margin-top:6px;color:#6B7280;font-weight:400;white-space:nowrap;"><?= $label ?></div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- Warning Banner -->
<div class="card" style="margin-bottom:16px;padding:14px 20px;background:#FEF3C7;border:1px solid #F59E0B;">
<div style="display:flex;align-items:center;gap:10px;">
<i data-lucide="alert-triangle" style="width:22px;height:22px;color:#D97706;flex-shrink:0;"></i>
<div>
<div style="font-weight:700;color:#92400E;font-size:14px;">أداة إدخال بأثر رجعي — للمدير فقط</div>
<div style="font-size:12px;color:#92400E;margin-top:2px;">هذه الأداة تتجاوز جميع مسارات العمل المعتادة. تُستخدم لإدخال بيانات أعضاء قدامى بكل تفاصيلهم المالية والزمنية لاختبار جميع السيناريوهات.</div>
</div>
</div>
</div>
<form id="retroForm" action="/members/retroactive-wizard" method="POST">
<?= csrf_field() ?>
<!-- ═══════════════ STEP 1: Basic Info ═══════════════ -->
<div class="wizard-panel" id="panel-1">
<div class="card" style="margin-bottom:16px;">
<div style="padding:16px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:16px;font-weight:700;"><i data-lucide="user-plus" style="width:18px;height:18px;vertical-align:middle;margin-left:6px;"></i> البيانات الأساسية للعضو</h3>
</div>
<div style="padding:20px;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">الاسم بالعربية <span style="color:#EF4444;">*</span></label>
<input type="text" name="full_name_ar" required class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;" placeholder="الاسم رباعي بالعربية">
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">الاسم بالإنجليزية</label>
<input type="text" name="full_name_en" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;direction:ltr;" placeholder="Full Name in English">
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">الرقم القومي</label>
<input type="text" name="national_id" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;direction:ltr;" maxlength="14" placeholder="14 رقم">
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">رقم جواز السفر</label>
<input type="text" name="passport_number" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;direction:ltr;" placeholder="للأجانب">
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">تاريخ الميلاد</label>
<input type="date" name="date_of_birth" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;">
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">النوع</label>
<select name="gender" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;">
<option value="male">ذكر</option>
<option value="female">أنثى</option>
</select>
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">الهاتف المحمول</label>
<input type="text" name="phone_mobile" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;direction:ltr;" placeholder="01XXXXXXXXX">
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">الهاتف المنزلي</label>
<input type="text" name="phone_home" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;direction:ltr;">
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">البريد الإلكتروني</label>
<input type="email" name="email" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;direction:ltr;">
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">الفرع <span style="color:#EF4444;">*</span></label>
<select name="branch_id" required class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;">
<?php foreach ($branches as $b): ?>
<option value="<?= $b['id'] ?>"><?= e($b['name_ar']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">نوع العضوية</label>
<select name="membership_type" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;">
<option value="working">عاملة</option>
<option value="seasonal">موسمية</option>
<option value="sports">رياضية</option>
<option value="honorary">شرفية</option>
<option value="foreign">أجنبية</option>
</select>
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">الجنسية</label>
<select name="nationality" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;">
<option value="egyptian">مصري</option>
<option value="foreign">أجنبي</option>
</select>
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">المؤهل</label>
<select name="qualification_id" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;">
<option value="">— اختر —</option>
<?php foreach ($qualifications as $q): ?>
<option value="<?= $q['id'] ?>"><?= e($q['name_ar']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">المهنة</label>
<input type="text" name="occupation" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;">
</div>
</div>
<div style="margin-top:16px;display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;">
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">العنوان</label>
<input type="text" name="residence_address" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;">
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">المنطقة</label>
<input type="text" name="area" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;">
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">المحافظة</label>
<input type="text" name="governorate" class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;">
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════ STEP 2: Dates & Timeline ═══════════════ -->
<div class="wizard-panel" id="panel-2" style="display:none;">
<div class="card" style="margin-bottom:16px;">
<div style="padding:16px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:16px;font-weight:700;"><i data-lucide="calendar" style="width:18px;height:18px;vertical-align:middle;margin-left:6px;"></i> التواريخ والجدول الزمني</h3>
<p style="margin:6px 0 0;font-size:12px;color:#6B7280;">حدد تواريخ الانضمام والاستمارة لمحاكاة العضوية القديمة</p>
</div>
<div style="padding:20px;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">تاريخ تقديم الاستمارة <span style="color:#EF4444;">*</span></label>
<input type="date" name="form_date" required class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;" value="2020-01-15">
<div style="font-size:11px;color:#6B7280;margin-top:4px;">تاريخ تقديم طلب العضوية الأصلي</div>
</div>
<div>
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px;color:#374151;">تاريخ التفعيل/الانضمام <span style="color:#EF4444;">*</span></label>
<input type="date" name="join_date" required class="form-input" style="width:100%;padding:10px 14px;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;" value="2020-02-01">
<div style="font-size:11px;color:#6B7280;margin-top:4px;">التاريخ الذي أصبح فيه العضو نشطاً</div>
</div>
</div>
<!-- Quick Presets -->
<div style="margin-top:20px;padding:16px;background:#F0FDF4;border-radius:10px;border:1px solid #86EFAC;">
<div style="font-size:13px;font-weight:700;color:#166534;margin-bottom:10px;"><i data-lucide="zap" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> إعدادات سريعة للسيناريوهات</div>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
<button type="button" class="btn-preset" onclick="setPresetDates('2018-01-01','2018-02-01')" style="padding:6px 12px;background:#fff;border:1px solid #D1D5DB;border-radius:6px;font-size:12px;cursor:pointer;">عضو من 2018 (7 سنوات)</button>
<button type="button" class="btn-preset" onclick="setPresetDates('2020-06-01','2020-07-01')" style="padding:6px 12px;background:#fff;border:1px solid #D1D5DB;border-radius:6px;font-size:12px;cursor:pointer;">عضو من 2020 (5 سنوات)</button>
<button type="button" class="btn-preset" onclick="setPresetDates('2022-03-01','2022-04-01')" style="padding:6px 12px;background:#fff;border:1px solid #D1D5DB;border-radius:6px;font-size:12px;cursor:pointer;">عضو من 2022 (3 سنوات)</button>
<button type="button" class="btn-preset" onclick="setPresetDates('2024-01-01','2024-02-01')" style="padding:6px 12px;background:#fff;border:1px solid #D1D5DB;border-radius:6px;font-size:12px;cursor:pointer;">عضو من 2024 (سنة)</button>
<button type="button" class="btn-preset" onclick="setPresetDates('2015-01-01','2015-02-01')" style="padding:6px 12px;background:#fff;border:1px solid #D1D5DB;border-radius:6px;font-size:12px;cursor:pointer;">عضو من 2015 (10 سنوات)</button>
</div>
</div>
<!-- Timeline Preview -->
<div style="margin-top:20px;padding:16px;background:#EFF6FF;border-radius:10px;border:1px solid #93C5FD;">
<div style="font-size:13px;font-weight:700;color:#1E40AF;margin-bottom:8px;">معاينة الجدول الزمني</div>
<div id="timelinePreview" style="font-size:12px;color:#1E40AF;line-height:1.8;">
<div>سنوات العضوية: <strong id="yearsDiff"></strong></div>
<div>اشتراكات سنوية مستحقة: <strong id="subsRequired"></strong></div>
<div>أقساط شهرية محتملة: <strong id="installmentsMax"></strong></div>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════ STEP 3: Financial ═══════════════ -->
<div class="wizard-panel" id="panel-3" style="display:none;">
<div class="card" style="margin-bottom:16px;">
<div style="padding:16px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:16px;font-weight:700;"><i data-lucide="banknote" style="width:18px;height:18px;vertical-align:middle;margin-left:6px;"></i> البيانات المالية — رسوم الاستمارة وقيمة العضوية</h3>
</div>
<div style="padding:20px;">
<!-- Form Fee -->
<div style="padding:16px;background:#F9FAFB;border-radius:10px;border:1px solid #E5E7EB;margin-bottom:20px;">
<div style="font-size:14px;font-weight:700;color:#374151;margin-bottom:12px;">رسوم الاستمارة</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:4px;color:#6B7280;">المبلغ (ج.م)</label>
<input type="number" name="form_fee_amount" value="505" step="0.01" class="form-input" style="width:100%;padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:14px;">
</div>
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:4px;color:#6B7280;">طريقة الدفع</label>
<select name="form_fee_method" class="form-input" style="width:100%;padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:14px;" onchange="toggleCheckFields('form_fee', this.value)">
<option value="cash">نقدي</option>
<option value="check">شيك</option>
<option value="visa">فيزا</option>
<option value="bank_transfer">تحويل بنكي</option>
</select>
</div>
</div>
<div id="form_fee_check_fields" style="display:none;margin-top:12px;grid-template-columns:1fr 1fr 1fr;gap:12px;">
<input type="text" name="form_fee_check_number" placeholder="رقم الشيك" class="form-input" style="padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:13px;">
<input type="text" name="form_fee_check_bank" placeholder="البنك" class="form-input" style="padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:13px;">
<input type="date" name="form_fee_check_date" class="form-input" style="padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:13px;">
</div>
</div>
<!-- Membership Value -->
<div style="padding:16px;background:#F0FDF4;border-radius:10px;border:1px solid #86EFAC;">
<div style="font-size:14px;font-weight:700;color:#166534;margin-bottom:12px;">قيمة العضوية</div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;">
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:4px;color:#6B7280;">قيمة العضوية (ج.م)</label>
<input type="number" name="membership_value" value="150000" step="0.01" class="form-input" style="width:100%;padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:14px;">
</div>
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:4px;color:#6B7280;">الخصم (ج.م)</label>
<input type="number" name="discount_amount" value="0" step="0.01" class="form-input" style="width:100%;padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:14px;">
</div>
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:4px;color:#6B7280;">تاريخ السداد</label>
<input type="date" name="membership_payment_date" class="form-input" style="width:100%;padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:14px;">
</div>
</div>
<div style="margin-top:12px;">
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:4px;color:#6B7280;">طريقة سداد العضوية</label>
<select name="membership_payment_method" id="membershipPayMethod" class="form-input" style="width:100%;padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:14px;" onchange="handleMembershipPayMethod(this.value)">
<option value="cash">نقدي — كامل</option>
<option value="check">شيك — كامل</option>
<option value="visa">فيزا — كامل</option>
<option value="bank_transfer">تحويل بنكي — كامل</option>
<option value="installment">تقسيط (مقدم + أقساط شهرية)</option>
</select>
</div>
<!-- Check/Visa/Transfer fields for membership -->
<div id="membership_extra_fields" style="display:none;margin-top:12px;grid-template-columns:1fr 1fr 1fr;gap:12px;">
<input type="text" name="membership_check_number" placeholder="رقم الشيك" class="form-input" style="padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:13px;">
<input type="text" name="membership_check_bank" placeholder="البنك" class="form-input" style="padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:13px;">
<input type="date" name="membership_check_date" class="form-input" style="padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:13px;">
<input type="text" name="membership_visa_reference" placeholder="رقم عملية الفيزا" class="form-input" style="padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:13px;">
<input type="text" name="membership_transfer_reference" placeholder="رقم التحويل" class="form-input" style="padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:13px;">
<input type="text" name="membership_transfer_bank" placeholder="بنك التحويل" class="form-input" style="padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:13px;">
</div>
</div>
<!-- Quick Value Presets -->
<div style="margin-top:16px;display:flex;flex-wrap:wrap;gap:8px;">
<span style="font-size:12px;color:#6B7280;padding:6px 0;">قيم شائعة:</span>
<button type="button" onclick="document.querySelector('[name=membership_value]').value='150000'" style="padding:4px 10px;background:#EFF6FF;border:1px solid #93C5FD;border-radius:4px;font-size:12px;cursor:pointer;">150,000</button>
<button type="button" onclick="document.querySelector('[name=membership_value]').value='225000'" style="padding:4px 10px;background:#EFF6FF;border:1px solid #93C5FD;border-radius:4px;font-size:12px;cursor:pointer;">225,000</button>
<button type="button" onclick="document.querySelector('[name=membership_value]').value='300000'" style="padding:4px 10px;background:#EFF6FF;border:1px solid #93C5FD;border-radius:4px;font-size:12px;cursor:pointer;">300,000</button>
<button type="button" onclick="document.querySelector('[name=membership_value]').value='500000'" style="padding:4px 10px;background:#EFF6FF;border:1px solid #93C5FD;border-radius:4px;font-size:12px;cursor:pointer;">500,000</button>
</div>
</div>
</div>
</div>
<!-- ═══════════════ STEP 4: Installments ═══════════════ -->
<div class="wizard-panel" id="panel-4" style="display:none;">
<div class="card" style="margin-bottom:16px;">
<div style="padding:16px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:16px;font-weight:700;"><i data-lucide="calculator" style="width:18px;height:18px;vertical-align:middle;margin-left:6px;"></i> خطة التقسيط والأقساط المدفوعة</h3>
<p style="margin:6px 0 0;font-size:12px;color:#6B7280;">فقط إذا كانت طريقة السداد "تقسيط" — حدد الأقساط التي تم سدادها بالفعل</p>
</div>
<div style="padding:20px;">
<div id="installmentSection">
<div id="noInstallmentMsg" style="text-align:center;padding:40px;color:#9CA3AF;">
<i data-lucide="info" style="width:32px;height:32px;margin-bottom:8px;"></i>
<div style="font-size:14px;">طريقة السداد ليست "تقسيط" — يمكنك تخطي هذه الخطوة</div>
</div>
<div id="installmentFields" style="display:none;">
<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:12px;margin-bottom:16px;">
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:4px;color:#6B7280;">المقدم (ج.م)</label>
<input type="number" name="inst_down_payment" id="instDown" value="37500" step="0.01" class="form-input" style="width:100%;padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:14px;" onchange="recalcInstallments()">
</div>
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:4px;color:#6B7280;">عدد الأشهر</label>
<input type="number" name="inst_months" id="instMonths" value="12" min="1" max="30" class="form-input" style="width:100%;padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:14px;" onchange="recalcInstallments()">
</div>
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:4px;color:#6B7280;">نسبة الفائدة %</label>
<input type="number" name="inst_interest_rate" id="instRate" value="22" step="0.01" class="form-input" style="width:100%;padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:14px;" onchange="recalcInstallments()">
</div>
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:4px;color:#6B7280;">تاريخ البدء</label>
<input type="date" name="inst_start_date" id="instStart" class="form-input" style="width:100%;padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:14px;" onchange="recalcInstallments()">
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin-bottom:16px;">
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:4px;color:#6B7280;">طريقة دفع المقدم</label>
<select name="inst_down_method" class="form-input" style="width:100%;padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:14px;">
<option value="cash">نقدي</option>
<option value="check">شيك</option>
<option value="visa">فيزا</option>
</select>
</div>
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:4px;color:#6B7280;">رقم شيك المقدم</label>
<input type="text" name="inst_down_check_number" class="form-input" style="width:100%;padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:13px;">
</div>
<div>
<label style="display:block;font-size:12px;font-weight:600;margin-bottom:4px;color:#6B7280;">بنك شيك المقدم</label>
<input type="text" name="inst_down_check_bank" class="form-input" style="width:100%;padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:13px;">
</div>
</div>
<!-- Schedule Preview -->
<div style="margin-top:16px;padding:16px;background:#F9FAFB;border-radius:10px;border:1px solid #E5E7EB;">
<div style="font-size:13px;font-weight:700;margin-bottom:8px;color:#374151;">جدول الأقساط <span id="instSummary" style="font-weight:400;color:#6B7280;"></span></div>
<div style="max-height:300px;overflow-y:auto;">
<table style="width:100%;border-collapse:collapse;font-size:12px;">
<thead>
<tr style="background:#E5E7EB;">
<th style="padding:6px 8px;text-align:right;">رقم</th>
<th style="padding:6px 8px;text-align:right;">تاريخ الاستحقاق</th>
<th style="padding:6px 8px;text-align:right;">المبلغ</th>
<th style="padding:6px 8px;text-align:right;">أصل</th>
<th style="padding:6px 8px;text-align:right;">فائدة</th>
<th style="padding:6px 8px;text-align:center;">مدفوع؟</th>
<th style="padding:6px 8px;text-align:right;">تاريخ السداد</th>
<th style="padding:6px 8px;text-align:right;">طريقة</th>
</tr>
</thead>
<tbody id="installmentSchedule"></tbody>
</table>
</div>
</div>
<input type="hidden" name="inst_paid_numbers" id="instPaidNumbers" value="">
<!-- Cheques -->
<div style="margin-top:16px;padding:16px;background:#FFF7ED;border-radius:10px;border:1px solid #FDBA74;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<div style="font-size:13px;font-weight:700;color:#9A3412;">شيكات مقدمة</div>
<button type="button" onclick="addCheque()" style="padding:4px 10px;background:#fff;border:1px solid #FDBA74;border-radius:4px;font-size:12px;cursor:pointer;">+ إضافة شيك</button>
</div>
<div id="chequesList"></div>
<input type="hidden" name="cheque_count" id="chequeCount" value="0">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════ STEP 5: Dependents ═══════════════ -->
<div class="wizard-panel" id="panel-5" style="display:none;">
<div class="card" style="margin-bottom:16px;">
<div style="padding:16px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:16px;font-weight:700;"><i data-lucide="users" style="width:18px;height:18px;vertical-align:middle;margin-left:6px;"></i> الملحقون (الزوجات والأبناء)</h3>
</div>
<div style="padding:20px;">
<!-- Spouses -->
<div style="margin-bottom:24px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<div style="font-size:14px;font-weight:700;color:#374151;">الزوجات</div>
<button type="button" onclick="addSpouse()" style="padding:6px 14px;background:#2563EB;color:#fff;border:none;border-radius:6px;font-size:12px;cursor:pointer;font-weight:600;">+ إضافة زوجة</button>
</div>
<div id="spousesList"></div>
<input type="hidden" name="spouse_count" id="spouseCount" value="0">
</div>
<!-- Children -->
<div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<div style="font-size:14px;font-weight:700;color:#374151;">الأبناء</div>
<button type="button" onclick="addChild()" style="padding:6px 14px;background:#2563EB;color:#fff;border:none;border-radius:6px;font-size:12px;cursor:pointer;font-weight:600;">+ إضافة ابن/ابنة</button>
</div>
<div id="childrenList"></div>
<input type="hidden" name="child_count" id="childCount" value="0">
</div>
</div>
</div>
</div>
<!-- ═══════════════ STEP 6: Subscriptions ═══════════════ -->
<div class="wizard-panel" id="panel-6" style="display:none;">
<div class="card" style="margin-bottom:16px;">
<div style="padding:16px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:16px;font-weight:700;"><i data-lucide="repeat" style="width:18px;height:18px;vertical-align:middle;margin-left:6px;"></i> الاشتراكات السنوية</h3>
<p style="margin:6px 0 0;font-size:12px;color:#6B7280;">أضف اشتراكات سنوية — مدفوعة أو متأخرة — لاختبار الغرامات والإسقاط</p>
</div>
<div style="padding:20px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<button type="button" onclick="generateSubscriptionYears()" style="padding:6px 14px;background:#059669;color:#fff;border:none;border-radius:6px;font-size:12px;cursor:pointer;font-weight:600;"><i data-lucide="wand-2" style="width:13px;height:13px;vertical-align:middle;margin-left:4px;"></i> توليد تلقائي من تاريخ الانضمام</button>
<button type="button" onclick="addSubscriptionRow()" style="padding:6px 14px;background:#2563EB;color:#fff;border:none;border-radius:6px;font-size:12px;cursor:pointer;font-weight:600;">+ إضافة سنة يدوياً</button>
</div>
<div style="max-height:400px;overflow-y:auto;">
<table style="width:100%;border-collapse:collapse;font-size:12px;" id="subsTable">
<thead>
<tr style="background:#F3F4F6;">
<th style="padding:8px;text-align:right;">السنة المالية</th>
<th style="padding:8px;text-align:right;">المبلغ الأساسي</th>
<th style="padding:8px;text-align:right;">رسم تنمية</th>
<th style="padding:8px;text-align:right;">الإجمالي</th>
<th style="padding:8px;text-align:right;">المدفوع</th>
<th style="padding:8px;text-align:right;">الغرامة</th>
<th style="padding:8px;text-align:center;">الحالة</th>
<th style="padding:8px;text-align:right;">تاريخ السداد</th>
<th style="padding:8px;text-align:center;">حذف</th>
</tr>
</thead>
<tbody id="subscriptionRows"></tbody>
</table>
</div>
<input type="hidden" name="subscription_count" id="subscriptionCount" value="0">
</div>
</div>
</div>
<!-- ═══════════════ STEP 7: Violations & Fines ═══════════════ -->
<div class="wizard-panel" id="panel-7" style="display:none;">
<div class="card" style="margin-bottom:16px;">
<div style="padding:16px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:16px;font-weight:700;"><i data-lucide="shield-alert" style="width:18px;height:18px;vertical-align:middle;margin-left:6px;"></i> المخالفات والغرامات</h3>
<p style="margin:6px 0 0;font-size:12px;color:#6B7280;">اختياري — أضف مخالفات وغرامات تاريخية لاختبار سيناريوهات الإيقاف والاستئناف</p>
</div>
<div style="padding:20px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<button type="button" onclick="addViolation()" style="padding:6px 14px;background:#DC2626;color:#fff;border:none;border-radius:6px;font-size:12px;cursor:pointer;font-weight:600;">+ إضافة مخالفة</button>
</div>
<div id="violationsList"></div>
<input type="hidden" name="violation_count" id="violationCount" value="0">
<!-- Final Summary -->
<div style="margin-top:24px;padding:20px;background:linear-gradient(135deg,#EFF6FF,#F0FDF4);border-radius:12px;border:1px solid #93C5FD;">
<div style="font-size:16px;font-weight:800;color:#1E40AF;margin-bottom:12px;"><i data-lucide="check-circle-2" style="width:20px;height:20px;vertical-align:middle;margin-left:6px;"></i> ملخص العضوية</div>
<div id="finalSummary" style="font-size:13px;color:#374151;line-height:2;"></div>
</div>
</div>
</div>
</div>
<!-- Navigation Buttons -->
<div class="card" style="padding:16px 20px;display:flex;justify-content:space-between;align-items:center;">
<button type="button" id="btnPrev" onclick="prevStep()" style="padding:10px 24px;background:#F3F4F6;border:1px solid #D1D5DB;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;display:none;">
<i data-lucide="arrow-right" style="width:16px;height:16px;vertical-align:middle;margin-left:4px;"></i> السابق
</button>
<div style="flex:1;"></div>
<button type="button" id="btnNext" onclick="nextStep()" style="padding:10px 24px;background:#2563EB;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;">
التالي <i data-lucide="arrow-left" style="width:16px;height:16px;vertical-align:middle;margin-right:4px;"></i>
</button>
<button type="submit" id="btnSubmit" style="display:none;padding:10px 24px;background:#059669;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:700;cursor:pointer;">
<i data-lucide="rocket" style="width:16px;height:16px;vertical-align:middle;margin-left:4px;"></i> إنشاء العضوية بأثر رجعي
</button>
</div>
</form>
</div>
<script>
let currentStep = 1;
const totalSteps = 7;
function updateStepUI() {
for (let i = 1; i <= totalSteps; i++) {
const panel = document.getElementById('panel-' + i);
const circle = document.getElementById('stepCircle' + i);
const label = document.getElementById('stepLabel' + i);
panel.style.display = (i === currentStep) ? '' : 'none';
if (i < currentStep) {
circle.style.background = '#059669';
circle.style.color = '#fff';
circle.innerHTML = '&#10003;';
label.style.color = '#059669';
label.style.fontWeight = '600';
} else if (i === currentStep) {
circle.style.background = '#2563EB';
circle.style.color = '#fff';
circle.innerHTML = i;
label.style.color = '#2563EB';
label.style.fontWeight = '700';
} else {
circle.style.background = '#E5E7EB';
circle.style.color = '#9CA3AF';
circle.innerHTML = i;
label.style.color = '#6B7280';
label.style.fontWeight = '400';
}
}
document.getElementById('progressFill').style.width = ((currentStep - 1) / (totalSteps - 1) * 100) + '%';
document.getElementById('btnPrev').style.display = currentStep > 1 ? '' : 'none';
document.getElementById('btnNext').style.display = currentStep < totalSteps ? '' : 'none';
document.getElementById('btnSubmit').style.display = currentStep === totalSteps ? '' : 'none';
if (currentStep === 4) updateInstallmentVisibility();
if (currentStep === 7) updateFinalSummary();
if (currentStep === 2) updateTimelinePreview();
}
function nextStep() {
if (currentStep === 1 && !document.querySelector('[name=full_name_ar]').value.trim()) {
alert('الاسم بالعربية مطلوب');
return;
}
if (currentStep < totalSteps) { currentStep++; updateStepUI(); window.scrollTo(0, 0); }
}
function prevStep() {
if (currentStep > 1) { currentStep--; updateStepUI(); window.scrollTo(0, 0); }
}
document.querySelectorAll('.wizard-step-indicator').forEach(el => {
el.addEventListener('click', () => {
const s = parseInt(el.dataset.step);
if (s <= currentStep) { currentStep = s; updateStepUI(); }
});
});
// Date presets
function setPresetDates(form, join) {
document.querySelector('[name=form_date]').value = form;
document.querySelector('[name=join_date]').value = join;
updateTimelinePreview();
}
function updateTimelinePreview() {
const joinDate = document.querySelector('[name=join_date]').value;
if (!joinDate) return;
const join = new Date(joinDate);
const now = new Date();
const years = Math.floor((now - join) / (365.25 * 24 * 60 * 60 * 1000));
document.getElementById('yearsDiff').textContent = years + ' سنة';
document.getElementById('subsRequired').textContent = years + ' اشتراك';
document.getElementById('installmentsMax').textContent = 'حتى ' + Math.min(years * 12, 30) + ' شهر';
}
// Financial fields toggles
function toggleCheckFields(prefix, method) {
const el = document.getElementById(prefix + '_check_fields');
if (el) { el.style.display = (method === 'check') ? 'grid' : 'none'; }
}
function handleMembershipPayMethod(method) {
const extra = document.getElementById('membership_extra_fields');
if (method !== 'cash' && method !== 'installment') {
extra.style.display = 'grid';
extra.style.gridTemplateColumns = '1fr 1fr 1fr';
} else {
extra.style.display = 'none';
}
updateInstallmentVisibility();
}
function updateInstallmentVisibility() {
const method = document.getElementById('membershipPayMethod').value;
const show = method === 'installment';
document.getElementById('noInstallmentMsg').style.display = show ? 'none' : '';
document.getElementById('installmentFields').style.display = show ? '' : 'none';
if (show) recalcInstallments();
}
// Installment schedule calculation
function recalcInstallments() {
const total = parseFloat(document.querySelector('[name=membership_value]').value) || 150000;
const down = parseFloat(document.getElementById('instDown').value) || 37500;
const months = parseInt(document.getElementById('instMonths').value) || 12;
const rate = parseFloat(document.getElementById('instRate').value) || 22;
const startDate = document.getElementById('instStart').value || document.querySelector('[name=join_date]').value || new Date().toISOString().split('T')[0];
const remaining = total - down;
const monthlyRate = rate / 1200;
const monthlyPrincipal = remaining / months;
let balance = remaining;
let totalInterest = 0;
const tbody = document.getElementById('installmentSchedule');
tbody.innerHTML = '';
for (let i = 1; i <= months; i++) {
const interest = Math.round(balance * monthlyRate * 100) / 100;
totalInterest += interest;
const principal = (i === months) ? balance : monthlyPrincipal;
const amount = Math.round((principal + interest) * 100) / 100;
balance -= principal;
if (balance < 0) balance = 0;
const dueDate = new Date(startDate);
dueDate.setMonth(dueDate.getMonth() + i);
const dueDateStr = dueDate.toISOString().split('T')[0];
const isPast = dueDate < new Date();
tbody.innerHTML += `
<tr style="border-bottom:1px solid #F3F4F6;${isPast ? 'background:#FEF3C7;' : ''}">
<td style="padding:6px 8px;">${i}</td>
<td style="padding:6px 8px;direction:ltr;">${dueDateStr}</td>
<td style="padding:6px 8px;">${amount.toLocaleString('ar-EG', {minimumFractionDigits:2})}</td>
<td style="padding:6px 8px;">${Math.round(principal).toLocaleString()}</td>
<td style="padding:6px 8px;">${interest.toLocaleString('ar-EG', {minimumFractionDigits:2})}</td>
<td style="padding:6px 8px;text-align:center;">
<input type="checkbox" class="inst-paid-cb" data-num="${i}" onchange="updatePaidInstallments()" ${isPast ? 'checked' : ''}>
</td>
<td style="padding:6px 8px;">
<input type="date" name="inst_paid_date_${i}" value="${isPast ? dueDateStr : ''}" style="font-size:11px;padding:2px 4px;border:1px solid #D1D5DB;border-radius:4px;width:100%;">
</td>
<td style="padding:6px 8px;">
<select name="inst_paid_method_${i}" style="font-size:11px;padding:2px 4px;border:1px solid #D1D5DB;border-radius:4px;">
<option value="cash">نقدي</option>
<option value="check">شيك</option>
</select>
</td>
</tr>`;
}
document.getElementById('instSummary').textContent = `— إجمالي الفائدة: ${totalInterest.toLocaleString('ar-EG', {minimumFractionDigits:2})} ج.م | القسط الشهري ≈ ${Math.round((remaining + totalInterest) / months).toLocaleString()} ج.م`;
updatePaidInstallments();
}
function updatePaidInstallments() {
const checks = document.querySelectorAll('.inst-paid-cb:checked');
const nums = Array.from(checks).map(cb => cb.dataset.num);
document.getElementById('instPaidNumbers').value = nums.join(',');
}
// Cheques
let chequeIdx = 0;
function addCheque() {
chequeIdx++;
document.getElementById('chequeCount').value = chequeIdx;
document.getElementById('chequesList').innerHTML += `
<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr auto;gap:8px;margin-bottom:8px;align-items:end;" id="chequeRow${chequeIdx}">
<div><label style="font-size:11px;color:#6B7280;">رقم الشيك</label><input type="text" name="cheque_number_${chequeIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<div><label style="font-size:11px;color:#6B7280;">البنك</label><input type="text" name="cheque_bank_${chequeIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<div><label style="font-size:11px;color:#6B7280;">التاريخ</label><input type="date" name="cheque_date_${chequeIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<div><label style="font-size:11px;color:#6B7280;">المبلغ</label><input type="number" name="cheque_amount_${chequeIdx}" step="0.01" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<button type="button" onclick="document.getElementById('chequeRow${chequeIdx}').remove()" style="padding:6px 10px;background:#FEE2E2;border:1px solid #FECACA;border-radius:4px;color:#DC2626;cursor:pointer;font-size:12px;">×</button>
</div>`;
}
// Spouses
let spouseIdx = 0;
function addSpouse() {
spouseIdx++;
document.getElementById('spouseCount').value = spouseIdx;
const joinDate = document.querySelector('[name=join_date]').value || '';
document.getElementById('spousesList').innerHTML += `
<div style="padding:12px;background:#F9FAFB;border-radius:8px;border:1px solid #E5E7EB;margin-bottom:10px;" id="spouseRow${spouseIdx}">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
<span style="font-size:13px;font-weight:600;color:#374151;">زوجة #${spouseIdx}</span>
<button type="button" onclick="document.getElementById('spouseRow${spouseIdx}').remove()" style="padding:2px 8px;background:#FEE2E2;border:1px solid #FECACA;border-radius:4px;color:#DC2626;cursor:pointer;font-size:12px;">حذف</button>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;">
<div><label style="font-size:11px;color:#6B7280;">الاسم *</label><input type="text" name="spouse_name_${spouseIdx}" required class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<div><label style="font-size:11px;color:#6B7280;">الرقم القومي</label><input type="text" name="spouse_nid_${spouseIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;direction:ltr;"></div>
<div><label style="font-size:11px;color:#6B7280;">تاريخ الميلاد</label><input type="date" name="spouse_dob_${spouseIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<div><label style="font-size:11px;color:#6B7280;">الهاتف</label><input type="text" name="spouse_phone_${spouseIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;direction:ltr;"></div>
<div><label style="font-size:11px;color:#6B7280;">تاريخ الانضمام</label><input type="date" name="spouse_join_date_${spouseIdx}" value="${joinDate}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<div><label style="font-size:11px;color:#6B7280;">رسوم الإضافة</label><input type="number" name="spouse_fee_${spouseIdx}" value="0" step="0.01" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
</div>
<div style="margin-top:8px;display:flex;align-items:center;gap:12px;">
<label style="font-size:11px;color:#6B7280;display:flex;align-items:center;gap:4px;"><input type="checkbox" name="spouse_separate_fee_${spouseIdx}" value="1"> رسوم إضافة منفصلة (ليست ضمن العضوية)</label>
<select name="spouse_pay_method_${spouseIdx}" style="font-size:11px;padding:4px 8px;border:1px solid #D1D5DB;border-radius:4px;"><option value="cash">نقدي</option><option value="check">شيك</option></select>
</div>
</div>`;
}
// Children
let childIdx = 0;
function addChild() {
childIdx++;
document.getElementById('childCount').value = childIdx;
const joinDate = document.querySelector('[name=join_date]').value || '';
document.getElementById('childrenList').innerHTML += `
<div style="padding:12px;background:#F9FAFB;border-radius:8px;border:1px solid #E5E7EB;margin-bottom:10px;" id="childRow${childIdx}">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
<span style="font-size:13px;font-weight:600;color:#374151;">ابن/ابنة #${childIdx}</span>
<button type="button" onclick="document.getElementById('childRow${childIdx}').remove()" style="padding:2px 8px;background:#FEE2E2;border:1px solid #FECACA;border-radius:4px;color:#DC2626;cursor:pointer;font-size:12px;">حذف</button>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:10px;">
<div><label style="font-size:11px;color:#6B7280;">الاسم *</label><input type="text" name="child_name_${childIdx}" required class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<div><label style="font-size:11px;color:#6B7280;">الرقم القومي</label><input type="text" name="child_nid_${childIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;direction:ltr;"></div>
<div><label style="font-size:11px;color:#6B7280;">تاريخ الميلاد</label><input type="date" name="child_dob_${childIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<div><label style="font-size:11px;color:#6B7280;">النوع</label><select name="child_gender_${childIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"><option value="male">ذكر</option><option value="female">أنثى</option></select></div>
</div>
<div style="margin-top:8px;display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;">
<div><label style="font-size:11px;color:#6B7280;">تاريخ الانضمام</label><input type="date" name="child_join_date_${childIdx}" value="${joinDate}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<div><label style="font-size:11px;color:#6B7280;">رسوم الإضافة</label><input type="number" name="child_fee_${childIdx}" value="0" step="0.01" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<div style="display:flex;align-items:end;">
<label style="font-size:11px;color:#6B7280;display:flex;align-items:center;gap:4px;"><input type="checkbox" name="child_separate_fee_${childIdx}" value="1"> رسوم منفصلة</label>
</div>
</div>
</div>`;
}
// Subscriptions
let subIdx = 0;
function addSubscriptionRow(year, status, baseAmt, devFee) {
subIdx++;
document.getElementById('subscriptionCount').value = subIdx;
year = year || '';
status = status || 'pending';
baseAmt = baseAmt || '492.00';
devFee = devFee || '35.00';
const total = (parseFloat(baseAmt) + parseFloat(devFee)).toFixed(2);
document.getElementById('subscriptionRows').innerHTML += `
<tr style="border-bottom:1px solid #F3F4F6;" id="subRow${subIdx}">
<td style="padding:6px;"><input type="text" name="sub_year_${subIdx}" value="${year}" placeholder="2023/2024" style="width:90px;padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;direction:ltr;"></td>
<td style="padding:6px;"><input type="number" name="sub_base_${subIdx}" value="${baseAmt}" step="0.01" style="width:70px;padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></td>
<td style="padding:6px;"><input type="number" name="sub_dev_fee_${subIdx}" value="${devFee}" step="0.01" style="width:60px;padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></td>
<td style="padding:6px;"><input type="number" name="sub_total_${subIdx}" value="${total}" step="0.01" style="width:70px;padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></td>
<td style="padding:6px;"><input type="number" name="sub_paid_${subIdx}" value="${status === 'paid' ? total : '0'}" step="0.01" style="width:70px;padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></td>
<td style="padding:6px;"><input type="number" name="sub_fine_${subIdx}" value="0" step="0.01" style="width:60px;padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></td>
<td style="padding:6px;text-align:center;">
<select name="sub_status_${subIdx}" style="padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:11px;" onchange="this.closest('tr').style.background=this.value==='paid'?'#F0FDF4':this.value==='overdue'?'#FEF2F2':'#fff'">
<option value="pending" ${status==='pending'?'selected':''}>معلق</option>
<option value="paid" ${status==='paid'?'selected':''}>مدفوع</option>
<option value="overdue" ${status==='overdue'?'selected':''}>متأخر</option>
<option value="exempted" ${status==='exempted'?'selected':''}>معفى</option>
</select>
</td>
<td style="padding:6px;"><input type="date" name="sub_paid_date_${subIdx}" style="padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:11px;width:110px;"></td>
<td style="padding:6px;text-align:center;"><button type="button" onclick="document.getElementById('subRow${subIdx}').remove()" style="color:#DC2626;cursor:pointer;border:none;background:none;font-size:16px;">×</button></td>
</tr>`;
}
function generateSubscriptionYears() {
const joinDate = document.querySelector('[name=join_date]').value;
if (!joinDate) { alert('حدد تاريخ الانضمام أولاً'); return; }
document.getElementById('subscriptionRows').innerHTML = '';
subIdx = 0;
const joinYear = parseInt(joinDate.split('-')[0]);
const currentYear = new Date().getFullYear();
for (let y = joinYear; y <= currentYear; y++) {
const fy = y + '/' + (y + 1);
const isPast = y < currentYear - 1;
const status = isPast ? 'paid' : (y === currentYear - 1 ? 'overdue' : 'pending');
addSubscriptionRow(fy, status, '492.00', '35.00');
}
}
// Violations
let violationIdx = 0;
function addViolation() {
violationIdx++;
document.getElementById('violationCount').value = violationIdx;
document.getElementById('violationsList').innerHTML += `
<div style="padding:14px;background:#FEF2F2;border-radius:8px;border:1px solid #FECACA;margin-bottom:10px;" id="violationRow${violationIdx}">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
<span style="font-size:13px;font-weight:600;color:#991B1B;">مخالفة #${violationIdx}</span>
<button type="button" onclick="document.getElementById('violationRow${violationIdx}').remove()" style="padding:2px 8px;background:#fff;border:1px solid #FECACA;border-radius:4px;color:#DC2626;cursor:pointer;font-size:12px;">حذف</button>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;">
<div><label style="font-size:11px;color:#6B7280;">تاريخ المخالفة</label><input type="date" name="violation_date_${violationIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<div><label style="font-size:11px;color:#6B7280;">الوصف *</label><input type="text" name="violation_desc_${violationIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;" placeholder="وصف المخالفة"></div>
<div><label style="font-size:11px;color:#6B7280;">ملاحظات/أدلة</label><input type="text" name="violation_evidence_${violationIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
</div>
<div style="margin-top:10px;display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:10px;">
<div><label style="font-size:11px;color:#6B7280;">مبلغ الغرامة</label><input type="number" name="violation_fine_amount_${violationIdx}" value="1000" step="0.01" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<div><label style="font-size:11px;color:#6B7280;">نوع العقوبة</label>
<select name="violation_penalty_type_${violationIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;">
<option value="warning">إنذار</option>
<option value="caution">تنبيه</option>
<option value="fine" selected>غرامة مالية</option>
<option value="suspension">إيقاف مؤقت</option>
<option value="ban">حرمان</option>
<option value="termination">فصل</option>
</select>
</div>
<div><label style="font-size:11px;color:#6B7280;">حالة الغرامة</label>
<select name="violation_fine_status_${violationIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;" onchange="toggleFinePayFields(${violationIdx}, this.value)">
<option value="imposed">مفروضة</option>
<option value="paid">مدفوعة</option>
<option value="waived">متنازل عنها</option>
<option value="appeal_pending">قيد الاستئناف</option>
</select>
</div>
<div><label style="font-size:11px;color:#6B7280;">تاريخ السداد</label><input type="date" name="violation_fine_paid_date_${violationIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
</div>
<div style="margin-top:8px;display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<div><label style="font-size:11px;color:#6B7280;">بداية الإيقاف</label><input type="date" name="violation_suspension_from_${violationIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
<div><label style="font-size:11px;color:#6B7280;">نهاية الإيقاف</label><input type="date" name="violation_suspension_to_${violationIdx}" class="form-input" style="width:100%;padding:6px 8px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></div>
</div>
</div>`;
}
function toggleFinePayFields(idx, status) {
const dateField = document.querySelector(`[name=violation_fine_paid_date_${idx}]`);
if (dateField) dateField.closest('div').style.opacity = status === 'paid' ? '1' : '0.4';
}
// Final Summary
function updateFinalSummary() {
const name = document.querySelector('[name=full_name_ar]').value || '—';
const joinDate = document.querySelector('[name=join_date]').value || '—';
const membershipValue = parseFloat(document.querySelector('[name=membership_value]').value || 0);
const method = document.getElementById('membershipPayMethod').value;
const methodLabels = {cash:'نقدي كامل',check:'شيك',visa:'فيزا',bank_transfer:'تحويل بنكي',installment:'تقسيط'};
const spouses = parseInt(document.getElementById('spouseCount').value) || 0;
const children = parseInt(document.getElementById('childCount').value) || 0;
const subs = parseInt(document.getElementById('subscriptionCount').value) || 0;
const violations = parseInt(document.getElementById('violationCount').value) || 0;
let html = `
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
<div><strong>الاسم:</strong> ${name}</div>
<div><strong>تاريخ الانضمام:</strong> ${joinDate}</div>
<div><strong>قيمة العضوية:</strong> ${membershipValue.toLocaleString()} ج.م</div>
<div><strong>طريقة السداد:</strong> ${methodLabels[method] || method}</div>
<div><strong>الزوجات:</strong> ${spouses}</div>
<div><strong>الأبناء:</strong> ${children}</div>
<div><strong>اشتراكات سنوية:</strong> ${subs} سنة</div>
<div><strong>مخالفات:</strong> ${violations}</div>
</div>`;
if (method === 'installment') {
const months = document.getElementById('instMonths').value || '12';
const down = parseFloat(document.getElementById('instDown').value || 0);
const paidNums = document.getElementById('instPaidNumbers').value;
const paidCount = paidNums ? paidNums.split(',').length : 0;
html += `<div style="margin-top:8px;padding:8px;background:#fff;border-radius:6px;"><strong>خطة التقسيط:</strong> ${months} شهر — مقدم ${down.toLocaleString()} ج.م — أقساط مدفوعة: ${paidCount}/${months}</div>`;
}
document.getElementById('finalSummary').innerHTML = html;
}
// Init
updateStepUI();
</script>
<style>
.wizard-panel .card { transition: all 0.2s; }
.wizard-panel .card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
.btn-preset:hover { background: #EFF6FF !important; border-color: #2563EB !important; }
</style>
<?php $__template->endSection(); ?>
......@@ -16,6 +16,7 @@ MenuRegistry::register('membership', [
// ── Core Members ────────────────────────────
['label_ar' => 'كل الأعضاء', 'label_en' => 'All Members', 'route' => '/members', 'permission' => 'member.view', 'order' => 1],
['label_ar' => 'عضو جديد', 'label_en' => 'New Member', 'route' => '/members/create', 'permission' => 'member.create', 'order' => 2],
['label_ar' => 'إدخال بأثر رجعي', 'label_en' => 'Retroactive Wizard', 'route' => '/members/retroactive-wizard', 'permission' => 'member.create', 'order' => 3],
['label_ar' => 'بحث الأعضاء', 'label_en' => 'Search Members', 'route' => '/members/search', 'permission' => 'member.search', 'order' => 3],
// ── Membership Types ────────────────────────
['label_ar' => 'الأعضاء المؤقتون', 'label_en' => 'Temporary Members', 'route' => '/temporary', 'permission' => 'temp.view', 'order' => 10],
......
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