Commit fb56f022 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add 4 dashboard/financial features: compact renewals, retroactive wizard fix,...

Add 4 dashboard/financial features: compact renewals, retroactive wizard fix, club revenue, expense shortcuts

- Compact overdue renewals alert into summary card with toggle detail list
- Rewrite retroactive enrollment wizard to CREATE new participants (not search existing)
- Add external revenue import form for club lump-sum payments
- Add quick-action buttons to financial overview for expenses/revenue
- Migration adds external_revenue to expenses category CHECK constraint
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 172a4836
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
case Medical = 'medical'; case Medical = 'medical';
case Utilities = 'utilities'; case Utilities = 'utilities';
case Other = 'other'; case Other = 'other';
case ExternalRevenue = 'external_revenue';
public function label(): string public function label(): string
{ {
...@@ -28,6 +29,7 @@ public function label(): string ...@@ -28,6 +29,7 @@ public function label(): string
self::Medical => 'طبي', self::Medical => 'طبي',
self::Utilities => 'مرافق وخدمات', self::Utilities => 'مرافق وخدمات',
self::Other => 'أخرى', self::Other => 'أخرى',
self::ExternalRevenue => 'إيراد خارجي',
}; };
} }
} }
...@@ -85,6 +85,64 @@ public function recordExpense(array $data, User $actor): Expense ...@@ -85,6 +85,64 @@ public function recordExpense(array $data, User $actor): Expense
}); });
} }
public function recordExternalRevenue(array $data, User $actor): Expense
{
return DB::transaction(function () use ($data, $actor) {
$academyId = app('current_academy')?->id ?? $actor->academy_id;
$expense = Expense::create([
'academy_id' => $academyId,
'branch_id' => $data['branch_id'] ?? null,
'category' => 'external_revenue',
'amount' => $data['amount'],
'description' => $data['description'],
'recipient_name' => $data['source'] ?? null,
'payment_method' => $data['payment_method'],
'receipt_reference' => $data['reference_number'] ?? null,
'expense_date' => $data['revenue_date'],
'notes' => $data['notes'] ?? null,
'created_by' => $actor->id,
]);
$revenueAccount = FinancialAccount::where('academy_id', $academyId)
->where('code', '4060')
->first();
if (!$revenueAccount) {
throw new DomainException('حساب الإيرادات الخارجية غير موجود — يرجى إعداد شجرة الحسابات');
}
$cashAccountCode = match ($data['payment_method']) {
'bank_transfer', 'cheque' => '1010',
default => '1000',
};
$cashAccount = FinancialAccount::where('academy_id', $academyId)
->where('code', $cashAccountCode)
->first();
if (!$cashAccount) {
throw new DomainException('حساب النقدية/البنك غير موجود — يرجى إعداد شجرة الحسابات');
}
Transaction::create([
'academy_id' => $academyId,
'debit_account_id' => $cashAccount->id,
'credit_account_id' => $revenueAccount->id,
'reference_type' => get_class($expense),
'reference_id' => $expense->id,
'amount' => $data['amount'],
'currency' => 'EGP',
'type' => TransactionType::PaymentReceived,
'description' => $data['description'],
'transaction_date' => $data['revenue_date'],
'created_by' => $actor->id,
]);
return $expense;
});
}
private function createExpenseTransaction( private function createExpenseTransaction(
int $academyId, int $academyId,
string $expenseAccountCode, string $expenseAccountCode,
......
...@@ -12,18 +12,17 @@ ...@@ -12,18 +12,17 @@
class OverdueRenewalsAlert extends Component class OverdueRenewalsAlert extends Component
{ {
public bool $expanded = false; public bool $showList = false;
public function toggleExpanded(): void public function toggleList(): void
{ {
$this->expanded = !$this->expanded; $this->showList = !$this->showList;
} }
public function render() public function render()
{ {
$today = now()->toDateString(); $today = now()->toDateString();
// 1. Participants with unpaid renewal invoices (already billed, not yet paid)
$unpaidInvoices = Invoice::whereIn('status', [InvoiceStatus::Sent, InvoiceStatus::PartiallyPaid, InvoiceStatus::Overdue]) $unpaidInvoices = Invoice::whereIn('status', [InvoiceStatus::Sent, InvoiceStatus::PartiallyPaid, InvoiceStatus::Overdue])
->where('due_amount', '>', 0) ->where('due_amount', '>', 0)
->where('billable_type', Participant::class) ->where('billable_type', Participant::class)
...@@ -32,7 +31,6 @@ public function render() ...@@ -32,7 +31,6 @@ public function render()
->orderBy('due_date') ->orderBy('due_date')
->get(); ->get();
// 2. Also find enrollments overdue but NO invoice generated yet
$overdueEnrollments = Enrollment::where('status', EnrollmentStatus::Active) $overdueEnrollments = Enrollment::where('status', EnrollmentStatus::Active)
->whereNotNull('next_billing_date') ->whereNotNull('next_billing_date')
->where('next_billing_date', '<=', $today) ->where('next_billing_date', '<=', $today)
...@@ -54,46 +52,50 @@ public function render() ...@@ -54,46 +52,50 @@ public function render()
->exists(); ->exists();
}); });
// Build unified list $invoiceCount = $unpaidInvoices->count();
$enrollmentCount = $overdueEnrollments->count();
$totalOverdue = $invoiceCount + $enrollmentCount;
$totalAmount = $unpaidInvoices->sum('due_amount');
$items = collect(); $items = collect();
if ($this->showList) {
foreach ($unpaidInvoices as $invoice) {
$participant = $invoice->billable;
if (!$participant) {
continue;
}
$items->push([
'participant_name' => $participant->person?->name_ar ?? $invoice->contact_name ?? '-',
'participant_phone' => $participant->person?->phone ?? null,
'participant_uuid' => $participant->uuid,
'amount' => $invoice->due_amount,
'program_name' => $this->extractProgramName($invoice->notes),
'days_overdue' => $invoice->due_date?->isPast() ? (int) now()->diffInDays($invoice->due_date) : 0,
'type' => 'invoice',
]);
}
foreach ($unpaidInvoices as $invoice) { foreach ($overdueEnrollments as $enrollment) {
$participant = $invoice->billable; $items->push([
if (!$participant) { 'participant_name' => $enrollment->participant?->person?->name_ar ?? '-',
continue; 'participant_phone' => $enrollment->participant?->person?->phone ?? null,
'participant_uuid' => $enrollment->participant?->uuid,
'amount' => null,
'program_name' => $enrollment->program?->name_ar ?? '-',
'days_overdue' => (int) now()->diffInDays($enrollment->next_billing_date),
'type' => 'no_invoice',
]);
} }
$items->push([
'participant_name' => $participant->person?->name_ar ?? $invoice->contact_name ?? '-',
'participant_phone' => $participant->person?->phone ?? null,
'participant_uuid' => $participant->uuid,
'amount' => $invoice->due_amount,
'program_name' => $this->extractProgramName($invoice->notes),
'days_overdue' => $invoice->due_date?->isPast() ? (int) now()->diffInDays($invoice->due_date) : 0,
'type' => 'invoice',
]);
}
foreach ($overdueEnrollments as $enrollment) { $items = $items->sortByDesc('days_overdue')->values();
$items->push([
'participant_name' => $enrollment->participant?->person?->name_ar ?? '-',
'participant_phone' => $enrollment->participant?->person?->phone ?? null,
'participant_uuid' => $enrollment->participant?->uuid,
'amount' => null,
'program_name' => $enrollment->program?->name_ar ?? '-',
'days_overdue' => (int) now()->diffInDays($enrollment->next_billing_date),
'type' => 'no_invoice',
]);
} }
$items = $items->sortByDesc('days_overdue')->values();
$totalOverdue = $items->count();
$displayList = $this->expanded ? $items : $items->take(15);
$totalAmount = $unpaidInvoices->sum('due_amount');
return view('livewire.dashboard.overdue-renewals-alert', [ return view('livewire.dashboard.overdue-renewals-alert', [
'items' => $displayList, 'items' => $items,
'totalOverdue' => $totalOverdue, 'totalOverdue' => $totalOverdue,
'totalAmount' => $totalAmount, 'totalAmount' => $totalAmount,
'invoiceCount' => $invoiceCount,
'enrollmentCount' => $enrollmentCount,
]); ]);
} }
......
<?php
namespace App\Livewire\Financial;
use App\Domain\Financial\Services\ExpenseService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Traits\UsesBranchScope;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('تسجيل إيراد خارجي')]
class ExternalRevenueForm extends Component
{
use UsesBranchScope;
public string $source = '';
public string $amount_display = '';
public string $description = '';
public string $payment_method = 'cash';
public string $reference_number = '';
public ?string $revenue_date = null;
public string $notes = '';
public function mount(): void
{
$this->authorize('expenses.create');
$this->revenue_date = now()->toDateString();
}
public function rules(): array
{
return [
'source' => 'required|string|max:255',
'amount_display' => 'required|numeric|min:0.01',
'description' => 'required|string|max:500',
'payment_method' => 'required|in:cash,card,bank_transfer,wallet,online,cheque,other',
'reference_number' => 'nullable|string|max:100',
'revenue_date' => 'required|date',
'notes' => 'nullable|string',
];
}
public function messages(): array
{
return [
'source.required' => 'مصدر الإيراد مطلوب',
'amount_display.required' => 'المبلغ مطلوب',
'amount_display.min' => 'المبلغ يجب أن يكون أكبر من صفر',
'description.required' => 'وصف الإيراد مطلوب',
'payment_method.required' => 'اختر طريقة الاستلام',
'revenue_date.required' => 'تاريخ الإيراد مطلوب',
];
}
public function save(ExpenseService $service): void
{
$this->validate();
try {
$service->recordExternalRevenue([
'branch_id' => $this->getActiveBranchId(),
'source' => $this->source,
'amount' => (int) round((float) $this->amount_display * 100),
'description' => $this->description,
'payment_method' => $this->payment_method,
'reference_number' => $this->reference_number ?: null,
'revenue_date' => $this->revenue_date,
'notes' => $this->notes ?: null,
], auth()->user());
session()->flash('success', __('تم تسجيل الإيراد الخارجي بنجاح'));
$this->redirect(route('financial.overview'), navigate: true);
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
public function render()
{
return view('livewire.financial.external-revenue-form');
}
}
...@@ -3,11 +3,14 @@ ...@@ -3,11 +3,14 @@
namespace App\Livewire\Receptionist; namespace App\Livewire\Receptionist;
use App\Domain\Financial\Enums\InvoiceStatus; use App\Domain\Financial\Enums\InvoiceStatus;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Financial\Services\InvoiceService; use App\Domain\Financial\Services\InvoiceService;
use App\Domain\Financial\Services\PaymentService; use App\Domain\Financial\Services\PaymentService;
use App\Domain\Identity\Models\Branch; use App\Domain\Identity\Models\Branch;
use App\Domain\Participant\Models\Participant; use App\Domain\Identity\Models\Guardian;
use App\Domain\Identity\Models\Person;
use App\Domain\Identity\Services\EgyptianNidDecoder;
use App\Domain\Identity\Services\PersonService;
use App\Domain\Participant\Services\ParticipantService;
use App\Domain\Pricing\Services\PricingService; use App\Domain\Pricing\Services\PricingService;
use App\Domain\Shared\Exceptions\DomainException; use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Services\PlatformFeeService; use App\Domain\Shared\Services\PlatformFeeService;
...@@ -16,7 +19,6 @@ ...@@ -16,7 +19,6 @@
use App\Domain\Training\Models\TrainingProgram; use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\EnrollmentService; use App\Domain\Training\Services\EnrollmentService;
use Carbon\Carbon; use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Livewire\Attributes\Computed; use Livewire\Attributes\Computed;
...@@ -33,27 +35,36 @@ class RetroactiveEnrollmentWizard extends Component ...@@ -33,27 +35,36 @@ class RetroactiveEnrollmentWizard extends Component
public ?int $branchId = null; public ?int $branchId = null;
public int $currentStep = 1; public int $currentStep = 1;
public int $totalSteps = 5; public int $totalSteps = 6;
// Step 1: Search existing participant // Step 1: Participant info (create new)
public string $search = ''; public string $participant_name_ar = '';
public ?int $selected_participant_id = null; public ?string $participant_date_of_birth = null;
public ?string $selected_participant_name = null; public string $participant_gender = 'male';
public string $participant_phone = '';
// Step 2: Program selection public string $participant_national_id = '';
public string $participant_medical_notes = '';
public bool $participant_nid_decoded = false;
// Step 2: Guardian info
public string $guardian_name_ar = '';
public string $guardian_phone = '';
public string $guardian_relation = 'father';
// Step 3: Program selection
public ?int $selected_activity_id = null; public ?int $selected_activity_id = null;
public ?int $selected_program_id = null; public ?int $selected_program_id = null;
// Step 3: Retroactive dates + months owed // Step 4: Retroactive dates + months owed
public string $actual_start_date = ''; public string $actual_start_date = '';
public array $monthStatuses = []; // ['2026-05' => 'unpaid', '2026-06' => 'paid_outside', ...] public array $monthStatuses = [];
// Admin override // Admin override
public bool $priceOverrideEnabled = false; public bool $priceOverrideEnabled = false;
public string $priceOverrideInput = ''; public string $priceOverrideInput = '';
public string $priceOverrideReason = ''; public string $priceOverrideReason = '';
// Step 4: Payment for outstanding months // Step 5: Payment for outstanding months
public bool $pay_now = false; public bool $pay_now = false;
public string $payment_method = 'cash'; public string $payment_method = 'cash';
public string $payment_notes = ''; public string $payment_notes = '';
...@@ -64,6 +75,7 @@ class RetroactiveEnrollmentWizard extends Component ...@@ -64,6 +75,7 @@ class RetroactiveEnrollmentWizard extends Component
public int $invoices_created = 0; public int $invoices_created = 0;
public int $payments_recorded = 0; public int $payments_recorded = 0;
public ?string $participant_uuid = null; public ?string $participant_uuid = null;
public ?string $participant_number = null;
public function mount(): void public function mount(): void
{ {
...@@ -74,13 +86,24 @@ public function mount(): void ...@@ -74,13 +86,24 @@ public function mount(): void
public function rules(): array public function rules(): array
{ {
return match ($this->currentStep) { return match ($this->currentStep) {
1 => ['selected_participant_id' => 'required|exists:participants,id'], 1 => [
'participant_name_ar' => 'required|string|min:3|max:100',
'participant_date_of_birth' => 'nullable|date|before:today',
'participant_gender' => 'required|in:male,female',
'participant_phone' => 'nullable|string|max:20',
'participant_national_id' => 'nullable|string|size:14',
],
2 => [ 2 => [
'guardian_name_ar' => 'required|string|min:3|max:100',
'guardian_phone' => 'required|string|min:10|max:20',
'guardian_relation' => 'required|in:father,mother,brother,sister,uncle,aunt,grandfather,grandmother,other',
],
3 => [
'selected_activity_id' => 'required|exists:activities,id', 'selected_activity_id' => 'required|exists:activities,id',
'selected_program_id' => 'required|exists:training_programs,id', 'selected_program_id' => 'required|exists:training_programs,id',
], ],
3 => ['actual_start_date' => 'required|date|before_or_equal:today'], 4 => ['actual_start_date' => 'required|date|before_or_equal:today'],
4 => ['payment_method' => 'required_if:pay_now,true|in:cash,card,bank_transfer,wallet,online,cheque,other'], 5 => ['payment_method' => 'required_if:pay_now,true|in:cash,card,bank_transfer,wallet,online,cheque,other'],
default => [], default => [],
}; };
} }
...@@ -88,7 +111,13 @@ public function rules(): array ...@@ -88,7 +111,13 @@ public function rules(): array
public function messages(): array public function messages(): array
{ {
return [ return [
'selected_participant_id.required' => 'يرجى اختيار المشترك', 'participant_name_ar.required' => 'يرجى إدخال اسم اللاعب',
'participant_name_ar.min' => 'اسم اللاعب قصير جداً',
'participant_date_of_birth.before' => 'تاريخ الميلاد غير صحيح',
'participant_national_id.size' => 'الرقم القومي يجب أن يكون 14 رقماً',
'guardian_name_ar.required' => 'يرجى إدخال اسم ولي الأمر',
'guardian_phone.required' => 'يرجى إدخال هاتف ولي الأمر',
'guardian_phone.min' => 'رقم الهاتف قصير جداً',
'selected_activity_id.required' => 'يرجى اختيار النشاط', 'selected_activity_id.required' => 'يرجى اختيار النشاط',
'selected_program_id.required' => 'يرجى اختيار البرنامج', 'selected_program_id.required' => 'يرجى اختيار البرنامج',
'actual_start_date.required' => 'يرجى تحديد تاريخ البدء الفعلي', 'actual_start_date.required' => 'يرجى تحديد تاريخ البدء الفعلي',
...@@ -96,10 +125,23 @@ public function messages(): array ...@@ -96,10 +125,23 @@ public function messages(): array
]; ];
} }
public function selectParticipant(int $id, string $name): void public function updatedParticipantNationalId(): void
{ {
$this->selected_participant_id = $id; $nid = trim($this->participant_national_id);
$this->selected_participant_name = $name; if (strlen($nid) !== 14) {
$this->participant_nid_decoded = false;
return;
}
try {
$decoder = app(EgyptianNidDecoder::class);
$info = $decoder->decode($nid);
$this->participant_date_of_birth = $info['date_of_birth'];
$this->participant_gender = $info['gender'];
$this->participant_nid_decoded = true;
} catch (\Throwable) {
$this->participant_nid_decoded = false;
}
} }
public function updatedSelectedActivityId(): void public function updatedSelectedActivityId(): void
...@@ -130,14 +172,12 @@ public function calculateMonthsOwed(): void ...@@ -130,14 +172,12 @@ public function calculateMonthsOwed(): void
$current->addMonth(); $current->addMonth();
} }
// Preserve existing selections
foreach ($months as $key => $default) { foreach ($months as $key => $default) {
if (!isset($this->monthStatuses[$key])) { if (!isset($this->monthStatuses[$key])) {
$this->monthStatuses[$key] = $default; $this->monthStatuses[$key] = $default;
} }
} }
// Remove months that are no longer in range
$this->monthStatuses = array_intersect_key($this->monthStatuses, $months); $this->monthStatuses = array_intersect_key($this->monthStatuses, $months);
} }
...@@ -151,19 +191,18 @@ public function setMonthStatus(string $month, string $status): void ...@@ -151,19 +191,18 @@ public function setMonthStatus(string $month, string $status): void
#[Computed] #[Computed]
public function monthlyPrice(): int public function monthlyPrice(): int
{ {
if (!$this->selected_participant_id || !$this->selected_program_id) { if (!$this->selected_program_id) {
return 0; return 0;
} }
try { try {
$participant = Participant::find($this->selected_participant_id);
$program = TrainingProgram::find($this->selected_program_id); $program = TrainingProgram::find($this->selected_program_id);
if (!$participant || !$program) return 0; if (!$program) return 0;
$pricingService = app(PricingService::class); $pricingService = app(PricingService::class);
$result = $pricingService->calculate( $result = $pricingService->calculate(
priceable: $program, priceable: $program,
participant: $participant, participant: null,
branchId: $this->branchId, branchId: $this->branchId,
); );
return $result->finalAmount; return $result->finalAmount;
...@@ -191,7 +230,7 @@ public function nextStep(): void ...@@ -191,7 +230,7 @@ public function nextStep(): void
{ {
$this->validate(); $this->validate();
if ($this->currentStep === 2) { if ($this->currentStep === 4) {
$this->calculateMonthsOwed(); $this->calculateMonthsOwed();
} }
...@@ -216,13 +255,66 @@ public function confirm(): void ...@@ -216,13 +255,66 @@ public function confirm(): void
try { try {
DB::transaction(function () use ($actor) { DB::transaction(function () use ($actor) {
$participant = Participant::findOrFail($this->selected_participant_id); $personService = app(PersonService::class);
$program = TrainingProgram::findOrFail($this->selected_program_id); $participantService = app(ParticipantService::class);
$enrollmentService = app(EnrollmentService::class); $enrollmentService = app(EnrollmentService::class);
$invoiceService = app(InvoiceService::class); $invoiceService = app(InvoiceService::class);
$paymentService = app(PaymentService::class); $paymentService = app(PaymentService::class);
// 1. Create enrollment with retroactive start date // 1. Create guardian Person (or find by phone)
$guardianPerson = Person::where('phone', $this->guardian_phone)->first();
if (!$guardianPerson) {
$guardianPerson = $personService->create([
'name_ar' => $this->guardian_name_ar,
'name' => $this->guardian_name_ar,
'phone' => $this->guardian_phone,
], $actor);
}
// 2. Create or find Guardian record
$guardian = Guardian::firstOrCreate(
['person_id' => $guardianPerson->id],
[
'academy_id' => app('current_academy')->id,
'relationship_type' => $this->guardian_relation,
'is_emergency_contact' => true,
'is_financial_responsible' => true,
'can_pickup' => true,
]
);
// 3. Create participant Person record
$participantPerson = $personService->create([
'name_ar' => $this->participant_name_ar,
'name' => $this->participant_name_ar,
'date_of_birth' => $this->participant_date_of_birth,
'gender' => $this->participant_gender,
'phone' => $this->participant_phone ?: null,
'national_id' => $this->participant_national_id ?: null,
'medical_notes' => $this->participant_medical_notes ?: null,
], $actor);
// 4. Create Participant record
$participant = $participantService->register([
'person_id' => $participantPerson->id,
'branch_id' => $this->branchId,
'registration_source' => 'walk_in',
'primary_guardian_id' => $guardian->id,
'primary_activity_id' => $this->selected_activity_id,
], $actor);
// 5. Link guardian to participant
$participant->guardians()->attach($guardian->id, [
'relationship_type' => $this->guardian_relation,
'is_primary' => true,
'is_emergency_contact' => true,
'can_pickup' => true,
'receives_notifications' => true,
'can_authorize_payment' => true,
]);
// 6. Enroll in program with retroactive start date
$program = TrainingProgram::findOrFail($this->selected_program_id);
$enrollment = $enrollmentService->enrollInProgram( $enrollment = $enrollmentService->enrollInProgram(
$participant, $participant,
$program, $program,
...@@ -233,10 +325,9 @@ public function confirm(): void ...@@ -233,10 +325,9 @@ public function confirm(): void
] ]
); );
// 2. Calculate effective per-month price // 7. Calculate effective per-month price
$perMonth = $this->monthlyPrice; $perMonth = $this->monthlyPrice;
// Admin override distributes total evenly
$unpaidCount = $this->unpaidMonthsCount; $unpaidCount = $this->unpaidMonthsCount;
$overrideActive = $this->priceOverrideEnabled && $this->priceOverrideInput !== '' && $actor->is_super_admin; $overrideActive = $this->priceOverrideEnabled && $this->priceOverrideInput !== '' && $actor->is_super_admin;
if ($overrideActive && $unpaidCount > 0) { if ($overrideActive && $unpaidCount > 0) {
...@@ -246,7 +337,7 @@ public function confirm(): void ...@@ -246,7 +337,7 @@ public function confirm(): void
Log::channel('audit')->info('retroactive_enrollment_price_override', [ Log::channel('audit')->info('retroactive_enrollment_price_override', [
'actor_id' => $actor->id, 'actor_id' => $actor->id,
'actor_name' => $actor->name, 'actor_name' => $actor->name,
'participant_name' => $this->selected_participant_name, 'participant_name' => $this->participant_name_ar,
'program' => $program->name_ar, 'program' => $program->name_ar,
'original_per_month' => $this->monthlyPrice, 'original_per_month' => $this->monthlyPrice,
'override_total' => $overrideTotal, 'override_total' => $overrideTotal,
...@@ -256,7 +347,7 @@ public function confirm(): void ...@@ -256,7 +347,7 @@ public function confirm(): void
]); ]);
} }
// 3. Generate invoices per month (no platform fee on individual past invoices) // 8. Generate invoices per month
$invoicesCreated = 0; $invoicesCreated = 0;
$paymentsRecorded = 0; $paymentsRecorded = 0;
$totalRetroactiveRevenue = 0; $totalRetroactiveRevenue = 0;
...@@ -276,7 +367,7 @@ public function confirm(): void ...@@ -276,7 +367,7 @@ public function confirm(): void
'tax_amount' => 0, 'tax_amount' => 0,
'service_fee_amount' => 0, 'service_fee_amount' => 0,
'due_date' => $monthDate->copy()->addDays(7)->toDateString(), 'due_date' => $monthDate->copy()->addDays(7)->toDateString(),
'contact_name' => $participant->person?->name_ar, 'contact_name' => $participantPerson->name_ar,
'notes' => 'اشتراك ' . $program->name_ar . ' — ' . $this->getArabicMonth($monthDate), 'notes' => 'اشتراك ' . $program->name_ar . ' — ' . $this->getArabicMonth($monthDate),
'metadata' => ['retroactive' => true, 'month' => $monthKey, 'fee_deferred_to_registration_month' => true], 'metadata' => ['retroactive' => true, 'month' => $monthKey, 'fee_deferred_to_registration_month' => true],
], [ ], [
...@@ -315,7 +406,7 @@ public function confirm(): void ...@@ -315,7 +406,7 @@ public function confirm(): void
'tax_amount' => 0, 'tax_amount' => 0,
'service_fee_amount' => 0, 'service_fee_amount' => 0,
'due_date' => $monthDate->copy()->addDays(7)->toDateString(), 'due_date' => $monthDate->copy()->addDays(7)->toDateString(),
'contact_name' => $participant->person?->name_ar, 'contact_name' => $participantPerson->name_ar,
'notes' => 'اشتراك ' . $program->name_ar . ' — ' . $this->getArabicMonth($monthDate), 'notes' => 'اشتراك ' . $program->name_ar . ' — ' . $this->getArabicMonth($monthDate),
'metadata' => ['retroactive' => true, 'month' => $monthKey, 'fee_deferred_to_registration_month' => true], 'metadata' => ['retroactive' => true, 'month' => $monthKey, 'fee_deferred_to_registration_month' => true],
], [ ], [
...@@ -348,7 +439,7 @@ public function confirm(): void ...@@ -348,7 +439,7 @@ public function confirm(): void
} }
} }
// 4. Platform fee invoice — charged TODAY for ALL retroactive months // 9. Platform fee invoice — charged TODAY for ALL retroactive months
$platformFeeService = app(PlatformFeeService::class); $platformFeeService = app(PlatformFeeService::class);
if ($platformFeeService->isActive() && $totalRetroactiveRevenue > 0) { if ($platformFeeService->isActive() && $totalRetroactiveRevenue > 0) {
$totalPlatformFee = $platformFeeService->calculate($totalRetroactiveRevenue); $totalPlatformFee = $platformFeeService->calculate($totalRetroactiveRevenue);
...@@ -366,7 +457,7 @@ public function confirm(): void ...@@ -366,7 +457,7 @@ public function confirm(): void
'tax_amount' => 0, 'tax_amount' => 0,
'service_fee_amount' => $totalPlatformFee, 'service_fee_amount' => $totalPlatformFee,
'due_date' => now()->addDays(7)->toDateString(), 'due_date' => now()->addDays(7)->toDateString(),
'contact_name' => $participant->person?->name_ar, 'contact_name' => $participantPerson->name_ar,
'notes' => 'رسوم خدمة — تسجيل بأثر رجعي (' . $monthCount . ' شهور) — ' . $program->name_ar, 'notes' => 'رسوم خدمة — تسجيل بأثر رجعي (' . $monthCount . ' شهور) — ' . $program->name_ar,
'metadata' => [ 'metadata' => [
'retroactive_platform_fee' => true, 'retroactive_platform_fee' => true,
...@@ -390,7 +481,7 @@ public function confirm(): void ...@@ -390,7 +481,7 @@ public function confirm(): void
} }
} }
// 5. Set next_billing_date to 1st of next month // 10. Set next_billing_date to 1st of next month
$enrollment->update([ $enrollment->update([
'next_billing_date' => now()->addMonth()->startOfMonth()->toDateString(), 'next_billing_date' => now()->addMonth()->startOfMonth()->toDateString(),
'last_billed_at' => now()->toDateString(), 'last_billed_at' => now()->toDateString(),
...@@ -399,9 +490,10 @@ public function confirm(): void ...@@ -399,9 +490,10 @@ public function confirm(): void
$this->invoices_created = $invoicesCreated; $this->invoices_created = $invoicesCreated;
$this->payments_recorded = $paymentsRecorded; $this->payments_recorded = $paymentsRecorded;
$this->participant_uuid = $participant->uuid; $this->participant_uuid = $participant->uuid;
$this->participant_number = $participant->participant_number;
$this->enrollment_summary = $program->name_ar; $this->enrollment_summary = $program->name_ar;
$this->completed = true; $this->completed = true;
$this->currentStep = 5; $this->currentStep = 6;
}); });
session()->flash('success', __('تم تسجيل اللاعب بنجاح بأثر رجعي')); session()->flash('success', __('تم تسجيل اللاعب بنجاح بأثر رجعي'));
...@@ -415,25 +507,6 @@ public function confirm(): void ...@@ -415,25 +507,6 @@ public function confirm(): void
public function render() public function render()
{ {
$searchResults = collect();
if (strlen($this->search) >= 2 && !$this->selected_participant_id) {
$searchResults = Participant::query()
->with('person')
->where('branch_id', $this->branchId)
->where('status', 'active')
->where(function ($q) {
$search = $this->search;
$q->where('participant_number', 'ilike', "%{$search}%")
->orWhereHas('person', function ($pq) use ($search) {
$pq->where('name_ar', 'ilike', "%{$search}%")
->orWhere('name', 'ilike', "%{$search}%")
->orWhere('phone', 'like', "%{$search}%");
});
})
->limit(10)
->get();
}
$activities = Activity::where('is_active', true)->orderBy('name_ar')->get(); $activities = Activity::where('is_active', true)->orderBy('name_ar')->get();
$programs = collect(); $programs = collect();
...@@ -446,7 +519,6 @@ public function render() ...@@ -446,7 +519,6 @@ public function render()
} }
return view('livewire.receptionist.retroactive-enrollment-wizard', [ return view('livewire.receptionist.retroactive-enrollment-wizard', [
'searchResults' => $searchResults,
'activities' => $activities, 'activities' => $activities,
'programs' => $programs, 'programs' => $programs,
'isSuperAdmin' => auth()->user()?->is_super_admin ?? false, 'isSuperAdmin' => auth()->user()?->is_super_admin ?? false,
......
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
DB::statement("ALTER TABLE expenses DROP CONSTRAINT IF EXISTS expenses_category_check");
DB::statement("ALTER TABLE expenses ADD CONSTRAINT expenses_category_check CHECK (category IN ('maintenance', 'supplies', 'transport', 'food_beverage', 'sports_equipment', 'printing', 'cleaning', 'medical', 'utilities', 'other', 'external_revenue'))");
}
public function down(): void
{
DB::statement("ALTER TABLE expenses DROP CONSTRAINT IF EXISTS expenses_category_check");
DB::statement("ALTER TABLE expenses ADD CONSTRAINT expenses_category_check CHECK (category IN ('maintenance', 'supplies', 'transport', 'food_beverage', 'sports_equipment', 'printing', 'cleaning', 'medical', 'utilities', 'other'))");
}
};
...@@ -31,6 +31,7 @@ public function run(): void ...@@ -31,6 +31,7 @@ public function run(): void
['code' => '4030', 'name' => 'Facility Rental', 'name_ar' => 'إيجار الملاعب', 'type' => 'revenue', 'category' => 'operating', 'is_system' => true], ['code' => '4030', 'name' => 'Facility Rental', 'name_ar' => 'إيجار الملاعب', 'type' => 'revenue', 'category' => 'operating', 'is_system' => true],
['code' => '4040', 'name' => 'Private Sessions', 'name_ar' => 'الحصص الخاصة', 'type' => 'revenue', 'category' => 'operating', 'is_system' => true], ['code' => '4040', 'name' => 'Private Sessions', 'name_ar' => 'الحصص الخاصة', 'type' => 'revenue', 'category' => 'operating', 'is_system' => true],
['code' => '4050', 'name' => 'Tournament Fees', 'name_ar' => 'رسوم البطولات', 'type' => 'revenue', 'category' => 'operating', 'is_system' => true], ['code' => '4050', 'name' => 'Tournament Fees', 'name_ar' => 'رسوم البطولات', 'type' => 'revenue', 'category' => 'operating', 'is_system' => true],
['code' => '4060', 'name' => 'External Revenue', 'name_ar' => 'إيرادات خارجية', 'type' => 'revenue', 'category' => 'operating', 'is_system' => true],
// Expenses // Expenses
['code' => '5000', 'name' => 'Trainer Salaries', 'name_ar' => 'رواتب المدربين', 'type' => 'expense', 'category' => 'operating', 'is_system' => true], ['code' => '5000', 'name' => 'Trainer Salaries', 'name_ar' => 'رواتب المدربين', 'type' => 'expense', 'category' => 'operating', 'is_system' => true],
......
<div> <div>
@if($totalOverdue > 0) @if($totalOverdue > 0)
<div class="bg-gradient-to-l from-red-50 to-red-100 rounded-xl shadow-sm border-2 border-red-300 p-5 mb-6"> <div class="bg-gradient-to-l from-red-50 to-red-100 rounded-xl shadow-sm border border-red-200 p-4 mb-4">
{{-- Header --}} <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="w-12 h-12 bg-red-200 rounded-xl flex items-center justify-center animate-pulse"> <div class="w-10 h-10 bg-red-200 rounded-lg flex items-center justify-center shrink-0">
<svg class="w-7 h-7 text-red-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 text-red-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg> </svg>
</div> </div>
<div> <div>
<h2 class="text-lg font-bold text-red-900">{{ __('اشتراكات لم تُجدد') }}</h2> <h2 class="text-sm font-bold text-red-900">{{ __('اشتراكات لم تُجدد') }}</h2>
<p class="text-sm text-red-700"> <div class="flex flex-wrap items-center gap-x-4 gap-y-1 mt-0.5">
<span class="font-bold text-xl">{{ $totalOverdue }}</span> <span class="text-lg font-bold text-red-800">{{ $totalOverdue }}</span>
{{ __('مشترك لم يجدد اشتراكه حتى الآن') }} <span class="text-xs text-red-700">{{ __('مشترك') }}</span>
@if($totalAmount > 0) @if($totalAmount > 0)
<span class="font-bold">{{ number_format($totalAmount / 100, 2) }} {{ __('ج.م') }}</span> {{ __('مستحقة') }} <span class="text-sm font-bold text-red-800">{{ number_format($totalAmount / 100, 0) }} {{ __('ج.م') }}</span>
<span class="text-xs text-red-600">{{ __('مستحقة') }}</span>
@endif @endif
</div>
<p class="text-[11px] text-red-600 mt-0.5">
{{ $invoiceCount }} {{ __('فاتورة غير مدفوعة') }}
&bull;
{{ $enrollmentCount }} {{ __('بدون فاتورة') }}
</p> </p>
</div> </div>
</div> </div>
<a href="{{ route('receptionist.collect-payment') }}" wire:navigate <div class="flex items-center gap-2">
class="inline-flex items-center gap-2 px-5 py-3 bg-red-700 text-white rounded-xl hover:bg-red-800 text-sm font-bold transition-colors shadow-sm"> <button wire:click="toggleList"
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> class="inline-flex items-center gap-1.5 px-3 py-2 text-red-700 bg-white border border-red-200 rounded-lg hover:bg-red-50 text-xs font-medium transition-colors">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/> <svg class="w-3.5 h-3.5 transition-transform {{ $showList ? 'rotate-180' : '' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
</svg> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
{{ __('تحصيل مدفوعات') }} </svg>
</a> {{ $showList ? __('إخفاء') : __('عرض التفاصيل') }}
</button>
<a href="{{ route('receptionist.collect-payment') }}" wire:navigate
class="inline-flex items-center gap-1.5 px-3 py-2 bg-red-700 text-white rounded-lg hover:bg-red-800 text-xs font-bold transition-colors">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8V7m0 10v1"/>
</svg>
{{ __('تحصيل مدفوعات') }}
</a>
</div>
</div> </div>
</div>
{{-- Participants list --}} {{-- Expandable Detail List --}}
<div class="bg-white rounded-xl border border-red-200 overflow-hidden"> @if($showList)
<table class="w-full text-sm"> <div class="bg-white rounded-xl shadow-sm border border-red-200 overflow-hidden mb-4" wire:loading.class="opacity-50" wire:target="toggleList">
<thead class="bg-red-50 border-b border-red-200"> <table class="w-full text-sm">
<tr> <thead class="bg-red-50 border-b border-red-200">
<th class="text-start px-4 py-2.5 font-semibold text-red-800">{{ __('المشترك') }}</th> <tr>
<th class="text-start px-4 py-2.5 font-semibold text-red-800">{{ __('البرنامج') }}</th> <th class="text-start px-4 py-2.5 font-semibold text-red-800">{{ __('المشترك') }}</th>
<th class="text-center px-4 py-2.5 font-semibold text-red-800">{{ __('المبلغ') }}</th> <th class="text-start px-4 py-2.5 font-semibold text-red-800">{{ __('البرنامج') }}</th>
<th class="text-center px-4 py-2.5 font-semibold text-red-800">{{ __('تأخير') }}</th> <th class="text-center px-4 py-2.5 font-semibold text-red-800">{{ __('المبلغ') }}</th>
<th class="text-center px-4 py-2.5 font-semibold text-red-800">{{ __('إجراء') }}</th> <th class="text-center px-4 py-2.5 font-semibold text-red-800">{{ __('تأخير') }}</th>
</tr> <th class="text-center px-4 py-2.5 font-semibold text-red-800">{{ __('إجراء') }}</th>
</thead> </tr>
<tbody class="divide-y divide-red-100"> </thead>
@foreach($items as $item) <tbody class="divide-y divide-red-100">
<tr class="hover:bg-red-50 transition-colors"> @foreach($items as $item)
<td class="px-4 py-3"> <tr class="hover:bg-red-50/50 transition-colors">
<div class="flex items-center gap-2"> <td class="px-4 py-2.5">
<div class="w-8 h-8 rounded-full {{ $item['type'] === 'invoice' ? 'bg-red-100' : 'bg-amber-100' }} flex items-center justify-center shrink-0"> <div class="flex items-center gap-2">
<svg class="w-4 h-4 {{ $item['type'] === 'invoice' ? 'text-red-600' : 'text-amber-600' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <div class="w-7 h-7 rounded-full {{ $item['type'] === 'invoice' ? 'bg-red-100' : 'bg-amber-100' }} flex items-center justify-center shrink-0">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/> <svg class="w-3.5 h-3.5 {{ $item['type'] === 'invoice' ? 'text-red-600' : 'text-amber-600' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
</svg> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</div>
<div class="min-w-0">
<p class="font-medium text-gray-800 truncate">{{ $item['participant_name'] }}</p>
@if($item['participant_phone'])
<p class="text-xs text-gray-500" dir="ltr">{{ $item['participant_phone'] }}</p>
@endif
</div>
</div>
</td>
<td class="px-4 py-3 text-gray-700">{{ $item['program_name'] }}</td>
<td class="px-4 py-3 text-center">
@if($item['amount'])
<span class="font-bold text-red-700">{{ number_format($item['amount'] / 100, 2) }}</span>
<span class="text-xs text-gray-500">{{ __('ج.م') }}</span>
@else
<span class="text-xs text-amber-600 font-medium">{{ __('بدون فاتورة') }}</span>
@endif
</td>
<td class="px-4 py-3 text-center">
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-bold
{{ $item['days_overdue'] > 7 ? 'bg-red-200 text-red-800' : 'bg-amber-100 text-amber-800' }}">
{{ $item['days_overdue'] }} {{ __('يوم') }}
</span>
</td>
<td class="px-4 py-3 text-center">
@if($item['participant_uuid'])
<a href="{{ route('receptionist.collect-payment') }}?participant={{ $item['participant_uuid'] }}" wire:navigate
class="inline-flex items-center gap-1 px-3 py-1.5 bg-green-600 text-white rounded-lg hover:bg-green-700 text-xs font-bold transition-colors">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8V7m0 10v1"/>
</svg> </svg>
{{ __('جدّد') }} </div>
</a> <div class="min-w-0">
@endif <p class="font-medium text-gray-800 truncate text-xs">{{ $item['participant_name'] }}</p>
</td> @if($item['participant_phone'])
</tr> <p class="text-[10px] text-gray-500" dir="ltr">{{ $item['participant_phone'] }}</p>
@endforeach @endif
</tbody> </div>
</table> </div>
</div> </td>
<td class="px-4 py-2.5 text-gray-700 text-xs">{{ $item['program_name'] }}</td>
{{-- Show more / less --}} <td class="px-4 py-2.5 text-center">
@if($totalOverdue > 15) @if($item['amount'])
<div class="mt-3 text-center"> <span class="font-bold text-red-700 text-xs">{{ number_format($item['amount'] / 100, 0) }}</span>
<button wire:click="toggleExpanded" class="text-sm text-red-700 hover:text-red-900 font-medium"> <span class="text-[10px] text-gray-500">{{ __('ج.م') }}</span>
@if($expanded) @else
{{ __('عرض أقل') }} <span class="text-[10px] text-amber-600 font-medium">{{ __('بدون فاتورة') }}</span>
@else @endif
{{ __('عرض الكل') }} ({{ $totalOverdue }}) </td>
@endif <td class="px-4 py-2.5 text-center">
</button> <span class="inline-flex items-center px-1.5 py-0.5 rounded-full text-[10px] font-bold
</div> {{ $item['days_overdue'] > 7 ? 'bg-red-200 text-red-800' : 'bg-amber-100 text-amber-800' }}">
@endif {{ $item['days_overdue'] }} {{ __('يوم') }}
</span>
</td>
<td class="px-4 py-2.5 text-center">
@if($item['participant_uuid'])
<a href="{{ route('receptionist.collect-payment') }}?participant={{ $item['participant_uuid'] }}" wire:navigate
class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-600 text-white rounded-lg hover:bg-green-700 text-[10px] font-bold transition-colors">
{{ __('جدّد') }}
</a>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div> </div>
@endif @endif
@endif
</div> </div>
<div>
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-4 sm:mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('تسجيل إيراد خارجي') }}</h1>
<a href="{{ route('financial.overview') }}" wire:navigate
class="inline-flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800">
<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="M11 17l-5-5m0 0l5-5m-5 5h12"/></svg>
{{ __('النظرة المالية') }}
</a>
</div>
@if(session('error'))
<div class="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">{{ session('error') }}</div>
@endif
<form wire:submit="save" class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
{{-- Source --}}
<div>
<label class="block text-sm text-gray-600 mb-1">{{ __('مصدر الإيراد') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="source"
class="w-full rounded-lg border-gray-300 text-sm py-2.5"
placeholder="{{ __('مثال: النادي الأهلي، شركة راعية') }}">
@error('source') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Amount --}}
<div>
<label class="block text-sm text-gray-600 mb-1">{{ __('المبلغ (ج.م)') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="amount_display" dir="ltr" step="0.01" min="0"
class="w-full rounded-lg border-gray-300 text-sm py-2.5" placeholder="0.00">
@error('amount_display') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Description --}}
<div class="md:col-span-2">
<label class="block text-sm text-gray-600 mb-1">{{ __('الوصف') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="description"
class="w-full rounded-lg border-gray-300 text-sm py-2.5"
placeholder="{{ __('مثال: دفعة النادي عن شهر يوليو') }}">
@error('description') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Payment Method --}}
<div class="md:col-span-2">
<label class="block text-sm text-gray-600 mb-2">{{ __('طريقة الاستلام') }} <span class="text-red-500">*</span></label>
<div class="grid grid-cols-2 sm:grid-cols-5 gap-2">
<label class="relative cursor-pointer">
<input type="radio" wire:model="payment_method" value="cash" class="peer sr-only">
<div class="peer-checked:ring-2 peer-checked:ring-green-500 peer-checked:border-green-500 peer-checked:bg-green-50 flex flex-col items-center gap-1.5 p-3 rounded-xl border-2 border-gray-200 hover:border-green-300 transition-all">
<svg class="w-7 h-7 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z"/>
</svg>
<span class="text-xs font-medium text-gray-700">{{ __('كاش') }}</span>
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model="payment_method" value="bank_transfer" class="peer sr-only">
<div class="peer-checked:ring-2 peer-checked:ring-purple-500 peer-checked:border-purple-500 peer-checked:bg-purple-50 flex flex-col items-center gap-1.5 p-3 rounded-xl border-2 border-gray-200 hover:border-purple-300 transition-all">
<svg class="w-7 h-7 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 14v3m4-3v3m4-3v3M3 21h18M3 10h18M3 7l9-4 9 4M4 10h16v11H4V10z"/>
</svg>
<span class="text-xs font-medium text-gray-700">{{ __('تحويل بنكي') }}</span>
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model="payment_method" value="cheque" class="peer sr-only">
<div class="peer-checked:ring-2 peer-checked:ring-amber-500 peer-checked:border-amber-500 peer-checked:bg-amber-50 flex flex-col items-center gap-1.5 p-3 rounded-xl border-2 border-gray-200 hover:border-amber-300 transition-all">
<svg class="w-7 h-7 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<span class="text-xs font-medium text-gray-700">{{ __('شيك') }}</span>
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model="payment_method" value="other" class="peer sr-only">
<div class="peer-checked:ring-2 peer-checked:ring-gray-500 peer-checked:border-gray-500 peer-checked:bg-gray-50 flex flex-col items-center gap-1.5 p-3 rounded-xl border-2 border-gray-200 hover:border-gray-300 transition-all">
<svg class="w-7 h-7 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h.01M12 12h.01M19 12h.01M6 12a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0z"/>
</svg>
<span class="text-xs font-medium text-gray-700">{{ __('أخرى') }}</span>
</div>
</label>
</div>
@error('payment_method') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Reference Number --}}
<div>
<label class="block text-sm text-gray-600 mb-1">{{ __('رقم المرجع / الإيصال') }}</label>
<input type="text" wire:model="reference_number" dir="ltr"
class="w-full rounded-lg border-gray-300 text-sm py-2.5">
@error('reference_number') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Date --}}
<div>
<label class="block text-sm text-gray-600 mb-1">{{ __('تاريخ الإيراد') }} <span class="text-red-500">*</span></label>
<input type="date" wire:model="revenue_date" dir="ltr"
class="w-full rounded-lg border-gray-300 text-sm py-2.5">
@error('revenue_date') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Notes --}}
<div class="md:col-span-2">
<label class="block text-sm text-gray-600 mb-1">{{ __('ملاحظات') }}</label>
<textarea wire:model="notes" rows="2"
class="w-full rounded-lg border-gray-300 text-sm py-2.5"
placeholder="{{ __('أي تفاصيل إضافية...') }}"></textarea>
</div>
</div>
{{-- Submit --}}
<div class="mt-6 flex justify-end">
<button type="submit" wire:loading.attr="disabled" wire:target="save"
class="inline-flex items-center gap-2 px-6 py-2.5 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 transition text-sm font-medium disabled:opacity-50">
<span wire:loading.remove wire:target="save">{{ __('تسجيل الإيراد') }}</span>
<span wire:loading wire:target="save">{{ __('جارٍ الحفظ...') }}</span>
</button>
</div>
</form>
</div>
...@@ -23,6 +23,26 @@ ...@@ -23,6 +23,26 @@
</div> </div>
</div> </div>
{{-- Quick Actions --}}
@can('expenses.create')
<div class="flex flex-wrap gap-2 mb-4">
<a href="{{ route('expenses.create') }}" wire:navigate
class="inline-flex items-center gap-1.5 px-3 py-2 bg-red-50 border border-red-200 text-red-700 rounded-lg hover:bg-red-100 text-xs font-medium transition-colors">
<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="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
</svg>
{{ __('تسجيل مصروف') }}
</a>
<a href="{{ route('revenue.external.create') }}" wire:navigate
class="inline-flex items-center gap-1.5 px-3 py-2 bg-emerald-50 border border-emerald-200 text-emerald-700 rounded-lg hover:bg-emerald-100 text-xs font-medium transition-colors">
<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="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
</svg>
{{ __('تسجيل إيراد خارجي') }}
</a>
</div>
@endcan
{{-- Loading overlay --}} {{-- Loading overlay --}}
<div wire:loading.class="opacity-50 pointer-events-none" class="transition-opacity"> <div wire:loading.class="opacity-50 pointer-events-none" class="transition-opacity">
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="flex items-center justify-between mb-6"> <div class="flex items-center justify-between mb-6">
<div> <div>
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('تسجيل لاعب سابق') }}</h1> <h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('تسجيل لاعب سابق') }}</h1>
<p class="text-sm text-gray-500 mt-1">{{ __('تسجيل مشترك بدأ قبل كده وتسوية الفواتير السابقة') }}</p> <p class="text-sm text-gray-500 mt-1">{{ __('تسجيل لاعب جديد لم يكن في النظام وتسوية الفواتير السابقة') }}</p>
</div> </div>
<a href="{{ route('receptionist.dashboard') }}" wire:navigate <a href="{{ route('receptionist.dashboard') }}" wire:navigate
class="inline-flex items-center gap-2 px-4 py-2 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 text-sm font-medium transition-colors"> class="inline-flex items-center gap-2 px-4 py-2 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 text-sm font-medium transition-colors">
...@@ -23,15 +23,16 @@ class="inline-flex items-center gap-2 px-4 py-2 text-gray-600 bg-gray-100 rounde ...@@ -23,15 +23,16 @@ class="inline-flex items-center gap-2 px-4 py-2 text-gray-600 bg-gray-100 rounde
@endif @endif
{{-- Step Indicator --}} {{-- Step Indicator --}}
@if($currentStep < 5) @if($currentStep < 6)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6"> <div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
@php @php
$steps = [ $steps = [
1 => 'اختيار المشترك', 1 => 'بيانات اللاعب',
2 => 'اختيار البرنامج', 2 => 'ولي الأمر',
3 => 'الشهور المستحقة', 3 => 'اختيار البرنامج',
4 => 'الدفع والتأكيد', 4 => 'الشهور المستحقة',
5 => 'الدفع والتأكيد',
]; ];
@endphp @endphp
@foreach($steps as $num => $label) @foreach($steps as $num => $label)
...@@ -69,64 +70,144 @@ class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'curs ...@@ -69,64 +70,144 @@ class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'curs
{{-- Step Content --}} {{-- Step Content --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6"> <div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
{{-- Step 1: Select Participant --}} {{-- Step 1: Participant Info --}}
@if($currentStep === 1) @if($currentStep === 1)
<div> <div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('اختيار المشترك') }}</h2> <h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('بيانات اللاعب') }}</h2>
<div class="mb-6"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<input type="text" wire:model.live.debounce.300ms="search" {{-- Name --}}
class="w-full px-4 py-3 min-h-16 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-lg" <div class="sm:col-span-2">
placeholder="{{ __('بحث بالاسم أو الهاتف أو رقم المشترك...') }}"> <label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اسم اللاعب بالكامل') }} *</label>
</div> <input type="text" wire:model="participant_name_ar"
class="w-full px-4 py-3 min-h-14 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-base @error('participant_name_ar') border-red-500 @enderror"
placeholder="{{ __('الاسم رباعي') }}">
@error('participant_name_ar') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
@if($selected_participant_id) {{-- National ID --}}
<div class="mb-4 p-4 bg-indigo-50 border border-indigo-200 rounded-xl flex items-center justify-between"> <div>
<div class="flex items-center gap-3"> <label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الرقم القومي') }}</label>
<div class="w-10 h-10 rounded-full bg-indigo-200 flex items-center justify-center"> <input type="text" wire:model.live.debounce.500ms="participant_national_id" dir="ltr" maxlength="14"
<svg class="w-5 h-5 text-indigo-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"> class="w-full px-4 py-3 min-h-14 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 text-base font-mono @error('participant_national_id') border-red-500 @enderror"
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/> placeholder="29901011234567">
</svg> @if($participant_nid_decoded)
<p class="mt-1 text-xs text-green-600">{{ __('تم استخراج تاريخ الميلاد والنوع تلقائياً') }}</p>
@endif
@error('participant_national_id') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Date of Birth --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ الميلاد') }}</label>
<input type="date" wire:model="participant_date_of_birth" dir="ltr"
class="w-full px-4 py-3 min-h-14 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 text-base @error('participant_date_of_birth') border-red-500 @enderror"
{{ $participant_nid_decoded ? 'disabled' : '' }}>
@error('participant_date_of_birth') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Gender --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __('النوع') }} *</label>
<div class="flex gap-4">
<label class="relative cursor-pointer flex-1">
<input type="radio" wire:model="participant_gender" value="male" class="peer sr-only" {{ $participant_nid_decoded ? 'disabled' : '' }}>
<div class="p-3 min-h-14 flex items-center justify-center border border-gray-300 rounded-xl transition-all text-sm font-medium
peer-checked:border-indigo-500 peer-checked:bg-indigo-50 peer-checked:text-indigo-700 hover:border-gray-400">
{{ __('ذكر') }}
</div>
</label>
<label class="relative cursor-pointer flex-1">
<input type="radio" wire:model="participant_gender" value="female" class="peer sr-only" {{ $participant_nid_decoded ? 'disabled' : '' }}>
<div class="p-3 min-h-14 flex items-center justify-center border border-gray-300 rounded-xl transition-all text-sm font-medium
peer-checked:border-indigo-500 peer-checked:bg-indigo-50 peer-checked:text-indigo-700 hover:border-gray-400">
{{ __('أنثى') }}
</div>
</label>
</div> </div>
<span class="font-medium text-indigo-800">{{ $selected_participant_name }}</span>
</div> </div>
<button wire:click="$set('selected_participant_id', null)" class="text-indigo-600 hover:text-indigo-800 text-sm">
{{ __('تغيير') }} {{-- Phone --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('هاتف اللاعب') }}</label>
<input type="tel" wire:model="participant_phone" dir="ltr"
class="w-full px-4 py-3 min-h-14 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 text-base"
placeholder="01012345678">
</div>
{{-- Medical Notes --}}
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ملاحظات طبية') }}</label>
<textarea wire:model="participant_medical_notes" rows="2"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 text-sm"
placeholder="{{ __('حساسية، إصابات سابقة، أمراض مزمنة...') }}"></textarea>
</div>
</div>
<div class="flex justify-end mt-8">
<button wire:click="nextStep" @if(!$participant_name_ar) disabled @endif
class="inline-flex items-center gap-2 px-6 py-3 min-h-14 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 text-base font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
{{ __('التالي') }}
<svg class="w-5 h-5 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
</button> </button>
</div> </div>
@endif </div>
@endif
@if(strlen($search) >= 2 && !$selected_participant_id) {{-- Step 2: Guardian Info --}}
<div class="space-y-2" wire:loading.class="opacity-50"> @if($currentStep === 2)
@forelse($searchResults as $result) <div>
<button wire:click="selectParticipant({{ $result->id }}, '{{ $result->person?->name_ar }}')" <h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('بيانات ولي الأمر') }}</h2>
class="w-full p-4 min-h-16 border border-gray-200 rounded-xl text-start hover:border-indigo-300 hover:bg-indigo-50 transition-all flex items-center justify-between">
<div> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<p class="font-medium text-gray-800">{{ $result->person?->name_ar }}</p> {{-- Guardian Name --}}
<div class="flex items-center gap-3 mt-1 text-xs text-gray-500"> <div class="sm:col-span-2">
@if($result->participant_number) <label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اسم ولي الأمر') }} *</label>
<span dir="ltr">{{ $result->participant_number }}</span> <input type="text" wire:model="guardian_name_ar"
@endif class="w-full px-4 py-3 min-h-14 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-base @error('guardian_name_ar') border-red-500 @enderror"
@if($result->person?->phone) placeholder="{{ __('الاسم بالكامل') }}">
<span dir="ltr">{{ $result->person->phone }}</span> @error('guardian_name_ar') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
@endif </div>
</div>
{{-- Guardian Phone --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('هاتف ولي الأمر') }} *</label>
<input type="tel" wire:model="guardian_phone" dir="ltr"
class="w-full px-4 py-3 min-h-14 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 text-base @error('guardian_phone') border-red-500 @enderror"
placeholder="01012345678">
@error('guardian_phone') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Relation --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __('صلة القرابة') }} *</label>
<div class="grid grid-cols-3 gap-2">
@foreach(['father' => 'أب', 'mother' => 'أم', 'brother' => 'أخ', 'sister' => 'أخت', 'uncle' => 'عم/خال', 'other' => 'أخرى'] as $key => $label)
<label class="relative cursor-pointer">
<input type="radio" wire:model="guardian_relation" value="{{ $key }}" class="peer sr-only">
<div class="p-2.5 flex items-center justify-center border border-gray-300 rounded-lg transition-all text-xs font-medium
peer-checked:border-indigo-500 peer-checked:bg-indigo-50 peer-checked:text-indigo-700 hover:border-gray-400">
{{ __($label) }}
</div>
</label>
@endforeach
</div> </div>
<span class="px-2 py-0.5 text-xs rounded-full bg-green-100 text-green-700">{{ __('نشط') }}</span> @error('guardian_relation') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</button>
@empty
<div class="text-center py-8 text-gray-500">
<p>{{ __('لا توجد نتائج') }}</p>
</div> </div>
@endforelse
</div> </div>
@endif
@error('selected_participant_id') <p class="mt-2 text-sm text-red-600">{{ $message }}</p> @enderror <div class="flex justify-between mt-8">
<button wire:click="previousStep"
<div class="flex justify-end mt-8"> class="inline-flex items-center gap-2 px-6 py-3 min-h-14 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 text-base font-medium">
<button wire:click="nextStep" @if(!$selected_participant_id) disabled @endif <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 text-base font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
{{ __('السابق') }}
</button>
<button wire:click="nextStep" @if(!$guardian_name_ar || !$guardian_phone) disabled @endif
class="inline-flex items-center gap-2 px-6 py-3 min-h-14 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 text-base font-medium disabled:opacity-50 disabled:cursor-not-allowed">
{{ __('التالي') }} {{ __('التالي') }}
<svg class="w-5 h-5 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
...@@ -136,8 +217,8 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit ...@@ -136,8 +217,8 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit
</div> </div>
@endif @endif
{{-- Step 2: Program Selection --}} {{-- Step 3: Program Selection --}}
@if($currentStep === 2) @if($currentStep === 3)
<div> <div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('اختيار البرنامج') }}</h2> <h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('اختيار البرنامج') }}</h2>
...@@ -148,7 +229,7 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit ...@@ -148,7 +229,7 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit
@foreach($activities as $activity) @foreach($activities as $activity)
<label class="relative cursor-pointer"> <label class="relative cursor-pointer">
<input type="radio" wire:model.live="selected_activity_id" value="{{ $activity->id }}" class="peer sr-only"> <input type="radio" wire:model.live="selected_activity_id" value="{{ $activity->id }}" class="peer sr-only">
<div class="p-4 min-h-16 border border-gray-200 rounded-xl text-center transition-all <div class="p-4 min-h-14 border border-gray-200 rounded-xl text-center transition-all
peer-checked:border-indigo-500 peer-checked:bg-indigo-50 hover:border-gray-300"> peer-checked:border-indigo-500 peer-checked:bg-indigo-50 hover:border-gray-300">
<p class="font-medium text-gray-800 text-sm">{{ $activity->name_ar }}</p> <p class="font-medium text-gray-800 text-sm">{{ $activity->name_ar }}</p>
</div> </div>
...@@ -169,7 +250,7 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit ...@@ -169,7 +250,7 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit
@foreach($programs as $program) @foreach($programs as $program)
<label class="relative cursor-pointer block"> <label class="relative cursor-pointer block">
<input type="radio" wire:model.live="selected_program_id" value="{{ $program->id }}" class="peer sr-only"> <input type="radio" wire:model.live="selected_program_id" value="{{ $program->id }}" class="peer sr-only">
<div class="p-4 min-h-16 border border-gray-200 rounded-xl transition-all <div class="p-4 min-h-14 border border-gray-200 rounded-xl transition-all
peer-checked:border-indigo-500 peer-checked:bg-indigo-50 hover:border-gray-300"> peer-checked:border-indigo-500 peer-checked:bg-indigo-50 hover:border-gray-300">
<p class="font-medium text-gray-800">{{ $program->name_ar }}</p> <p class="font-medium text-gray-800">{{ $program->name_ar }}</p>
@if($program->description) @if($program->description)
...@@ -186,14 +267,14 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit ...@@ -186,14 +267,14 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit
<div class="flex justify-between mt-8"> <div class="flex justify-between mt-8">
<button wire:click="previousStep" <button wire:click="previousStep"
class="inline-flex items-center gap-2 px-6 py-3 min-h-16 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 text-base font-medium"> class="inline-flex items-center gap-2 px-6 py-3 min-h-14 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 text-base font-medium">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg> </svg>
{{ __('السابق') }} {{ __('السابق') }}
</button> </button>
<button wire:click="nextStep" @if(!$selected_program_id) disabled @endif <button wire:click="nextStep" @if(!$selected_program_id) disabled @endif
class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 text-base font-medium disabled:opacity-50 disabled:cursor-not-allowed"> class="inline-flex items-center gap-2 px-6 py-3 min-h-14 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 text-base font-medium disabled:opacity-50 disabled:cursor-not-allowed">
{{ __('التالي') }} {{ __('التالي') }}
<svg class="w-5 h-5 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
...@@ -203,8 +284,8 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit ...@@ -203,8 +284,8 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit
</div> </div>
@endif @endif
{{-- Step 3: Months Owed --}} {{-- Step 4: Months Owed --}}
@if($currentStep === 3) @if($currentStep === 4)
<div> <div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('الشهور المستحقة') }}</h2> <h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('الشهور المستحقة') }}</h2>
...@@ -311,14 +392,14 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin ...@@ -311,14 +392,14 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin
<div class="flex justify-between mt-8"> <div class="flex justify-between mt-8">
<button wire:click="previousStep" <button wire:click="previousStep"
class="inline-flex items-center gap-2 px-6 py-3 min-h-16 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 text-base font-medium"> class="inline-flex items-center gap-2 px-6 py-3 min-h-14 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 text-base font-medium">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg> </svg>
{{ __('السابق') }} {{ __('السابق') }}
</button> </button>
<button wire:click="nextStep" @if(!$actual_start_date) disabled @endif <button wire:click="nextStep" @if(!$actual_start_date) disabled @endif
class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 text-base font-medium disabled:opacity-50 disabled:cursor-not-allowed"> class="inline-flex items-center gap-2 px-6 py-3 min-h-14 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 text-base font-medium disabled:opacity-50 disabled:cursor-not-allowed">
{{ __('التالي') }} {{ __('التالي') }}
<svg class="w-5 h-5 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
...@@ -328,16 +409,20 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit ...@@ -328,16 +409,20 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit
</div> </div>
@endif @endif
{{-- Step 4: Payment & Confirm --}} {{-- Step 5: Payment & Confirm --}}
@if($currentStep === 4) @if($currentStep === 5)
<div> <div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('الدفع والتأكيد') }}</h2> <h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('الدفع والتأكيد') }}</h2>
{{-- Summary --}} {{-- Summary --}}
<div class="p-5 bg-gray-50 border border-gray-200 rounded-xl mb-6 space-y-3"> <div class="p-5 bg-gray-50 border border-gray-200 rounded-xl mb-6 space-y-3">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-gray-600">{{ __('المشترك') }}</span> <span class="text-gray-600">{{ __('اللاعب') }}</span>
<span class="font-medium">{{ $selected_participant_name }}</span> <span class="font-medium">{{ $participant_name_ar }}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-600">{{ __('ولي الأمر') }}</span>
<span class="font-medium">{{ $guardian_name_ar }}</span>
</div> </div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-gray-600">{{ __('تاريخ البدء') }}</span> <span class="text-gray-600">{{ __('تاريخ البدء') }}</span>
...@@ -359,7 +444,7 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit ...@@ -359,7 +444,7 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit
@if($platformFee->isActive()) @if($platformFee->isActive())
<div class="border-t border-gray-200 pt-3 flex items-center justify-between text-sm"> <div class="border-t border-gray-200 pt-3 flex items-center justify-between text-sm">
<span class="text-gray-500">{{ __('رسوم خدمة المنصة') }} ({{ $platformFee->getPercentage() }}%)</span> <span class="text-gray-500">{{ __('رسوم خدمة المنصة') }} ({{ $platformFee->getPercentage() }}%)</span>
<span class="font-medium text-gray-600" dir="ltr">{{ number_format($platformFee->calculate(count($monthStatuses) * $monthlyPrice) / 100, 2) }} {{ __('ج.م') }}</span> <span class="font-medium text-gray-600" dir="ltr">{{ number_format($platformFee->calculate(count($monthStatuses) * $this->monthlyPrice) / 100, 2) }} {{ __('ج.م') }}</span>
</div> </div>
<p class="text-xs text-gray-400 mt-1">{{ __('تُحسب على كل الشهور وتُسجَّل في الشهر الحالي') }}</p> <p class="text-xs text-gray-400 mt-1">{{ __('تُحسب على كل الشهور وتُسجَّل في الشهر الحالي') }}</p>
@endif @endif
...@@ -391,14 +476,14 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit ...@@ -391,14 +476,14 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-whit
<div class="flex justify-between mt-8"> <div class="flex justify-between mt-8">
<button wire:click="previousStep" <button wire:click="previousStep"
class="inline-flex items-center gap-2 px-6 py-3 min-h-16 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 text-base font-medium"> class="inline-flex items-center gap-2 px-6 py-3 min-h-14 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 text-base font-medium">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg> </svg>
{{ __('السابق') }} {{ __('السابق') }}
</button> </button>
<button wire:click="confirm" wire:loading.attr="disabled" <button wire:click="confirm" wire:loading.attr="disabled"
class="inline-flex items-center gap-2 px-8 py-3 min-h-16 bg-green-600 text-white rounded-lg hover:bg-green-700 text-base font-bold transition-colors disabled:opacity-50"> class="inline-flex items-center gap-2 px-8 py-3 min-h-14 bg-green-600 text-white rounded-lg hover:bg-green-700 text-base font-bold transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد التسجيل') }}</span> <span wire:loading.remove wire:target="confirm">{{ __('تأكيد التسجيل') }}</span>
<span wire:loading wire:target="confirm" class="inline-flex items-center gap-2"> <span wire:loading wire:target="confirm" class="inline-flex items-center gap-2">
<svg class="animate-spin w-5 h-5" fill="none" viewBox="0 0 24 24"> <svg class="animate-spin w-5 h-5" fill="none" viewBox="0 0 24 24">
...@@ -412,8 +497,8 @@ class="inline-flex items-center gap-2 px-8 py-3 min-h-16 bg-green-600 text-white ...@@ -412,8 +497,8 @@ class="inline-flex items-center gap-2 px-8 py-3 min-h-16 bg-green-600 text-white
</div> </div>
@endif @endif
{{-- Step 5: Success --}} {{-- Step 6: Success --}}
@if($currentStep === 5) @if($currentStep === 6)
<div class="text-center py-8"> <div class="text-center py-8">
<div class="w-20 h-20 mx-auto bg-green-100 rounded-full flex items-center justify-center mb-6"> <div class="w-20 h-20 mx-auto bg-green-100 rounded-full flex items-center justify-center mb-6">
<svg class="w-10 h-10 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-10 h-10 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
...@@ -422,14 +507,20 @@ class="inline-flex items-center gap-2 px-8 py-3 min-h-16 bg-green-600 text-white ...@@ -422,14 +507,20 @@ class="inline-flex items-center gap-2 px-8 py-3 min-h-16 bg-green-600 text-white
</div> </div>
<h2 class="text-2xl font-bold text-green-700 mb-2">{{ __('تم التسجيل بنجاح!') }}</h2> <h2 class="text-2xl font-bold text-green-700 mb-2">{{ __('تم التسجيل بنجاح!') }}</h2>
<p class="text-gray-600 mb-6">{{ __('تم تسجيل اللاعب بأثر رجعي وإنشاء الفواتير') }}</p> <p class="text-gray-600 mb-6">{{ __('تم إنشاء اللاعب وتسجيله بأثر رجعي وإنشاء الفواتير') }}</p>
<div class="inline-block bg-gray-50 border border-gray-200 rounded-xl p-6 mb-6 text-start"> <div class="inline-block bg-gray-50 border border-gray-200 rounded-xl p-6 mb-6 text-start">
<div class="space-y-3"> <div class="space-y-3">
<div> <div>
<p class="text-xs text-gray-500">{{ __('المشترك') }}</p> <p class="text-xs text-gray-500">{{ __('اللاعب') }}</p>
<p class="font-medium text-gray-800">{{ $selected_participant_name }}</p> <p class="font-medium text-gray-800">{{ $participant_name_ar }}</p>
</div>
@if($participant_number)
<div>
<p class="text-xs text-gray-500">{{ __('رقم المشترك') }}</p>
<p class="font-medium text-gray-800" dir="ltr">{{ $participant_number }}</p>
</div> </div>
@endif
<div> <div>
<p class="text-xs text-gray-500">{{ __('البرنامج') }}</p> <p class="text-xs text-gray-500">{{ __('البرنامج') }}</p>
<p class="font-medium text-gray-800">{{ $enrollment_summary }}</p> <p class="font-medium text-gray-800">{{ $enrollment_summary }}</p>
...@@ -452,12 +543,12 @@ class="inline-flex items-center gap-2 px-8 py-3 min-h-16 bg-green-600 text-white ...@@ -452,12 +543,12 @@ class="inline-flex items-center gap-2 px-8 py-3 min-h-16 bg-green-600 text-white
<div class="flex items-center justify-center gap-4 mt-6"> <div class="flex items-center justify-center gap-4 mt-6">
@if($participant_uuid) @if($participant_uuid)
<a href="{{ route('participants.show', $participant_uuid) }}" wire:navigate <a href="{{ route('participants.show', $participant_uuid) }}" wire:navigate
class="inline-flex items-center gap-2 px-6 py-3 min-h-16 border border-indigo-300 text-indigo-700 rounded-lg hover:bg-indigo-50 text-base font-medium"> class="inline-flex items-center gap-2 px-6 py-3 min-h-14 border border-indigo-300 text-indigo-700 rounded-lg hover:bg-indigo-50 text-base font-medium">
{{ __('عرض المشترك') }} {{ __('عرض المشترك') }}
</a> </a>
@endif @endif
<a href="{{ route('receptionist.retroactive-enrollment') }}" wire:navigate <a href="{{ route('receptionist.retroactive-enrollment') }}" wire:navigate
class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 text-base font-medium"> class="inline-flex items-center gap-2 px-6 py-3 min-h-14 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 text-base font-medium">
{{ __('تسجيل آخر') }} {{ __('تسجيل آخر') }}
</a> </a>
</div> </div>
......
...@@ -253,6 +253,8 @@ ...@@ -253,6 +253,8 @@
->middleware('permission:facilities.update'); ->middleware('permission:facilities.update');
Route::get('/expenses/rent/create', \App\Livewire\Financial\FacilityRentForm::class)->name('expenses.rent.create') Route::get('/expenses/rent/create', \App\Livewire\Financial\FacilityRentForm::class)->name('expenses.rent.create')
->middleware('permission:facilities.update'); ->middleware('permission:facilities.update');
Route::get('/revenue/external', \App\Livewire\Financial\ExternalRevenueForm::class)->name('revenue.external.create')
->middleware('permission:expenses.create');
// Invoices // Invoices
Route::get('/invoices', InvoiceList::class)->name('invoices.list') Route::get('/invoices', InvoiceList::class)->name('invoices.list')
......
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