Commit 419462ef authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add reconciliation wizard for participants with no invoices

SuperAdmin tool that finds enrolled participants with zero invoices
in the system, then offers two reconciliation paths:
- Two invoices: last month (auto-paid) + current month renewal
- One invoice: combined amount for both months

After reconciliation, participant is on the normal billing cycle.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent d35e8e40
<?php
namespace App\Livewire\Admin;
use App\Domain\Financial\Enums\InvoiceStatus;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Financial\Services\InvoiceService;
use App\Domain\Financial\Services\PaymentService;
use App\Domain\Participant\Models\Participant;
use App\Domain\Pricing\Services\PricingService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Enums\EnrollmentStatus;
use App\Domain\Training\Models\Enrollment;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('تسوية المشتركين')]
class ReconciliationWizard extends Component
{
public int $currentStep = 1;
// Step 1: list of participants with zero invoices
public array $selectedParticipantIds = [];
// Step 2: per-participant config
public ?int $activeParticipantId = null;
public array $participantConfigs = [];
// config structure: [participant_id => ['mode' => 'two_invoices'|'one_invoice', 'pay_now' => bool, 'payment_method' => 'cash']]
// Step 3: results
public array $results = [];
public bool $completed = false;
public function mount(): void
{
$this->authorize('super_admin.access');
}
public function selectAll(array $ids): void
{
$this->selectedParticipantIds = $ids;
}
public function toggleParticipant(int $id): void
{
if (in_array($id, $this->selectedParticipantIds)) {
$this->selectedParticipantIds = array_values(array_diff($this->selectedParticipantIds, [$id]));
} else {
$this->selectedParticipantIds[] = $id;
}
}
public function proceedToConfig(): void
{
if (empty($this->selectedParticipantIds)) {
session()->flash('error', __('يرجى اختيار مشترك واحد على الأقل'));
return;
}
foreach ($this->selectedParticipantIds as $id) {
if (!isset($this->participantConfigs[$id])) {
$this->participantConfigs[$id] = [
'mode' => 'two_invoices',
'pay_now' => true,
'payment_method' => 'cash',
];
}
}
$this->activeParticipantId = $this->selectedParticipantIds[0];
$this->currentStep = 2;
}
public function setActiveParticipant(int $id): void
{
$this->activeParticipantId = $id;
}
public function applyToAll(string $mode, bool $payNow, string $method): void
{
foreach ($this->selectedParticipantIds as $id) {
$this->participantConfigs[$id] = [
'mode' => $mode,
'pay_now' => $payNow,
'payment_method' => $method,
];
}
}
public function executeReconciliation(InvoiceService $invoiceService, PaymentService $paymentService, PricingService $pricingService): void
{
$this->authorize('super_admin.access');
$this->results = [];
$actor = auth()->user();
foreach ($this->selectedParticipantIds as $participantId) {
$config = $this->participantConfigs[$participantId] ?? ['mode' => 'two_invoices', 'pay_now' => true, 'payment_method' => 'cash'];
try {
$result = DB::transaction(function () use ($participantId, $config, $invoiceService, $paymentService, $pricingService, $actor) {
$participant = Participant::with('person')->find($participantId);
if (!$participant) {
throw new DomainException('المشترك غير موجود');
}
$enrollments = Enrollment::where('participant_id', $participantId)
->where('status', EnrollmentStatus::Active)
->with(['program', 'group'])
->get();
$invoicesCreated = [];
foreach ($enrollments as $enrollment) {
$program = $enrollment->program;
if (!$program) {
continue;
}
$branchId = $enrollment->group?->branch_id ?? $participant->branch_id;
try {
$priceResult = $pricingService->calculate(
priceable: $program,
participant: $participant,
branchId: $branchId,
);
} catch (DomainException $e) {
continue;
}
if ($priceResult->finalAmount <= 0) {
continue;
}
if ($config['mode'] === 'two_invoices') {
// Invoice 1: last month (already paid offline)
$lastMonthInvoice = $invoiceService->create([
'academy_id' => $enrollment->academy_id,
'branch_id' => $branchId,
'billable_type' => $participant->getMorphClass(),
'billable_id' => $participant->id,
'total_amount' => $priceResult->finalAmount,
'subtotal_amount' => $priceResult->baseAmount,
'discount_amount' => $priceResult->totalDiscount,
'tax_amount' => 0,
'issue_date' => now()->subMonth()->startOfMonth()->toDateString(),
'due_date' => now()->subMonth()->endOfMonth()->toDateString(),
'contact_name' => $participant->person?->name_ar,
'notes' => 'تسوية — اشتراك الشهر السابق: ' . $program->name_ar,
], [
[
'description' => "اشتراك الشهر السابق: {$program->name_ar}",
'quantity' => 1,
'unit_price' => $priceResult->baseAmount,
'discount_amount' => $priceResult->totalDiscount,
'tax_amount' => 0,
],
], $actor);
$lastMonthInvoice->update(['status' => InvoiceStatus::Sent]);
// Record payment for last month (paid offline)
$paymentService->recordPayment([
'invoice_id' => $lastMonthInvoice->id,
'branch_id' => $branchId,
'amount' => $lastMonthInvoice->total_amount,
'method' => $config['payment_method'],
'direction' => 'inbound',
'currency' => 'EGP',
'payment_date' => now()->subMonth()->toDateString(),
'notes' => 'تسوية — دفع سابق خارج النظام',
], $actor);
$invoicesCreated[] = ['invoice' => $lastMonthInvoice->fresh(), 'label' => 'الشهر السابق'];
// Invoice 2: current month renewal
$currentInvoice = $invoiceService->create([
'academy_id' => $enrollment->academy_id,
'branch_id' => $branchId,
'billable_type' => $participant->getMorphClass(),
'billable_id' => $participant->id,
'total_amount' => $priceResult->finalAmount,
'subtotal_amount' => $priceResult->baseAmount,
'discount_amount' => $priceResult->totalDiscount,
'tax_amount' => 0,
'due_date' => now()->addDays(7)->toDateString(),
'contact_name' => $participant->person?->name_ar,
'notes' => 'تجديد اشتراك — ' . $program->name_ar,
], [
[
'description' => "تجديد اشتراك: {$program->name_ar}",
'quantity' => 1,
'unit_price' => $priceResult->baseAmount,
'discount_amount' => $priceResult->totalDiscount,
'tax_amount' => 0,
],
], $actor);
$currentInvoice->update(['status' => InvoiceStatus::Sent]);
if ($config['pay_now']) {
$paymentService->recordPayment([
'invoice_id' => $currentInvoice->id,
'branch_id' => $branchId,
'amount' => $currentInvoice->total_amount,
'method' => $config['payment_method'],
'direction' => 'inbound',
'currency' => 'EGP',
'payment_date' => now()->toDateString(),
'notes' => 'تسوية — دفع تجديد الشهر الحالي',
], $actor);
}
$invoicesCreated[] = ['invoice' => $currentInvoice->fresh(), 'label' => 'الشهر الحالي'];
} else {
// One combined invoice for everything
$totalAmount = $priceResult->finalAmount * 2; // last month + current
$invoice = $invoiceService->create([
'academy_id' => $enrollment->academy_id,
'branch_id' => $branchId,
'billable_type' => $participant->getMorphClass(),
'billable_id' => $participant->id,
'total_amount' => $totalAmount,
'subtotal_amount' => $priceResult->baseAmount * 2,
'discount_amount' => $priceResult->totalDiscount * 2,
'tax_amount' => 0,
'due_date' => now()->addDays(7)->toDateString(),
'contact_name' => $participant->person?->name_ar,
'notes' => 'تسوية — اشتراك شهرين: ' . $program->name_ar,
], [
[
'description' => "اشتراك الشهر السابق: {$program->name_ar}",
'quantity' => 1,
'unit_price' => $priceResult->baseAmount,
'discount_amount' => $priceResult->totalDiscount,
'tax_amount' => 0,
],
[
'description' => "تجديد الشهر الحالي: {$program->name_ar}",
'quantity' => 1,
'unit_price' => $priceResult->baseAmount,
'discount_amount' => $priceResult->totalDiscount,
'tax_amount' => 0,
],
], $actor);
$invoice->update(['status' => InvoiceStatus::Sent]);
if ($config['pay_now']) {
$paymentService->recordPayment([
'invoice_id' => $invoice->id,
'branch_id' => $branchId,
'amount' => $invoice->total_amount,
'method' => $config['payment_method'],
'direction' => 'inbound',
'currency' => 'EGP',
'payment_date' => now()->toDateString(),
'notes' => 'تسوية — دفع كامل لشهرين',
], $actor);
}
$invoicesCreated[] = ['invoice' => $invoice->fresh(), 'label' => 'فاتورة موحدة'];
}
// Advance billing date so they're in the normal cycle going forward
$this->advanceBillingDate($enrollment);
}
return [
'participant_id' => $participantId,
'name' => $participant->person?->name_ar ?? '-',
'status' => 'success',
'invoices' => count($invoicesCreated),
'message' => 'تمت التسوية بنجاح',
];
});
$this->results[] = $result;
} catch (\Throwable $e) {
Log::error('Reconciliation failed', ['participant_id' => $participantId, 'error' => $e->getMessage()]);
$this->results[] = [
'participant_id' => $participantId,
'name' => Participant::with('person')->find($participantId)?->person?->name_ar ?? '-',
'status' => 'error',
'invoices' => 0,
'message' => $e->getMessage(),
];
}
}
$this->completed = true;
$this->currentStep = 3;
}
private function advanceBillingDate(Enrollment $enrollment): void
{
$program = $enrollment->program;
$billingDay = $program->billing_day ?? 1;
$next = now()->addMonth();
$maxDay = $next->daysInMonth;
$next->day = min($billingDay, $maxDay);
$enrollment->update([
'next_billing_date' => $next->toDateString(),
'last_billed_at' => now()->toDateString(),
'payment_status' => 'current',
]);
}
public function render()
{
$unpaidParticipants = collect();
if ($this->currentStep === 1) {
// Find participants who are enrolled (active) but have ZERO invoices in the system
$unpaidParticipants = Participant::query()
->whereHas('enrollments', fn ($q) => $q->where('status', EnrollmentStatus::Active))
->whereDoesntHave('invoices')
->with(['person', 'enrollments' => fn ($q) => $q->where('status', 'active')->with('program')])
->orderBy('id')
->get();
}
$configParticipants = collect();
if ($this->currentStep === 2) {
$configParticipants = Participant::whereIn('id', $this->selectedParticipantIds)
->with(['person', 'enrollments' => fn ($q) => $q->where('status', 'active')->with('program')])
->get()
->keyBy('id');
}
return view('livewire.admin.reconciliation-wizard', [
'unpaidParticipants' => $unpaidParticipants,
'configParticipants' => $configParticipants,
]);
}
}
<div class="max-w-6xl mx-auto py-6 px-4 sm:px-6 lg:px-8" dir="rtl">
{{-- Header --}}
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">تسوية المشتركين</h1>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
مشتركون مسجلون في برامج ولكن ليس لديهم أي فواتير في النظام
</p>
</div>
{{-- Progress Steps --}}
<div class="mb-8">
<div class="flex items-center justify-center gap-4">
@foreach ([1 => 'اختيار المشتركين', 2 => 'إعداد التسوية', 3 => 'النتائج'] as $step => $label)
<div class="flex items-center gap-2">
<span class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold {{ $currentStep >= $step ? 'bg-indigo-600 text-white' : 'bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-400' }}">
{{ $step }}
</span>
<span class="text-sm font-medium {{ $currentStep >= $step ? 'text-indigo-600 dark:text-indigo-400' : 'text-gray-500 dark:text-gray-400' }}">
{{ $label }}
</span>
</div>
@if ($step < 3)
<div class="w-12 h-0.5 {{ $currentStep > $step ? 'bg-indigo-600' : 'bg-gray-200 dark:bg-gray-700' }}"></div>
@endif
@endforeach
</div>
</div>
{{-- Flash messages --}}
@if (session('error'))
<div class="mb-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg text-red-700 dark:text-red-300 text-sm">
{{ session('error') }}
</div>
@endif
@if (session('success'))
<div class="mb-4 p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg text-green-700 dark:text-green-300 text-sm">
{{ session('success') }}
</div>
@endif
{{-- Step 1: Select Participants --}}
@if ($currentStep === 1)
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700">
<div class="p-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
مشتركون بدون فواتير ({{ $unpaidParticipants->count() }})
</h2>
@if ($unpaidParticipants->isNotEmpty())
<button
wire:click="selectAll({{ json_encode($unpaidParticipants->pluck('id')->toArray()) }})"
class="text-sm text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 font-medium"
>
تحديد الكل
</button>
@endif
</div>
@if ($unpaidParticipants->isEmpty())
<div class="p-12 text-center">
<svg class="mx-auto h-12 w-12 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<h3 class="mt-3 text-lg font-medium text-gray-900 dark:text-white">لا يوجد مشتركون بحاجة لتسوية</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">جميع المشتركين المسجلين لديهم فواتير في النظام</p>
</div>
@else
<div class="divide-y divide-gray-200 dark:divide-gray-700 max-h-[500px] overflow-y-auto">
@foreach ($unpaidParticipants as $participant)
<label class="flex items-center gap-4 p-4 hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer transition">
<input
type="checkbox"
wire:click="toggleParticipant({{ $participant->id }})"
@checked(in_array($participant->id, $selectedParticipantIds))
class="w-5 h-5 text-indigo-600 rounded border-gray-300 dark:border-gray-600 focus:ring-indigo-500"
>
<div class="flex-1 min-w-0">
<p class="text-sm font-semibold text-gray-900 dark:text-white truncate">
{{ $participant->person?->name_ar ?? '-' }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ $participant->participant_number ?? '' }}
</p>
</div>
<div class="text-end">
@foreach ($participant->enrollments as $enrollment)
<span class="inline-block text-xs bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 px-2 py-0.5 rounded-full mb-1">
{{ $enrollment->program?->name_ar ?? '-' }}
</span>
@endforeach
</div>
</label>
@endforeach
</div>
<div class="p-4 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">
تم تحديد {{ count($selectedParticipantIds) }} من {{ $unpaidParticipants->count() }}
</span>
<button
wire:click="proceedToConfig"
@disabled(empty($selectedParticipantIds))
class="px-6 py-2.5 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed transition"
>
التالي — إعداد التسوية
</button>
</div>
@endif
</div>
@endif
{{-- Step 2: Configure reconciliation --}}
@if ($currentStep === 2)
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
{{-- Sidebar: participant list --}}
<div class="lg:col-span-1">
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 sticky top-4">
<div class="p-4 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">المشتركون المحددون ({{ count($selectedParticipantIds) }})</h3>
</div>
<div class="divide-y divide-gray-100 dark:divide-gray-700 max-h-[400px] overflow-y-auto">
@foreach ($configParticipants as $participant)
<button
wire:click="setActiveParticipant({{ $participant->id }})"
class="w-full text-start p-3 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition {{ $activeParticipantId === $participant->id ? 'bg-indigo-50 dark:bg-indigo-900/20 border-s-4 border-indigo-500' : '' }}"
>
<p class="text-sm font-medium text-gray-900 dark:text-white truncate">
{{ $participant->person?->name_ar ?? '-' }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
@php $cfg = $participantConfigs[$participant->id] ?? []; @endphp
{{ ($cfg['mode'] ?? 'two_invoices') === 'two_invoices' ? 'فاتورتين' : 'فاتورة واحدة' }}
— {{ ($cfg['pay_now'] ?? true) ? 'دفع فوري' : 'بدون دفع' }}
</p>
</button>
@endforeach
</div>
</div>
</div>
{{-- Main: configuration form --}}
<div class="lg:col-span-2">
@if ($activeParticipantId && isset($configParticipants[$activeParticipantId]))
@php
$activeParticipant = $configParticipants[$activeParticipantId];
$cfg = $participantConfigs[$activeParticipantId] ?? ['mode' => 'two_invoices', 'pay_now' => true, 'payment_method' => 'cash'];
@endphp
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700">
<div class="p-5 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
{{ $activeParticipant->person?->name_ar ?? '-' }}
</h3>
<div class="mt-2 flex flex-wrap gap-2">
@foreach ($activeParticipant->enrollments as $enrollment)
<span class="text-xs bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 px-2 py-1 rounded-full">
{{ $enrollment->program?->name_ar ?? '-' }}
</span>
@endforeach
</div>
</div>
<div class="p-5 space-y-6">
{{-- Mode selection --}}
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">نوع التسوية</label>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<label class="relative flex items-start p-4 border-2 rounded-xl cursor-pointer transition {{ ($cfg['mode'] ?? '') === 'two_invoices' ? 'border-indigo-500 bg-indigo-50 dark:bg-indigo-900/20' : 'border-gray-200 dark:border-gray-600 hover:border-gray-300' }}">
<input
type="radio"
wire:model.live="participantConfigs.{{ $activeParticipantId }}.mode"
value="two_invoices"
class="mt-0.5 text-indigo-600"
>
<div class="ms-3">
<span class="block text-sm font-semibold text-gray-900 dark:text-white">فاتورتين منفصلتين</span>
<span class="block text-xs text-gray-500 dark:text-gray-400 mt-1">فاتورة الشهر الماضي (مدفوعة) + فاتورة تجديد هذا الشهر</span>
</div>
</label>
<label class="relative flex items-start p-4 border-2 rounded-xl cursor-pointer transition {{ ($cfg['mode'] ?? '') === 'one_invoice' ? 'border-indigo-500 bg-indigo-50 dark:bg-indigo-900/20' : 'border-gray-200 dark:border-gray-600 hover:border-gray-300' }}">
<input
type="radio"
wire:model.live="participantConfigs.{{ $activeParticipantId }}.mode"
value="one_invoice"
class="mt-0.5 text-indigo-600"
>
<div class="ms-3">
<span class="block text-sm font-semibold text-gray-900 dark:text-white">فاتورة واحدة مجمعة</span>
<span class="block text-xs text-gray-500 dark:text-gray-400 mt-1">فاتورة واحدة بمبلغ شهرين يدفعها الآن</span>
</div>
</label>
</div>
</div>
{{-- Pay now --}}
<div>
<label class="flex items-center gap-3">
<input
type="checkbox"
wire:model.live="participantConfigs.{{ $activeParticipantId }}.pay_now"
class="w-5 h-5 text-indigo-600 rounded border-gray-300 dark:border-gray-600"
>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
تسجيل الدفع فورًا (الشهر الحالي)
</span>
</label>
<p class="mt-1 ms-8 text-xs text-gray-500 dark:text-gray-400">
إذا كان المشترك سيدفع الآن، سيتم تسجيل الدفع مباشرة. وإلا ستبقى الفاتورة مستحقة.
</p>
</div>
{{-- Payment method --}}
@if ($cfg['pay_now'] ?? true)
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">طريقة الدفع</label>
<select
wire:model.live="participantConfigs.{{ $activeParticipantId }}.payment_method"
class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 text-sm"
>
<option value="cash">نقدي</option>
<option value="card">بطاقة</option>
<option value="bank_transfer">تحويل بنكي</option>
<option value="wallet">محفظة</option>
<option value="online">إلكتروني</option>
<option value="cheque">شيك</option>
<option value="other">أخرى</option>
</select>
</div>
@endif
{{-- Apply to all --}}
<div class="pt-4 border-t border-gray-200 dark:border-gray-700">
<button
wire:click="applyToAll('{{ $cfg['mode'] ?? 'two_invoices' }}', {{ ($cfg['pay_now'] ?? true) ? 'true' : 'false' }}, '{{ $cfg['payment_method'] ?? 'cash' }}')"
class="text-sm text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 font-medium"
>
تطبيق هذا الإعداد على جميع المشتركين المحددين
</button>
</div>
</div>
</div>
@endif
{{-- Action buttons --}}
<div class="mt-6 flex items-center justify-between">
<button
wire:click="$set('currentStep', 1)"
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition"
>
رجوع
</button>
<button
wire:click="executeReconciliation"
wire:loading.attr="disabled"
wire:confirm="هل أنت متأكد من تنفيذ التسوية لـ {{ count($selectedParticipantIds) }} مشترك؟ سيتم إنشاء الفواتير وتسجيل المدفوعات."
class="px-6 py-2.5 bg-green-600 text-white text-sm font-bold rounded-lg hover:bg-green-700 disabled:opacity-50 transition"
>
<span wire:loading.remove wire:target="executeReconciliation">
تنفيذ التسوية ({{ count($selectedParticipantIds) }} مشترك)
</span>
<span wire:loading wire:target="executeReconciliation">
جارٍ التنفيذ...
</span>
</button>
</div>
</div>
</div>
@endif
{{-- Step 3: Results --}}
@if ($currentStep === 3)
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700">
<div class="p-5 border-b border-gray-200 dark:border-gray-700">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">نتائج التسوية</h2>
</div>
<div class="divide-y divide-gray-200 dark:divide-gray-700">
@foreach ($results as $result)
<div class="p-4 flex items-center justify-between">
<div class="flex items-center gap-3">
@if ($result['status'] === 'success')
<span class="w-8 h-8 rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center">
<svg class="w-5 h-5 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
</span>
@else
<span class="w-8 h-8 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
<svg class="w-5 h-5 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</span>
@endif
<div>
<p class="text-sm font-semibold text-gray-900 dark:text-white">{{ $result['name'] }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">{{ $result['message'] }}</p>
</div>
</div>
@if ($result['status'] === 'success')
<span class="text-xs bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 px-2 py-1 rounded-full">
{{ $result['invoices'] }} {{ $result['invoices'] > 1 ? 'فواتير' : 'فاتورة' }}
</span>
@endif
</div>
@endforeach
</div>
<div class="p-5 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between">
<div class="text-sm text-gray-600 dark:text-gray-400">
@php
$successCount = collect($results)->where('status', 'success')->count();
$failCount = collect($results)->where('status', 'error')->count();
@endphp
نجاح: {{ $successCount }} — فشل: {{ $failCount }}
</div>
<a
href="{{ route('admin.panel') }}"
class="px-4 py-2 text-sm font-medium text-indigo-600 dark:text-indigo-400 border border-indigo-300 dark:border-indigo-700 rounded-lg hover:bg-indigo-50 dark:hover:bg-indigo-900/20 transition"
>
العودة للوحة التحكم
</a>
</div>
</div>
@endif
</div>
...@@ -5,6 +5,14 @@ ...@@ -5,6 +5,14 @@
<p class="mt-1 text-sm text-gray-500">{{ __('نظرة عامة على جميع الأكاديميات') }}</p> <p class="mt-1 text-sm text-gray-500">{{ __('نظرة عامة على جميع الأكاديميات') }}</p>
</div> </div>
<!-- Quick Actions -->
<div class="mb-6 flex flex-wrap gap-3">
<a href="{{ route('admin.reconciliation') }}" class="inline-flex items-center gap-2 px-4 py-2 bg-amber-50 border border-amber-200 text-amber-800 text-sm font-medium rounded-lg hover:bg-amber-100 transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z"/></svg>
{{ __('تسوية المشتركين') }}
</a>
</div>
<!-- Stats Cards --> <!-- Stats Cards -->
<div class="grid grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4 mb-6 sm:mb-8"> <div class="grid grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4 mb-6 sm:mb-8">
<!-- Total Academies --> <!-- Total Academies -->
......
...@@ -490,6 +490,8 @@ ...@@ -490,6 +490,8 @@
// SuperAdmin // SuperAdmin
Route::get('/admin', SuperAdminPanel::class)->name('admin.panel') Route::get('/admin', SuperAdminPanel::class)->name('admin.panel')
->middleware('permission:super_admin.access'); ->middleware('permission:super_admin.access');
Route::get('/admin/reconciliation', \App\Livewire\Admin\ReconciliationWizard::class)->name('admin.reconciliation')
->middleware('permission:super_admin.access');
// Exports // Exports
Route::get('/export/report', [\App\Http\Controllers\ExportController::class, 'report']) Route::get('/export/report', [\App\Http\Controllers\ExportController::class, 'report'])
......
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