Commit 71a6c06f authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add financial overview dashboard, enforce setup wizard, fix permission system

Financial Overview:
- New FinancialOverview Livewire component with P&L, break-even gauge,
  facility costs breakdown, revenue sources, collection rate
- Migration adds monthly_rental_cost to facilities table
- Monthly cost field added to facility form
- Sidebar entry under المالية section

Setup Wizard Enforcement:
- Activities are now mandatory (min 1)
- Programs are now mandatory with price > 0
- Facilities are now mandatory with monthly cost field
- Auto-creates BasePrice records for programs during setup
- Facility form includes operating hours and monthly cost

Permission System Fix:
- PermissionSeeder now called from DatabaseSeeder (has correct permission
  names matching route middleware: pos.sell, attendance.mark, etc.)
- Branch manager gets all relevant module prefixes (inventory, pricing,
  wallets, activities, audit, notifications)
- Receptionist gets pos.sell, pos.list, cash_sessions.manage
- Accountant gets reports.view, wallets.view, pricing.list
- PermissionService falls back to roles() pivot when primaryRole is null
- SetCurrentAcademy middleware eager-loads roles.permissions as fallback
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 2fc16d13
......@@ -39,6 +39,7 @@ class Facility extends Model
'operating_start',
'operating_end',
'rental_cost_per_hour',
'monthly_rental_cost',
'address',
'latitude',
'longitude',
......@@ -62,6 +63,7 @@ protected function casts(): array
'has_lighting' => 'boolean',
'has_ac' => 'boolean',
'rental_cost_per_hour' => 'integer',
'monthly_rental_cost' => 'integer',
'amenities' => 'array',
'metadata' => 'array',
'sort_order' => 'integer',
......
......@@ -20,7 +20,14 @@ public function can(User $user, string $permission, ?Model $record = null): bool
// Get user's role (primary role via role_id for performance)
$role = $user->primaryRole;
if (!$role) {
return false;
// Fallback: check many-to-many roles
$role = $user->roles->first();
if (!$role) {
return false;
}
if (!$role->relationLoaded('permissions')) {
$role->load('permissions');
}
}
// Check if role has this permission
......@@ -56,7 +63,13 @@ public function applyScope(Builder $query, User $user, string $permission): Buil
$role = $user->primaryRole;
if (!$role) {
return $query->whereRaw('1 = 0'); // no results
$role = $user->roles->first();
if (!$role) {
return $query->whereRaw('1 = 0');
}
if (!$role->relationLoaded('permissions')) {
$role->load('permissions');
}
}
$rolePermission = $role->permissions->firstWhere('name', $permission);
......
......@@ -21,6 +21,8 @@ public function handle(Request $request, Closure $next): Response
// Eager-load role with permissions for permission checks
if ($user->role_id && !$user->relationLoaded('primaryRole')) {
$user->load('primaryRole.permissions');
} elseif (!$user->role_id && !$user->relationLoaded('roles')) {
$user->load('roles.permissions');
}
}
......
......@@ -37,6 +37,7 @@ class FacilityForm extends Component
public string $operating_start = '';
public string $operating_end = '';
public ?string $rental_cost_display = null;
public ?string $monthly_rental_cost_display = null;
public string $address = '';
public ?string $latitude = null;
public ?string $longitude = null;
......@@ -68,6 +69,7 @@ public function mount(?Facility $facility = null): void
$this->operating_start = $facility->operating_start ?? '';
$this->operating_end = $facility->operating_end ?? '';
$this->rental_cost_display = $facility->rental_cost_per_hour ? (string) ($facility->rental_cost_per_hour / 100) : null;
$this->monthly_rental_cost_display = $facility->monthly_rental_cost ? (string) ($facility->monthly_rental_cost / 100) : null;
$this->address = $facility->address ?? '';
$this->latitude = $facility->latitude;
$this->longitude = $facility->longitude;
......@@ -97,6 +99,7 @@ public function rules(): array
'operating_start' => 'nullable|date_format:H:i',
'operating_end' => 'nullable|date_format:H:i|after:operating_start',
'rental_cost_display' => 'nullable|numeric|min:0',
'monthly_rental_cost_display' => 'nullable|numeric|min:0',
'address' => 'nullable|string|max:255',
'latitude' => 'nullable|numeric|between:-90,90',
'longitude' => 'nullable|numeric|between:-180,180',
......@@ -123,8 +126,10 @@ public function messages(): array
'operating_start.date_format' => 'وقت البداية يجب أن يكون بصيغة ساعة:دقيقة',
'operating_end.date_format' => 'وقت النهاية يجب أن يكون بصيغة ساعة:دقيقة',
'operating_end.after' => 'وقت النهاية يجب أن يكون بعد وقت البداية',
'rental_cost_display.numeric' => 'تكلفة الإيجار يجب أن تكون رقم',
'rental_cost_display.min' => 'تكلفة الإيجار لا يمكن أن تكون سالبة',
'rental_cost_display.numeric' => 'تكلفة الإيجار بالساعة يجب أن تكون رقم',
'rental_cost_display.min' => 'تكلفة الإيجار بالساعة لا يمكن أن تكون سالبة',
'monthly_rental_cost_display.numeric' => 'تكلفة الإيجار الشهري يجب أن تكون رقم',
'monthly_rental_cost_display.min' => 'تكلفة الإيجار الشهري لا يمكن أن تكون سالبة',
'branch_id.required' => 'الفرع مطلوب',
'branch_id.exists' => 'الفرع غير موجود',
'sort_order.integer' => 'الترتيب يجب أن يكون رقم صحيح',
......@@ -158,6 +163,7 @@ public function save(FacilityService $service): void
'operating_start' => $this->operating_start ?: null,
'operating_end' => $this->operating_end ?: null,
'rental_cost_per_hour' => $this->rental_cost_display ? (int) round((float) $this->rental_cost_display * 100) : null,
'monthly_rental_cost' => $this->monthly_rental_cost_display ? (int) round((float) $this->monthly_rental_cost_display * 100) : null,
'address' => $this->address ?: null,
'latitude' => $this->latitude,
'longitude' => $this->longitude,
......
This diff is collapsed.
......@@ -11,6 +11,7 @@
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Facility\Models\Facility;
use App\Domain\Pricing\Models\BasePrice;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
......@@ -178,6 +179,9 @@ public function addFacility(): void
'name_ar' => '',
'type' => 'field',
'capacity' => '',
'monthly_cost' => '',
'operating_start' => '08:00',
'operating_end' => '22:00',
'address' => '',
];
}
......@@ -260,9 +264,10 @@ protected function validateBranches(): void
protected function validateActivities(): void
{
// Activities are optional, but if added, must be valid
if (empty($this->activities)) {
return;
throw \Illuminate\Validation\ValidationException::withMessages([
'activities' => __('يجب إضافة نشاط واحد على الأقل حتى يعمل النظام'),
]);
}
$validCategories = implode(',', array_column(ActivityCategory::cases(), 'value'));
......@@ -281,7 +286,9 @@ protected function validateActivities(): void
protected function validatePrograms(): void
{
if (empty($this->programs)) {
return;
throw \Illuminate\Validation\ValidationException::withMessages([
'programs' => __('يجب إضافة برنامج واحد على الأقل مع تحديد السعر'),
]);
}
$this->validate([
......@@ -289,19 +296,22 @@ protected function validatePrograms(): void
'programs.*.activity_index' => 'required|integer|min:0',
'programs.*.duration_months' => 'required|integer|min:1|max:24',
'programs.*.max_participants' => 'required|integer|min:1|max:500',
'programs.*.monthly_fee' => 'required|numeric|min:0',
'programs.*.monthly_fee' => 'required|numeric|min:1',
], [
'programs.*.name_ar.required' => __('اسم البرنامج بالعربية مطلوب'),
'programs.*.duration_months.required' => __('مدة البرنامج مطلوبة'),
'programs.*.max_participants.required' => __('الحد الأقصى للمشتركين مطلوب'),
'programs.*.monthly_fee.required' => __('الرسوم الشهرية مطلوبة'),
'programs.*.monthly_fee.min' => __('يجب تحديد سعر البرنامج (لا يمكن أن يكون صفر)'),
]);
}
protected function validateFacilities(): void
{
if (empty($this->facilities)) {
return;
throw \Illuminate\Validation\ValidationException::withMessages([
'facilities' => __('يجب إضافة منشأة واحدة على الأقل (ملعب، قاعة، حمام سباحة...)'),
]);
}
$validTypes = implode(',', array_column(FacilityType::cases(), 'value'));
......@@ -310,10 +320,14 @@ protected function validateFacilities(): void
'facilities.*.name_ar' => 'required|string|min:2|max:255',
'facilities.*.type' => "required|string|in:{$validTypes}",
'facilities.*.capacity' => 'nullable|integer|min:1|max:10000',
'facilities.*.monthly_cost' => 'required|numeric|min:0',
'facilities.*.operating_start' => 'nullable|date_format:H:i',
'facilities.*.operating_end' => 'nullable|date_format:H:i',
'facilities.*.address' => 'nullable|string|max:500',
], [
'facilities.*.name_ar.required' => __('اسم المنشأة بالعربية مطلوب'),
'facilities.*.type.required' => __('نوع المنشأة مطلوب'),
'facilities.*.monthly_cost.required' => __('الإيجار الشهري مطلوب (أدخل 0 إذا كانت ملك)'),
]);
}
......@@ -382,14 +396,16 @@ public function completeSetup(): void
]);
}
// Create programs
// Create programs + base prices
foreach ($this->programs as $programData) {
$activityIndex = (int) $programData['activity_index'];
$activity = $createdActivities[$activityIndex] ?? null;
if ($activity) {
$durationMonths = (int) $programData['duration_months'];
TrainingProgram::create([
$monthlyFeePiasters = (int) round((float) $programData['monthly_fee'] * 100);
$program = TrainingProgram::create([
'academy_id' => $academyId,
'activity_id' => $activity->id,
'name_ar' => $programData['name_ar'],
......@@ -400,7 +416,23 @@ public function completeSetup(): void
'status' => 'active',
'registration_open' => true,
'created_by' => $actor->id,
'metadata' => ['monthly_fee_piasters' => (int) round((float) $programData['monthly_fee'] * 100)],
'metadata' => ['monthly_fee_piasters' => $monthlyFeePiasters],
]);
BasePrice::create([
'academy_id' => $academyId,
'priceable_type' => TrainingProgram::class,
'priceable_id' => $program->id,
'branch_id' => null,
'name_ar' => 'الاشتراك الشهري — ' . $programData['name_ar'],
'name' => 'Monthly — ' . $programData['name_ar'],
'amount' => $monthlyFeePiasters,
'currency' => 'EGP',
'effective_from' => now()->toDateString(),
'effective_to' => null,
'is_active' => true,
'priority' => 1,
'created_by' => $actor->id,
]);
}
}
......@@ -414,8 +446,12 @@ public function completeSetup(): void
'name' => $facilityData['name_ar'],
'type' => $facilityData['type'],
'capacity' => !empty($facilityData['capacity']) ? (int) $facilityData['capacity'] : null,
'monthly_rental_cost' => !empty($facilityData['monthly_cost']) ? (int) round((float) $facilityData['monthly_cost'] * 100) : 0,
'operating_start' => $facilityData['operating_start'] ?? null,
'operating_end' => $facilityData['operating_end'] ?? null,
'address' => $facilityData['address'] ?? null,
'status' => 'active',
'created_by' => $actor->id,
]);
}
......
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('facilities', function (Blueprint $table) {
$table->bigInteger('monthly_rental_cost')->nullable()->after('rental_cost_per_hour');
});
}
public function down(): void
{
Schema::table('facilities', function (Blueprint $table) {
$table->dropColumn('monthly_rental_cost');
});
}
};
......@@ -40,6 +40,7 @@ public function run(): void
$this->call(FinancialAccountsSeeder::class);
$this->call(RolesAndPermissionsSeeder::class);
$this->call(PermissionSeeder::class);
// Assign academy_owner role to the admin user
$ownerRole = Role::where('academy_id', $academy->id)
......
......@@ -190,6 +190,7 @@ private function getRolePermissions(array $allPermissions): array
'branch_manager' => array_filter($allPermissions, fn ($p) =>
str_starts_with($p, 'participants.') ||
str_starts_with($p, 'guardians.') ||
str_starts_with($p, 'activities.') ||
str_starts_with($p, 'programs.') ||
str_starts_with($p, 'groups.') ||
str_starts_with($p, 'sessions.') ||
......@@ -199,14 +200,22 @@ private function getRolePermissions(array $allPermissions): array
str_starts_with($p, 'excuses.') ||
str_starts_with($p, 'invoices.') ||
str_starts_with($p, 'payments.') ||
str_starts_with($p, 'wallets.') ||
str_starts_with($p, 'cash_sessions.') ||
str_starts_with($p, 'pricing.') ||
str_starts_with($p, 'pos.') ||
str_starts_with($p, 'inventory.') ||
str_starts_with($p, 'products.') ||
str_starts_with($p, 'warehouses.') ||
str_starts_with($p, 'facilities.') ||
str_starts_with($p, 'reservations.') ||
str_starts_with($p, 'assignments.') ||
str_starts_with($p, 'evaluations.') ||
str_starts_with($p, 'notifications.') ||
str_starts_with($p, 'reports.') ||
$p === 'dashboard.view' || $p === 'holidays.list'
str_starts_with($p, 'audit.') ||
$p === 'dashboard.view' || $p === 'holidays.list' ||
$p === 'settings.view'
),
'head_trainer' => [
'dashboard.view',
......@@ -233,12 +242,12 @@ private function getRolePermissions(array $allPermissions): array
'participants.list', 'participants.create', 'participants.update', 'participants.show',
'guardians.list', 'guardians.create', 'guardians.update',
'enrollments.list', 'enrollments.create',
'pos.access',
'pos.access', 'pos.sell', 'pos.list',
'invoices.list', 'invoices.show', 'invoices.create',
'payments.list', 'payments.create',
'cash_sessions.open', 'cash_sessions.close', 'cash_sessions.list',
'cash_sessions.open', 'cash_sessions.close', 'cash_sessions.list', 'cash_sessions.manage',
'pos_sessions.open', 'pos_sessions.close', 'pos_sessions.list',
'wallets.list', 'wallets.credit',
'wallets.list', 'wallets.view', 'wallets.credit',
],
'accountant' => [
'dashboard.view',
......@@ -247,12 +256,13 @@ private function getRolePermissions(array $allPermissions): array
'payments.list', 'payments.create', 'payments.refund', 'payments.export',
'transactions.list', 'transactions.export',
'accounts.list', 'accounts.create', 'accounts.update',
'wallets.list', 'wallets.credit', 'wallets.debit', 'wallets.freeze',
'wallets.list', 'wallets.view', 'wallets.credit', 'wallets.debit', 'wallets.freeze',
'payment_plans.list', 'payment_plans.create', 'payment_plans.update',
'cash_sessions.open', 'cash_sessions.close', 'cash_sessions.list',
'cash_sessions.open', 'cash_sessions.close', 'cash_sessions.list', 'cash_sessions.manage',
'refunds.initiate', 'refunds.approve',
'daily_closing.create', 'daily_closing.view',
'reports.financial', 'reports.export_pdf', 'reports.export_excel',
'reports.financial', 'reports.view', 'reports.export_pdf', 'reports.export_excel',
'pricing.list',
],
'data_entry' => [
'dashboard.view',
......
......@@ -22,6 +22,7 @@
]],
['section' => 'المالية', 'items' => [
['label' => 'النظرة المالية', 'route' => 'financial.overview', 'icon' => 'chart-bar', 'permission' => 'invoices.list'],
['label' => 'الفواتير', 'route' => 'invoices.list', 'icon' => 'document', 'permission' => 'invoices.list'],
['label' => 'المحافظ', 'route' => 'wallets.list', 'icon' => 'wallet', 'permission' => 'wallets.list'],
['label' => 'جلسات الكاشير', 'route' => 'cash-sessions.list', 'icon' => 'calculator', 'permission' => 'cash_sessions.list'],
......
......@@ -138,13 +138,22 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin
@error('width_m') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Rental Cost --}}
{{-- Rental Cost per Hour --}}
<div>
<label for="rental_cost_display" class="block text-sm font-medium text-gray-700 mb-1">{{ __('تكلفة الإيجار/ساعة (ج.م)') }}</label>
<input type="number" id="rental_cost_display" wire:model="rental_cost_display" min="0" step="0.01" dir="ltr"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 @error('rental_cost_display') border-red-500 @enderror">
@error('rental_cost_display') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Monthly Rental Cost --}}
<div>
<label for="monthly_rental_cost_display" class="block text-sm font-medium text-gray-700 mb-1">{{ __('الإيجار الشهري (ج.م)') }}</label>
<input type="number" id="monthly_rental_cost_display" wire:model="monthly_rental_cost_display" min="0" step="0.01" dir="ltr"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 @error('monthly_rental_cost_display') border-red-500 @enderror"
placeholder="{{ __('المبلغ الذي تدفعه شهرياً لاستئجار هذه المنشأة') }}">
@error('monthly_rental_cost_display') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
</div>
{{-- Boolean Options --}}
......
......@@ -189,9 +189,13 @@ class="mt-4 w-full border-2 border-dashed border-gray-300 rounded-xl p-4 text-gr
<div class="p-8">
<div class="mb-6">
<h2 class="text-xl font-bold text-gray-800">{{ __('الأنشطة') }}</h2>
<p class="text-sm text-gray-500 mt-1">{{ __('أضف الأنشطة الرياضية أو التعليمية التي تقدمها الأكاديمية. يمكنك تخطي هذه الخطوة وإضافتها لاحقاً.') }}</p>
<p class="text-sm text-gray-500 mt-1">{{ __('أضف الأنشطة الرياضية أو التعليمية التي تقدمها الأكاديمية (مطلوب نشاط واحد على الأقل).') }}</p>
</div>
@error('activities')
<div class="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">{{ $message }}</div>
@enderror
<div class="space-y-4">
@foreach($activities as $index => $activity)
<div class="border border-gray-200 rounded-xl p-5 relative group hover:border-blue-200 transition-colors"
......@@ -274,9 +278,13 @@ class="mt-4 w-full border-2 border-dashed border-gray-300 rounded-xl p-4 text-gr
<div class="p-8">
<div class="mb-6">
<h2 class="text-xl font-bold text-gray-800">{{ __('البرامج التدريبية') }}</h2>
<p class="text-sm text-gray-500 mt-1">{{ __('أضف البرامج المرتبطة بالأنشطة التي أضفتها. يمكنك تخطي هذه الخطوة.') }}</p>
<p class="text-sm text-gray-500 mt-1">{{ __('أضف البرامج التدريبية مع أسعارها. مطلوب برنامج واحد على الأقل مع تحديد الرسوم الشهرية.') }}</p>
</div>
@error('programs')
<div class="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">{{ $message }}</div>
@enderror
@if(empty($activities))
<div class="text-center py-8 border-2 border-dashed border-gray-200 rounded-xl">
<svg class="w-12 h-12 text-amber-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
......@@ -382,9 +390,16 @@ class="mt-4 w-full border-2 border-dashed border-gray-300 rounded-xl p-4 text-gr
<div class="p-8">
<div class="mb-6">
<h2 class="text-xl font-bold text-gray-800">{{ __('المنشآت') }}</h2>
<p class="text-sm text-gray-500 mt-1">{{ __('أضف الملاعب والمنشآت الرياضية الخاصة بالأكاديمية. يمكنك تخطي هذه الخطوة.') }}</p>
<p class="text-sm text-gray-500 mt-1">{{ __('أضف الملاعب والمنشآت الرياضية. حدد الإيجار الشهري وساعات العمل لحساب نقطة التعادل.') }}</p>
<div class="mt-2 p-3 bg-amber-50 border border-amber-200 rounded-lg text-xs text-amber-700">
<strong>{{ __('مهم:') }}</strong> {{ __('الإيجار الشهري يستخدم في حساب الأرباح والخسائر. إذا كانت المنشأة ملكك أدخل 0.') }}
</div>
</div>
@error('facilities')
<div class="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">{{ $message }}</div>
@enderror
<div class="space-y-4">
@foreach($facilities as $index => $facility)
<div class="border border-gray-200 rounded-xl p-5 relative group hover:border-blue-200 transition-colors"
......@@ -396,7 +411,7 @@ class="absolute top-3 start-3 w-7 h-7 rounded-full bg-red-50 text-red-500 hover:
</svg>
</button>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
{{ __('اسم المنشأة') }} <span class="text-red-500">*</span>
......@@ -422,6 +437,17 @@ class="w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:r
<p class="mt-1 text-xs text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
{{ __('الإيجار الشهري (ج.م)') }} <span class="text-red-500">*</span>
</label>
<input type="number" wire:model="facilities.{{ $index }}.monthly_cost" dir="ltr"
class="w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
min="0" step="0.01" placeholder="5000.00">
@error("facilities.{$index}.monthly_cost")
<p class="mt-1 text-xs text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
{{ __('السعة') }}
......@@ -430,6 +456,18 @@ class="w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:r
class="w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
min="1" placeholder="50">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
{{ __('ساعات العمل') }}
</label>
<div class="flex items-center gap-2">
<input type="time" wire:model="facilities.{{ $index }}.operating_start" dir="ltr"
class="flex-1 rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-sm">
<span class="text-gray-400"></span>
<input type="time" wire:model="facilities.{{ $index }}.operating_end" dir="ltr"
class="flex-1 rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-sm">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
{{ __('العنوان') }}
......@@ -448,7 +486,8 @@ class="w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:r
<svg class="w-12 h-12 text-gray-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
</svg>
<p class="text-gray-400 text-sm">{{ __('لم تضف منشآت بعد. أضف منشأة أو تخطَّ هذه الخطوة.') }}</p>
<p class="text-red-500 text-sm font-medium">{{ __('يجب إضافة منشأة واحدة على الأقل') }}</p>
<p class="text-gray-400 text-xs mt-1">{{ __('أضف الملعب أو القاعة التي تعمل فيها') }}</p>
</div>
@endif
......
......@@ -19,6 +19,7 @@
use App\Livewire\Evaluations\EvaluationShow;
use App\Livewire\Facilities\FacilityForm;
use App\Livewire\Facilities\FacilityList;
use App\Livewire\Financial\FinancialOverview;
use App\Livewire\Auth\Login;
use App\Livewire\CashSessions\CashSessionList;
use App\Livewire\CashSessions\CashSessionManage;
......@@ -178,6 +179,10 @@
Route::get('/cash-sessions/manage', CashSessionManage::class)->name('cash-sessions.manage')
->middleware('permission:cash_sessions.manage');
// Financial Overview
Route::get('/financial-overview', FinancialOverview::class)->name('financial.overview')
->middleware('permission:invoices.list');
// Invoices
Route::get('/invoices', InvoiceList::class)->name('invoices.list')
->middleware('permission: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