Commit 45c184f0 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fgnfgjn

parent 4f63ae92
......@@ -262,6 +262,7 @@ final class PaymentRequestService
'carnet_replacement' => 'بدل فاقد كارنيه',
'sports_registration' => 'تسجيل رياضي',
'activity_subscription' => 'اشتراك نشاط',
'seasonal_fee' => 'رسوم عضوية موسمية',
default => $type,
};
}
......
......@@ -113,6 +113,39 @@ EventBus::listen('payment_request.completed', function (array $data) {
EventBus::dispatch($cfg['event'], [$cfg['key'] => $entityId, 'payment_id' => $paymentId]);
\App\Core\Logger::info("Life-event fee paid via cashier", ['type' => $paymentType, 'entity_id' => $entityId]);
}
$receiptNumber = $data['receipt_number'] ?? '';
if ($paymentType === 'addition_fee' && $entityType === 'spouses') {
$db->update('spouses', [
'status' => 'active',
'join_date' => date('Y-m-d'),
'fee_receipt_number' => $receiptNumber ?: null,
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [$entityId]);
EventBus::dispatch('spouse.fee_paid', ['spouse_id' => $entityId, 'member_id' => $memberId, 'payment_id' => $paymentId]);
\App\Core\Logger::info("Spouse activated via cashier", ['spouse_id' => $entityId]);
}
if ($paymentType === 'addition_fee' && $entityType === 'children') {
$db->update('children', [
'status' => 'active',
'fee_receipt_number' => $receiptNumber ?: null,
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [$entityId]);
EventBus::dispatch('child.fee_paid', ['child_id' => $entityId, 'member_id' => $memberId, 'payment_id' => $paymentId]);
\App\Core\Logger::info("Child activated via cashier", ['child_id' => $entityId]);
}
if ($paymentType === 'addition_fee' && $entityType === 'temporary_members') {
$db->update('temporary_members', [
'status' => 'active',
'fee_receipt_number' => $receiptNumber ?: null,
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [$entityId]);
EventBus::dispatch('temporary.fee_paid', ['temp_id' => $entityId, 'member_id' => $memberId, 'payment_id' => $paymentId]);
\App\Core\Logger::info("Temporary member activated via cashier", ['temp_id' => $entityId]);
}
}
} catch (\Throwable $e) {
\App\Core\Logger::error("payment_request.completed listener failed: " . $e->getMessage(), ['data' => $data]);
......
......@@ -11,6 +11,7 @@ use App\Core\EventBus;
use App\Modules\Children\Models\Child;
use App\Modules\Children\Services\ChildFeeCalculator;
use App\Modules\Members\Services\NationalIdParser;
use App\Modules\Cashier\Services\PaymentRequestService;
class ChildController extends Controller
{
......@@ -127,6 +128,9 @@ class ChildController extends Controller
$childOrder = $feeCalc['child_order'] ?? Child::getNextOrder((int) $memberId);
$classification = $feeCalc['classification'] ?? 'included';
$totalFee = $feeCalc['total_fee'] ?? $feeCalc['fee'] ?? '0.00';
$hasFee = bccomp($totalFee, '0', 2) > 0;
$child = Child::create([
'member_id' => (int) $memberId,
'child_order' => $childOrder,
......@@ -142,8 +146,8 @@ class ChildController extends Controller
'school_faculty' => $data['school_faculty'] ?? null,
'nationality' => $data['nationality'] ?? 'مصري',
'classification' => $classification,
'addition_fee' => $feeCalc['fee'] ?? '0.00',
'status' => 'active',
'addition_fee' => $totalFee,
'status' => $hasFee ? 'pending_payment' : 'active',
'remarks' => $data['remarks'] ?? null,
]);
......@@ -151,11 +155,31 @@ class ChildController extends Controller
'member_id' => (int) $memberId,
'child_id' => (int) $child->id,
'classification' => $classification,
'fee' => $feeCalc['fee'] ?? '0.00',
'fee' => $totalFee,
]);
if ($hasFee) {
$childLabel = $data['gender'] === 'male' ? 'ابن' : 'ابنة';
$payResult = PaymentRequestService::createRequest([
'member_id' => (int) $memberId,
'amount' => $totalFee,
'payment_type' => 'addition_fee',
'related_entity_type' => 'children',
'related_entity_id' => (int) $child->id,
'description_ar' => 'رسوم إضافة ' . $childLabel . ' — ' . trim($data['full_name_ar']),
]);
if ($payResult['success']) {
return $this->redirect("/members/{$memberId}")
->withSuccess('تم إضافة الابن/الابنة وإرسال طلب الدفع للخزينة — التصنيف: ' . $child->getClassificationLabel() . ' — الرسوم: ' . money($totalFee) . ' — رقم الطلب: ' . $payResult['request_number']);
}
return $this->redirect("/members/{$memberId}")
->withError('تم إضافة الابن/الابنة لكن فشل إنشاء طلب الدفع: ' . ($payResult['error'] ?? ''));
}
return $this->redirect("/members/{$memberId}")
->withSuccess('تم إضافة الابن/الابنة — التصنيف: ' . ($child->getClassificationLabel()) . ' — الرسوم: ' . money($feeCalc['fee'] ?? '0.00'));
->withSuccess('تم إضافة الابن/الابنة — التصنيف: ' . $child->getClassificationLabel() . ' — مشمول بدون رسوم');
}
public function show(Request $request, string $memberId, string $id): Response
......
......@@ -119,9 +119,10 @@ class MemberController extends Controller
$branch = $db->selectOne("SELECT name_ar FROM branches WHERE id = ?", [(int) $member->branch_id]);
$qualification = $member->qualification_id ? $db->selectOne("SELECT name_ar FROM qualifications WHERE id = ?", [(int) $member->qualification_id]) : null;
$spouses = []; $children = [];
$spouses = []; $children = []; $temporaries = [];
try { $spouses = $db->select("SELECT * FROM spouses WHERE member_id = ? AND is_archived = 0 ORDER BY spouse_order", [(int) $id]); } catch (\Throwable $e) {}
try { $children = $db->select("SELECT * FROM children WHERE member_id = ? AND is_archived = 0 ORDER BY child_order", [(int) $id]); } catch (\Throwable $e) {}
try { $temporaries = $db->select("SELECT * FROM temporary_members WHERE member_id = ? AND is_archived = 0 ORDER BY id", [(int) $id]); } catch (\Throwable $e) {}
$bill = BillingService::getMemberBill((int) $id);
$formFilled = ($member->qualification_id !== null && $member->qualification_id > 0);
......@@ -147,6 +148,7 @@ class MemberController extends Controller
'qualificationName' => $qualification['name_ar'] ?? '—',
'spouses' => $spouses,
'children' => $children,
'temporaries' => $temporaries,
'bill' => $bill,
'formFee' => MemberNumberGenerator::getFormFee(),
'formFilled' => $formFilled,
......
......@@ -23,12 +23,27 @@ final class BillingService
// ── 1. Form Fee (505) ──
$formFeePaid = false;
$formFeePending = false;
try {
$ff = $db->selectOne(
"SELECT id FROM payments WHERE member_id = ? AND payment_type = 'form_fee' AND is_voided = 0 LIMIT 1",
[$memberId]
);
$formFeePaid = ($ff !== null);
if (!$formFeePaid) {
$ffReq = $db->selectOne(
"SELECT id, status FROM payment_requests WHERE member_id = ? AND payment_type = 'form_fee' AND is_voided = 0 AND status IN ('pending','processing') LIMIT 1",
[$memberId]
);
$formFeePending = ($ffReq !== null);
if (!$formFeePending) {
$ffCompleted = $db->selectOne(
"SELECT id FROM payment_requests WHERE member_id = ? AND payment_type = 'form_fee' AND is_voided = 0 AND status = 'completed' LIMIT 1",
[$memberId]
);
$formFeePaid = ($ffCompleted !== null);
}
}
} catch (\Throwable $e) {}
$formFeeAmount = ServicePrice::getPrice('SVC_NEW_FORM', '505.00');
......@@ -44,12 +59,27 @@ final class BillingService
// ── 2. Membership Value ──
$membershipPaid = false;
$membershipPending = false;
try {
$mp = $db->selectOne(
"SELECT id FROM payments WHERE member_id = ? AND payment_type IN ('membership_fee','down_payment') AND is_voided = 0 LIMIT 1",
[$memberId]
);
$membershipPaid = ($mp !== null);
if (!$membershipPaid) {
$mpReq = $db->selectOne(
"SELECT id FROM payment_requests WHERE member_id = ? AND payment_type IN ('membership_fee','down_payment') AND is_voided = 0 AND status IN ('pending','processing') LIMIT 1",
[$memberId]
);
$membershipPending = ($mpReq !== null);
if (!$membershipPending) {
$mpCompleted = $db->selectOne(
"SELECT id FROM payment_requests WHERE member_id = ? AND payment_type IN ('membership_fee','down_payment') AND is_voided = 0 AND status = 'completed' LIMIT 1",
[$memberId]
);
$membershipPaid = ($mpCompleted !== null);
}
}
} catch (\Throwable $e) {}
$qualName = '—';
......@@ -172,13 +202,13 @@ final class BillingService
// ── Check what's already paid ──
try {
$paidAdditions = $db->select(
"SELECT related_entity_type, related_entity_id FROM payments
"SELECT related_entity_type, related_entity_id FROM payments
WHERE member_id = ? AND payment_type = 'addition_fee' AND is_voided = 0",
[$memberId]
);
foreach ($paidAdditions as $pa) {
foreach ($items as &$item) {
if (($item['entity_type'] ?? '') === $pa['related_entity_type']
if (($item['entity_type'] ?? '') === $pa['related_entity_type']
&& ($item['entity_id'] ?? 0) == $pa['related_entity_id']) {
$item['paid'] = true;
}
......@@ -187,6 +217,28 @@ final class BillingService
}
} catch (\Throwable $e) {}
// ── Check payment_requests for completed or pending additions ──
try {
$additionRequests = $db->select(
"SELECT related_entity_type, related_entity_id, status FROM payment_requests
WHERE member_id = ? AND payment_type = 'addition_fee' AND is_voided = 0",
[$memberId]
);
foreach ($additionRequests as $ar) {
foreach ($items as &$item) {
if (($item['entity_type'] ?? '') === $ar['related_entity_type']
&& ($item['entity_id'] ?? 0) == $ar['related_entity_id']) {
if ($ar['status'] === 'completed') {
$item['paid'] = true;
} elseif (in_array($ar['status'], ['pending', 'processing'], true)) {
$item['in_queue'] = true;
}
}
}
unset($item);
}
} catch (\Throwable $e) {}
// ── Calculate totals ──
$totalRequired = '0.00';
$totalPaid = '0.00';
......@@ -207,15 +259,17 @@ final class BillingService
}
return [
'items' => $items,
'membership_value' => $membershipValue,
'total_required' => $totalRequired,
'total_paid' => $totalPaid,
'total_pending' => $totalPending,
'total_included' => $totalIncluded,
'form_fee_paid' => $formFeePaid,
'membership_paid' => $membershipPaid,
'all_paid' => (bccomp($totalPending, '0', 2) <= 0),
'items' => $items,
'membership_value' => $membershipValue,
'total_required' => $totalRequired,
'total_paid' => $totalPaid,
'total_pending' => $totalPending,
'total_included' => $totalIncluded,
'form_fee_paid' => $formFeePaid,
'form_fee_pending' => $formFeePending ?? false,
'membership_paid' => $membershipPaid,
'membership_pending' => $membershipPending ?? false,
'all_paid' => (bccomp($totalPending, '0', 2) <= 0),
];
}
}
\ No newline at end of file
This diff is collapsed.
......@@ -11,6 +11,7 @@ use App\Core\EventBus;
use App\Modules\Seasonal\Models\SeasonalMembership;
use App\Modules\Rules\Services\RuleEngine;
use App\Modules\Payments\Services\PaymentService;
use App\Modules\Cashier\Services\PaymentRequestService;
class SeasonalController extends Controller
{
......@@ -110,21 +111,20 @@ class SeasonalController extends Controller
'notes' => $data['notes'] ?? null,
]);
// Process payment through central PaymentService
// Send payment to cashier queue
if (bccomp($fee, '0.00', 2) > 0) {
$payResult = PaymentService::processPayment([
$payResult = PaymentRequestService::createRequest([
'member_id' => (int) $memberId,
'amount' => $fee,
'payment_type' => 'seasonal_fee',
'payment_method' => $data['payment_method'] ?? 'cash',
'related_entity_type' => 'seasonal_memberships',
'related_entity_id' => (int) $seasonal->id,
'description' => 'رسوم عضوية موسمية — من ' . $startDate . ' إلى ' . $endDate,
'description_ar' => 'رسوم عضوية موسمية — من ' . $startDate . ' إلى ' . $endDate,
]);
if (!$payResult['success']) {
return $this->redirect("/members/{$memberId}/seasonal/create")
->withError('تم إنشاء العضوية الموسمية لكن فشل تسجيل الدفع: ' . ($payResult['error'] ?? ''));
->withError('تم إنشاء العضوية الموسمية لكن فشل إنشاء طلب الدفع: ' . ($payResult['error'] ?? ''));
}
}
......@@ -135,6 +135,6 @@ class SeasonalController extends Controller
]);
return $this->redirect("/members/{$memberId}")
->withSuccess("تم إنشاء العضوية الموسمية — من {$startDate} إلى {$endDate} — الرسوم: " . money($fee));
->withSuccess("تم إنشاء العضوية الموسمية وإرسال طلب الدفع للخزينة — من {$startDate} إلى {$endDate} — الرسوم: " . money($fee));
}
}
\ No newline at end of file
......@@ -11,6 +11,7 @@ use App\Core\EventBus;
use App\Modules\Spouses\Models\Spouse;
use App\Modules\Spouses\Services\SpouseFeeCalculator;
use App\Modules\Members\Services\NationalIdParser;
use App\Modules\Cashier\Services\PaymentRequestService;
class SpouseController extends Controller
{
......@@ -145,6 +146,9 @@ class SpouseController extends Controller
$spouseOrder = $feeCalc['spouse_order'] ?? ($currentCount + 1);
$totalFee = $feeCalc['total_fee'] ?? '0.00';
$hasFee = bccomp($totalFee, '0', 2) > 0;
$spouse = Spouse::create([
'member_id' => (int) $memberId,
'spouse_order' => $spouseOrder,
......@@ -164,22 +168,43 @@ class SpouseController extends Controller
'work_phone' => $data['work_phone'] ?? null,
'mobile' => $data['mobile'] ?? null,
'marriage_date' => $data['marriage_date'],
'join_date' => date('Y-m-d'),
'join_date' => $hasFee ? null : date('Y-m-d'),
'classification' => 'working',
'addition_fee' => $feeCalc['total_fee'] ?? '0.00',
'status' => 'active',
'addition_fee' => $totalFee,
'status' => $hasFee ? 'pending_payment' : 'active',
]);
EventBus::dispatch('spouse.added', [
'member_id' => (int) $memberId,
'spouse_id' => (int) $spouse->id,
'spouse_order'=> $spouseOrder,
'fee' => $feeCalc['total_fee'] ?? '0.00',
'fee' => $totalFee,
]);
$genderWord = $requiredGender === 'male' ? 'الزوج' : 'الزوجة';
if ($hasFee) {
$payResult = PaymentRequestService::createRequest([
'member_id' => (int) $memberId,
'amount' => $totalFee,
'payment_type' => 'addition_fee',
'related_entity_type' => 'spouses',
'related_entity_id' => (int) $spouse->id,
'description_ar' => 'رسوم إضافة ' . $genderWord . ' — ' . trim($data['full_name_ar']),
'notes' => json_encode(['fee_breakdown' => $feeCalc['breakdown'] ?? []], JSON_UNESCAPED_UNICODE),
]);
if ($payResult['success']) {
return $this->redirect("/members/{$memberId}")
->withSuccess("تم إضافة {$genderWord} وإرسال طلب الدفع للخزينة — الرسوم: " . money($totalFee) . ' — رقم الطلب: ' . $payResult['request_number']);
}
return $this->redirect("/members/{$memberId}")
->withError("تم إضافة {$genderWord} لكن فشل إنشاء طلب الدفع: " . ($payResult['error'] ?? ''));
}
return $this->redirect("/members/{$memberId}")
->withSuccess("تم إضافة {$genderWord} — الترتيب: #{$spouseOrder}الرسوم: " . money($feeCalc['total_fee'] ?? '0.00'));
->withSuccess("تم إضافة {$genderWord} — الترتيب: #{$spouseOrder}مشمول/ة في قيمة العضوية");
}
public function show(Request $request, string $memberId, string $id): Response
......
......@@ -11,6 +11,7 @@ use App\Core\EventBus;
use App\Modules\Temporary\Models\TemporaryMember;
use App\Modules\Temporary\Services\TemporaryFeeCalculator;
use App\Modules\Members\Services\NationalIdParser;
use App\Modules\Cashier\Services\PaymentRequestService;
class TemporaryController extends Controller
{
......@@ -129,6 +130,9 @@ class TemporaryController extends Controller
$feeCalc = TemporaryFeeCalculator::calculate((int) $memberId, $data);
$totalFee = $feeCalc['total_fee'] ?? $feeCalc['fee'] ?? '0.00';
$hasFee = bccomp($totalFee, '0', 2) > 0;
$temp = TemporaryMember::create([
'member_id' => (int) $memberId,
'category' => $category,
......@@ -144,10 +148,10 @@ class TemporaryController extends Controller
'relationship_to_member' => $data['relationship_to_member'] ?? null,
'has_championship' => !empty($data['has_championship']) ? 1 : 0,
'disability_documentation' => !empty($data['disability_documentation']) ? 1 : 0,
'addition_fee' => $feeCalc['fee'] ?? '0.00',
'addition_fee' => $totalFee,
'can_separate' => TemporaryFeeCalculator::canSeparate($category) ? 1 : 0,
'can_get_independent' => TemporaryFeeCalculator::canGetIndependent($category) ? 1 : 0,
'status' => 'active',
'status' => $hasFee ? 'pending_payment' : 'active',
'notes' => $data['notes'] ?? null,
]);
......@@ -155,11 +159,31 @@ class TemporaryController extends Controller
'member_id' => (int) $memberId,
'temporary_id' => (int) $temp->id,
'category' => $category,
'fee' => $feeCalc['fee'] ?? '0.00',
'fee' => $totalFee,
]);
if ($hasFee) {
$catLabel = $temp->getCategoryLabel();
$payResult = PaymentRequestService::createRequest([
'member_id' => (int) $memberId,
'amount' => $totalFee,
'payment_type' => 'addition_fee',
'related_entity_type' => 'temporary_members',
'related_entity_id' => (int) $temp->id,
'description_ar' => 'رسوم إضافة عضو مؤقت (' . $catLabel . ') — ' . trim($data['full_name_ar']),
]);
if ($payResult['success']) {
return $this->redirect("/members/{$memberId}")
->withSuccess('تم إضافة العضو المؤقت وإرسال طلب الدفع للخزينة — الرسوم: ' . money($totalFee) . ' — رقم الطلب: ' . $payResult['request_number']);
}
return $this->redirect("/members/{$memberId}")
->withError('تم إضافة العضو المؤقت لكن فشل إنشاء طلب الدفع: ' . ($payResult['error'] ?? ''));
}
return $this->redirect("/members/{$memberId}")
->withSuccess('تم إضافة العضو المؤقت — الرسوم: ' . money($feeCalc['fee'] ?? '0.00'));
->withSuccess('تم إضافة العضو المؤقت — معفى من الرسوم');
}
public function show(Request $request, string $memberId, string $id): Response
......
......@@ -31,7 +31,7 @@ $currentPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
<!-- Main Stylesheet -->
<link rel="stylesheet" href="<?= url('assets/css/main.css') ?>">
<link rel="stylesheet" href="<?= url('assets/css/main.css') ?>?v=<?= filemtime(dirname(__DIR__, 2) . '/public/assets/css/main.css') ?: time() ?>">
<?= $__template->yield('styles', '') ?>
</head>
<body>
......@@ -97,7 +97,7 @@ $currentPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
</footer>
</div>
<script src="<?= url('assets/js/app.js') ?>"></script>
<script src="<?= url('assets/js/app.js') ?>?v=<?= filemtime(dirname(__DIR__, 2) . '/public/assets/js/app.js') ?: time() ?>"></script>
<script>
// Initialize Lucide icons
lucide.createIcons();
......
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