Commit f1fb0d5a authored by Mahmoud Aglan's avatar Mahmoud Aglan

dghdfhmk

parent 5b4e277d
......@@ -73,7 +73,10 @@ class CashierController extends Controller
return $this->redirect('/cashier/' . $id)->withError($result['error']);
}
return $this->redirect('/cashier')->withSuccess(
$receiptId = $result['receipt_id'] ?? null;
$printParam = $receiptId ? '?print_receipt=' . $receiptId : '';
return $this->redirect('/cashier' . $printParam)->withSuccess(
'تم تحصيل الدفعة — إيصال: ' . ($result['receipt_number'] ?? '')
);
}
......
......@@ -36,16 +36,19 @@ final class PaymentRequestService
if ($entityType !== null && $entityId !== null) {
$existing = $db->selectOne(
"SELECT id FROM payment_requests WHERE member_id = ? AND payment_type = ? AND related_entity_type = ? AND related_entity_id = ? AND status IN ('pending','processing') AND is_voided = 0 LIMIT 1",
"SELECT id, status FROM payment_requests WHERE member_id = ? AND payment_type = ? AND related_entity_type = ? AND related_entity_id = ? AND is_voided = 0 AND status IN ('pending','processing','completed') ORDER BY FIELD(status,'pending','processing','completed') LIMIT 1",
[$memberId, $paymentType, $entityType, (int) $entityId]
);
} else {
$existing = $db->selectOne(
"SELECT id FROM payment_requests WHERE member_id = ? AND payment_type = ? AND status IN ('pending','processing') AND is_voided = 0 LIMIT 1",
"SELECT id, status FROM payment_requests WHERE member_id = ? AND payment_type = ? AND is_voided = 0 AND status IN ('pending','processing') LIMIT 1",
[$memberId, $paymentType]
);
}
if ($existing) {
if ($existing['status'] === 'completed') {
return ['success' => false, 'error' => 'تم دفع هذا البند بالفعل'];
}
return ['success' => false, 'error' => 'يوجد طلب دفع معلق بالفعل لنفس النوع'];
}
......@@ -147,6 +150,7 @@ final class PaymentRequestService
return [
'success' => true,
'payment_id' => $paymentResult['payment_id'],
'receipt_id' => $paymentResult['receipt_id'] ?? null,
'receipt_number' => $paymentResult['receipt_number'] ?? '',
'request_number' => $request['request_number'],
];
......@@ -221,10 +225,12 @@ final class PaymentRequestService
return $db->select(
"SELECT pr.*, m.full_name_ar as member_name, m.form_number, m.membership_number,
e.full_name_ar as requested_by_name
e.full_name_ar as requested_by_name,
pay.receipt_id
FROM payment_requests pr
LEFT JOIN members m ON m.id = pr.member_id
LEFT JOIN employees e ON e.id = pr.requested_by
LEFT JOIN payments pay ON pay.id = pr.payment_id
WHERE {$where}
ORDER BY pr.created_at ASC
LIMIT 200",
......
......@@ -59,15 +59,15 @@ $statusLabel = match($pr['status']) {
<?php if (is_array($feeBreakdown) && !empty($feeBreakdown)): ?>
<tr>
<td colspan="2" style="padding:12px 0;">
<div style="background:#F9FAFB;border:1px solid #E5E7EB;border-radius:8px;padding:12px 16px;">
<div style="font-weight:700;color:#1A1A2E;margin-bottom:8px;font-size:13px;">تفاصيل المبلغ:</div>
<div style="background:#FFFBEB;border:2px solid #F59E0B;border-radius:10px;padding:16px 20px;">
<div style="font-weight:700;color:#92400E;margin-bottom:10px;font-size:14px;">📊 تفصيل المبلغ (لماذا هذا الرقم):</div>
<?php foreach ($feeBreakdown as $line): ?>
<?php if (str_contains($line, '═══')): ?>
<hr style="border:0;border-top:1px solid #D1D5DB;margin:6px 0;">
<hr style="border:0;border-top:2px solid #FCD34D;margin:8px 0;">
<?php elseif (str_starts_with($line, '💵')): ?>
<div style="font-weight:700;color:#059669;font-size:14px;margin-top:4px;"><?= e($line) ?></div>
<div style="font-weight:700;color:#059669;font-size:16px;margin-top:6px;background:#ECFDF5;padding:6px 10px;border-radius:6px;"><?= e($line) ?></div>
<?php else: ?>
<div style="font-size:13px;color:#374151;padding:2px 0;"><?= e($line) ?></div>
<div style="font-size:14px;color:#374151;padding:3px 0;"><?= e($line) ?></div>
<?php endif; ?>
<?php endforeach; ?>
</div>
......
......@@ -114,18 +114,20 @@
$nd = $r['notes'] ? json_decode($r['notes'], true) : null;
$fb = $nd['fee_breakdown'] ?? null;
if (is_array($fb) && !empty($fb)):
$summary = [];
$detailLines = [];
$totalLine = '';
foreach ($fb as $ln) {
if (str_contains($ln, '═══') || str_starts_with($ln, '💵')) continue;
$summary[] = $ln;
if (str_contains($ln, '═══')) continue;
if (str_starts_with($ln, '💵')) { $totalLine = $ln; continue; }
$detailLines[] = $ln;
}
?>
<div style="font-size:11px;font-weight:400;color:#6B7280;margin-top:2px;max-width:220px;line-height:1.4;">
<?php foreach (array_slice($summary, 0, 3) as $sl): ?>
<div><?= e($sl) ?></div>
<div style="font-size:11px;font-weight:400;color:#374151;margin-top:4px;max-width:280px;line-height:1.5;text-align:right;direction:rtl;background:#F9FAFB;border:1px solid #E5E7EB;border-radius:6px;padding:6px 8px;">
<?php foreach ($detailLines as $dl): ?>
<div style="padding:1px 0;"><?= e($dl) ?></div>
<?php endforeach; ?>
<?php if (count($summary) > 3): ?>
<div style="color:#9CA3AF;">+<?= count($summary) - 3 ?> بنود أخرى</div>
<?php if ($totalLine): ?>
<div style="border-top:1px solid #D1D5DB;margin-top:4px;padding-top:4px;font-weight:700;color:#059669;"><?= e($totalLine) ?></div>
<?php endif; ?>
</div>
<?php endif; ?>
......@@ -137,7 +139,10 @@
<?php if ($r['status'] === 'pending' || $r['status'] === 'processing'): ?>
<a href="/cashier/<?= (int)$r['id'] ?>" class="btn btn-sm btn-primary">تحصيل</a>
<?php elseif ($r['status'] === 'completed'): ?>
<span style="color:#059669;font-size:12px;">تم</span>
<span style="color:#059669;font-size:12px;">&#x2705; تم</span>
<?php if (!empty($r['receipt_id'])): ?>
<a href="/receipts/<?= (int)$r['receipt_id'] ?>/print" target="_blank" class="btn btn-sm btn-outline" style="font-size:11px;margin-top:4px;">&#x1f5a8; طباعة</a>
<?php endif; ?>
<?php endif; ?>
</td>
</tr>
......@@ -155,5 +160,15 @@
<script>
setTimeout(function(){ location.reload(); }, 30000);
(function(){
var params = new URLSearchParams(window.location.search);
var printId = params.get('print_receipt');
if (printId) {
window.open('/receipts/' + printId + '/print', '_blank');
params.delete('print_receipt');
var newUrl = window.location.pathname + (params.toString() ? '?' + params.toString() : '');
window.history.replaceState({}, '', newUrl);
}
})();
</script>
<?php $__template->endSection(); ?>
......@@ -36,7 +36,7 @@ class Child extends Model
{
$db = App::getInstance()->db();
$row = $db->selectOne(
"SELECT COUNT(*) as cnt FROM children WHERE member_id = ? AND is_archived = 0 AND status = 'active'",
"SELECT COUNT(*) as cnt FROM children WHERE member_id = ? AND is_archived = 0",
[$memberId]
);
return (int) ($row['cnt'] ?? 0);
......@@ -46,7 +46,7 @@ class Child extends Model
{
$db = App::getInstance()->db();
$row = $db->selectOne(
"SELECT COUNT(*) as cnt FROM children WHERE member_id = ? AND is_archived = 0 AND status = 'active' AND age_years < 18",
"SELECT COUNT(*) as cnt FROM children WHERE member_id = ? AND is_archived = 0 AND age_years < 18",
[$memberId]
);
return (int) ($row['cnt'] ?? 0);
......
......@@ -21,7 +21,8 @@ final class ChildFeeCalculator
}
$membershipValue = $member['membership_value'] ?? '0.00';
if (bccomp($membershipValue, '0.00', 2) <= 0) {
$isOnInitialForm = FormFeeService::isOnInitialForm($member);
if (bccomp($membershipValue, '0.00', 2) <= 0 && !$isOnInitialForm) {
return ['error' => 'قيمة العضوية غير محددة', 'fee' => '0.00', 'classification' => 'included'];
}
......
......@@ -90,6 +90,19 @@ class DeathController extends Controller
// Send payment to cashier queue
if (bccomp($totalFee, '0', 2) > 0) {
$deceasedLabel = match ($deceasedType) {
'primary_member' => 'العضو الأصلي',
'spouse' => 'الزوج/ة',
default => $deceasedType,
};
$breakdown = [
'📋 نوع الوفاة: ' . $deceasedLabel,
'📝 رسوم استمارة نقل: ' . money($formFee),
'📅 اشتراك سنوي: ' . money($annualSubBase) . ' + تنمية ' . money($devFee) . ' = ' . money($annualSub),
'═══════════════════════════',
'💵 الإجمالي: ' . money($totalFee),
];
$result = PaymentRequestService::createRequest([
'member_id' => (int) $memberId,
'amount' => $totalFee,
......@@ -97,6 +110,7 @@ class DeathController extends Controller
'related_entity_type' => 'death_cases',
'related_entity_id' => (int) $case->id,
'description_ar' => 'رسوم وفاة (استمارة + اشتراك) — حالة #' . $case->id,
'notes' => json_encode(['fee_breakdown' => $breakdown], JSON_UNESCAPED_UNICODE),
]);
if ($result['success']) {
......
......@@ -75,6 +75,17 @@ class DivorceController extends Controller
// Send payment to cashier queue
$amount = $feeCalc['total_fee'] ?? '0.00';
if (bccomp((string) $amount, '0', 2) > 0) {
$caseTypeLabel = DivorceCase::getCaseTypeLabel($feeCalc['case_type']);
$breakdown = [
'📋 نوع الحالة: ' . $caseTypeLabel,
'💰 قيمة العضوية: ' . money($feeCalc['membership_value']),
'📊 النسبة: ' . $feeCalc['fee_percentage'] . '%',
'💵 رسوم الطلاق: ' . money($feeCalc['fee_amount']),
'📝 رسوم استمارة نقل: ' . money($feeCalc['form_fee']),
'═══════════════════════════',
'💵 الإجمالي: ' . money($amount),
];
$result = PaymentRequestService::createRequest([
'member_id' => (int) $memberId,
'amount' => $amount,
......@@ -82,6 +93,7 @@ class DivorceController extends Controller
'related_entity_type' => 'divorce_cases',
'related_entity_id' => (int) $case->id,
'description_ar' => 'رسوم طلاق — حالة #' . $case->id,
'notes' => json_encode(['fee_breakdown' => $breakdown], JSON_UNESCAPED_UNICODE),
]);
if ($result['success']) {
......
......@@ -100,6 +100,10 @@ class MemberController extends Controller
if ($dob) { $age = age_from_dob($dob); $ageYears = $age['years']; $ageMonths = $age['months']; }
$idType = 'passport';
}
if ($ageYears !== null && $ageYears < 21) {
$errors[] = 'الحد الأدنى لسن العضوية العاملة 21 سنة (السن الحالي: ' . $ageYears . ')';
}
if (!empty($errors)) { $session = App::getInstance()->session(); $session->flash('_alerts', array_map(fn($e) => ['type' => 'error', 'message' => $e], $errors)); $session->flash('_old_input', $request->all()); return $this->redirect('/members/create'); }
$formNumber = MemberNumberGenerator::next();
......@@ -246,6 +250,15 @@ class MemberController extends Controller
return $this->redirect('/members/' . $id)->withError('بيانات غير صالحة');
}
// Block if a combined membership payment is already pending
$hasCombined = $db->selectOne(
"SELECT id FROM payment_requests WHERE member_id = ? AND payment_type IN ('membership_fee','down_payment') AND status IN ('pending','processing') AND is_voided = 0 LIMIT 1",
[(int) $id]
);
if ($hasCombined) {
return $this->redirect('/members/' . $id)->withError('يوجد طلب دفع مجمع في الخزينة بالفعل — الرسوم مشمولة فيه');
}
$entity = $db->selectOne("SELECT * FROM {$entityType} WHERE id = ? AND member_id = ? AND is_archived = 0", [$entityId, (int) $id]);
if (!$entity) return $this->redirect('/members/' . $id)->withError('العنصر غير موجود');
......@@ -260,6 +273,14 @@ class MemberController extends Controller
default => '',
};
$breakdown = [
'👤 ' . $typeLabel . ': ' . $nameLabel,
'📌 نوع الرسوم: رسوم إضافة تابع',
'💰 رسوم الإضافة: ' . money($fee),
'═══════════════════════════',
'💵 الإجمالي: ' . money($fee),
];
$result = PaymentRequestService::createRequest([
'member_id' => (int) $id,
'amount' => $fee,
......@@ -267,6 +288,7 @@ class MemberController extends Controller
'related_entity_type' => $entityType,
'related_entity_id' => $entityId,
'description_ar' => 'رسوم إضافة ' . $typeLabel . ' — ' . $nameLabel,
'notes' => json_encode(['fee_breakdown' => $breakdown], JSON_UNESCAPED_UNICODE),
]);
if (!$result['success']) return $this->redirect('/members/' . $id)->withError($result['error']);
......
This diff is collapsed.
......@@ -17,8 +17,9 @@ class Receipt
public static function findWithDetails(int $id): ?array
{
$db = App::getInstance()->db();
return $db->selectOne(
$receipt = $db->selectOne(
"SELECT r.*, m.full_name_ar as member_name, m.membership_number, m.phone_mobile,
m.form_number,
e.full_name_ar as issued_by_name, ve.full_name_ar as voided_by_name,
p.payment_type, p.payment_method
FROM receipts r
......@@ -29,6 +30,19 @@ class Receipt
WHERE r.id = ?",
[$id]
);
if (!$receipt) return null;
$pr = $db->selectOne(
"SELECT notes FROM payment_requests WHERE payment_id = ? AND is_voided = 0 LIMIT 1",
[(int) $receipt['payment_id']]
);
$receipt['fee_breakdown'] = null;
if ($pr && $pr['notes']) {
$decoded = json_decode($pr['notes'], true);
$receipt['fee_breakdown'] = $decoded['fee_breakdown'] ?? null;
}
return $receipt;
}
public static function generateNumber(): string
......
......@@ -56,17 +56,15 @@ $showFooter = $rd['show_footer_print_info'];
<td style="padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;">رقم العضوية</td>
<td style="padding:8px;border:1px solid #E5E7EB;"><?= e($receipt['membership_number'] ?? '—') ?></td>
</tr>
<?php if ($receipt['form_number'] ?? ''): ?>
<tr>
<td style="padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;">البيان</td>
<td style="padding:8px;border:1px solid #E5E7EB;"><?= e($receipt['description_ar'] ?? '—') ?></td>
</tr>
<tr>
<td style="padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;">المبلغ (رقماً)</td>
<td style="padding:8px;border:1px solid #E5E7EB;font-size:22px;font-weight:700;color:<?= e($headerColor) ?>;direction:ltr;text-align:right;"><?= money($receipt['amount']) ?></td>
<td style="padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;">رقم الاستمارة</td>
<td style="padding:8px;border:1px solid #E5E7EB;"><?= e($receipt['form_number']) ?></td>
</tr>
<?php endif; ?>
<tr>
<td style="padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;">المبلغ (كتابةً)</td>
<td style="padding:8px;border:1px solid #E5E7EB;font-size:13px;"><?= e($receipt['amount_in_words_ar'] ?? '') ?></td>
<td style="padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;">البيان</td>
<td style="padding:8px;border:1px solid #E5E7EB;"><?= e($receipt['description_ar'] ?? '—') ?></td>
</tr>
<?php if ($receipt['payment_type'] ?? ''): ?>
<tr>
......@@ -82,6 +80,60 @@ $showFooter = $rd['show_footer_print_info'];
<?php endif; ?>
</table>
<?php
$breakdown = $receipt['fee_breakdown'] ?? null;
if (is_array($breakdown) && !empty($breakdown)):
$detailItems = [];
$totalLine = '';
foreach ($breakdown as $bLine) {
if (str_contains($bLine, '═══')) continue;
if (str_starts_with($bLine, '💵')) { $totalLine = $bLine; continue; }
$detailItems[] = $bLine;
}
?>
<table style="width:100%;border-collapse:collapse;margin-bottom:15px;font-size:13px;">
<thead>
<tr style="background:#F9FAFB;">
<th style="padding:8px 10px;border:1px solid #E5E7EB;text-align:right;font-weight:700;color:<?= e($headerColor) ?>;" colspan="2">تفصيل المبلغ</th>
</tr>
</thead>
<tbody>
<?php foreach ($detailItems as $idx => $dItem): ?>
<tr>
<td style="padding:6px 10px;border:1px solid #E5E7EB;width:30px;text-align:center;color:#6B7280;font-size:12px;"><?= $idx + 1 ?></td>
<td style="padding:6px 10px;border:1px solid #E5E7EB;"><?= e($dItem) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr style="background:#F0FDF4;">
<td colspan="2" style="padding:10px;border:2px solid <?= e($headerColor) ?>;font-weight:700;font-size:16px;text-align:center;color:<?= e($headerColor) ?>;">
<?= e($totalLine ?: ('💵 الإجمالي: ' . money($receipt['amount']))) ?>
</td>
</tr>
</tfoot>
</table>
<?php else: ?>
<table style="width:100%;border-collapse:collapse;margin-bottom:15px;font-size:14px;">
<tr>
<td style="padding:10px;border:2px solid <?= e($headerColor) ?>;font-weight:700;font-size:22px;text-align:center;color:<?= e($headerColor) ?>;direction:ltr;">
<?= money($receipt['amount']) ?>
</td>
</tr>
</table>
<?php endif; ?>
<table style="width:100%;border-collapse:collapse;margin-bottom:20px;font-size:14px;">
<tr>
<td style="padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;width:30%;">المبلغ (رقماً)</td>
<td style="padding:8px;border:1px solid #E5E7EB;font-size:22px;font-weight:700;color:<?= e($headerColor) ?>;direction:ltr;text-align:right;"><?= money($receipt['amount']) ?></td>
</tr>
<tr>
<td style="padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;">المبلغ (كتابةً)</td>
<td style="padding:8px;border:1px solid #E5E7EB;font-size:13px;"><?= e($receipt['amount_in_words_ar'] ?? '') ?></td>
</tr>
</table>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:30px;margin-top:50px;text-align:center;font-size:13px;">
<div style="border-top:1px solid #000;padding-top:10px;">توقيع المستلم</div>
<div style="border-top:1px solid #000;padding-top:10px;">أمين الخزينة<br><small><?= e($receipt['issued_by_name'] ?? '') ?></small></div>
......
......@@ -36,7 +36,7 @@ class Spouse extends Model
{
$db = App::getInstance()->db();
$row = $db->selectOne(
"SELECT COUNT(*) as cnt FROM spouses WHERE member_id = ? AND is_archived = 0 AND status = 'active'",
"SELECT COUNT(*) as cnt FROM spouses WHERE member_id = ? AND is_archived = 0",
[$memberId]
);
return (int) ($row['cnt'] ?? 0);
......
......@@ -33,8 +33,9 @@ final class SpouseFeeCalculator
}
$membershipValue = $member['membership_value'] ?? '0.00';
$isOnInitialForm = FormFeeService::isOnInitialForm($member);
if (bccomp($membershipValue, '0.01', 2) < 0) {
if (bccomp($membershipValue, '0.01', 2) < 0 && !$isOnInitialForm) {
return self::error('يجب تحديد قيمة العضوية أولاً (ملء الاستمارة واختيار المؤهل)');
}
......
......@@ -18,7 +18,8 @@ final class TemporaryFeeCalculator
}
$membershipValue = $member['membership_value'] ?? '0.00';
if (bccomp($membershipValue, '0.00', 2) <= 0) {
$isOnInitialForm = FormFeeService::isOnInitialForm($member);
if (bccomp($membershipValue, '0.00', 2) <= 0 && !$isOnInitialForm) {
return ['fee' => '0.00', 'error' => 'قيمة العضوية غير محددة'];
}
......
......@@ -82,6 +82,16 @@ class WaiverController extends Controller
// Send payment to cashier queue
if (bccomp($waiverFee, '0', 2) > 0) {
$breakdown = [
'📋 نوع العملية: تنازل عن العضوية',
'💰 قيمة العضوية: ' . money($membershipValue),
'📊 نسبة رسوم التنازل: ' . $waiverPct . '%',
'💵 الرسوم: ' . $waiverPct . '% × ' . money($membershipValue) . ' = ' . money($waiverFee),
'👥 عدد التابعين: ' . ($spouseCount + $childCount + $tempCount),
'═══════════════════════════',
'💵 الإجمالي: ' . money($waiverFee),
];
$result = PaymentRequestService::createRequest([
'member_id' => (int) $memberId,
'amount' => $waiverFee,
......@@ -89,6 +99,7 @@ class WaiverController extends Controller
'related_entity_type' => 'waiver_requests',
'related_entity_id' => (int) $waiver->id,
'description_ar' => 'رسوم تنازل — طلب #' . $waiver->id,
'notes' => json_encode(['fee_breakdown' => $breakdown], JSON_UNESCAPED_UNICODE),
]);
if ($result['success']) {
......
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