From e31f33d9bf6209d5a21719e4b3cf3d9bbdae789b Mon Sep 17 00:00:00 2001 From: E98Developer Date: Mon, 20 Apr 2026 21:32:05 +0200 Subject: [PATCH] ADD delivery calendar part2 --- app/Enums/OverrideScope.php | 28 ++ .../Schemas/DeliveryCalendarOverrideForm.php | 22 ++ .../Tables/DeliveryCalendarOverridesTable.php | 38 +- .../Widgets/DeliveryCalendarWidget.php | 342 ++++++++++++++++++ app/Models/DeliveryCalendarOverride.php | 12 + app/Providers/AppServiceProvider.php | 2 + app/Services/DeliveryCalendarService.php | 51 ++- .../DeliveryCalendarOverrideFactory.php | 32 ++ ...e_to_delivery_calendar_overrides_table.php | 31 ++ ...dule_id_in_delivery_calendar_overrides.php | 31 ++ .../components/speed-button-nav-bar.blade.php | 1 + tests/Feature/DeliveryCalendarServiceTest.php | 162 +++++++-- 12 files changed, 705 insertions(+), 47 deletions(-) create mode 100644 app/Enums/OverrideScope.php create mode 100644 app/Filament/Widgets/DeliveryCalendarWidget.php create mode 100644 database/migrations/2026_04_20_162446_add_scope_to_delivery_calendar_overrides_table.php create mode 100644 database/migrations/2026_04_20_171028_change_region_to_delivery_schedule_id_in_delivery_calendar_overrides.php diff --git a/app/Enums/OverrideScope.php b/app/Enums/OverrideScope.php new file mode 100644 index 0000000..8ac2e78 --- /dev/null +++ b/app/Enums/OverrideScope.php @@ -0,0 +1,28 @@ + 'Általános (minden telephely)', + self::Region => 'Régió-specifikus', + self::ProfitCenter => 'Profitcenter-specifikus', + }; + } + + public function color(): string + { + return match($this) { + self::General => 'gray', + self::Region => 'warning', + self::ProfitCenter => 'info', + }; + } +} diff --git a/app/Filament/Resources/DeliveryCalendarOverrides/Schemas/DeliveryCalendarOverrideForm.php b/app/Filament/Resources/DeliveryCalendarOverrides/Schemas/DeliveryCalendarOverrideForm.php index 5555e41..8ffc373 100644 --- a/app/Filament/Resources/DeliveryCalendarOverrides/Schemas/DeliveryCalendarOverrideForm.php +++ b/app/Filament/Resources/DeliveryCalendarOverrides/Schemas/DeliveryCalendarOverrideForm.php @@ -2,10 +2,12 @@ namespace App\Filament\Resources\DeliveryCalendarOverrides\Schemas; +use App\Enums\OverrideScope; use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\Select; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; +use Filament\Schemas\Components\Utilities\Get; use Filament\Schemas\Schema; class DeliveryCalendarOverrideForm @@ -19,6 +21,26 @@ public static function configure(Schema $schema): Schema ->relationship('supplier', 'name') ->searchable() ->required(), + Select::make('override_scope') + ->label('Felülbírálás szintje') + ->options(collect(OverrideScope::cases())->mapWithKeys( + fn(OverrideScope $scope) => [$scope->value => $scope->label()] + )) + ->default(OverrideScope::General->value) + ->required() + ->live(), + Select::make('delivery_schedule_id') + ->label('Szállítási sablon') + ->relationship('deliverySchedule', 'name') + ->searchable() + ->visible(fn(Get $get) => $get('override_scope') === OverrideScope::Region->value) + ->required(fn(Get $get) => $get('override_scope') === OverrideScope::Region->value), + Select::make('profit_center_id') + ->label('Profitcenter') + ->relationship('profitCenter', 'name') + ->searchable() + ->visible(fn(Get $get) => $get('override_scope') === OverrideScope::ProfitCenter->value) + ->required(fn(Get $get) => $get('override_scope') === OverrideScope::ProfitCenter->value), DatePicker::make('date') ->label('Dátum') ->required() diff --git a/app/Filament/Resources/DeliveryCalendarOverrides/Tables/DeliveryCalendarOverridesTable.php b/app/Filament/Resources/DeliveryCalendarOverrides/Tables/DeliveryCalendarOverridesTable.php index 991121e..b927463 100644 --- a/app/Filament/Resources/DeliveryCalendarOverrides/Tables/DeliveryCalendarOverridesTable.php +++ b/app/Filament/Resources/DeliveryCalendarOverrides/Tables/DeliveryCalendarOverridesTable.php @@ -2,11 +2,15 @@ namespace App\Filament\Resources\DeliveryCalendarOverrides\Tables; +use App\Enums\OverrideScope; use Filament\Actions\BulkActionGroup; use Filament\Actions\DeleteBulkAction; use Filament\Actions\EditAction; use Filament\Actions\ForceDeleteBulkAction; use Filament\Actions\RestoreBulkAction; +use Filament\Tables\Columns\IconColumn; +use Filament\Tables\Columns\TextColumn; +use Filament\Tables\Filters\SelectFilter; use Filament\Tables\Filters\TrashedFilter; use Filament\Tables\Table; @@ -16,22 +20,42 @@ public static function configure(Table $table): Table { return $table ->columns([ - \Filament\Tables\Columns\TextColumn::make('supplier.name') + TextColumn::make('supplier.name') ->label('Beszállító') ->searchable() ->sortable(), - \Filament\Tables\Columns\TextColumn::make('date') - ->label('Dátum') - ->date() + TextColumn::make('override_scope') + ->label('Szint') + ->badge() + ->formatStateUsing(fn(OverrideScope $state) => $state->label()) + ->color(fn(OverrideScope $state) => $state->color()) ->sortable(), - \Filament\Tables\Columns\IconColumn::make('is_delivery_day') + TextColumn::make('deliverySchedule.name') + ->label('Szállítási sablon') + ->placeholder('—') + ->toggleable(), + TextColumn::make('profitCenter.name') + ->label('Profitcenter') + ->placeholder('—') + ->toggleable(), + TextColumn::make('date') + ->label('Dátum') + ->date('Y-m-d') + ->sortable(), + IconColumn::make('is_delivery_day') ->boolean() ->label('Szállítási nap'), - \Filament\Tables\Columns\TextColumn::make('description') + TextColumn::make('description') ->label('Megjegyzés') - ->limit(30), + ->limit(30) + ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ + SelectFilter::make('override_scope') + ->label('Felülbírálás szintje') + ->options(collect(OverrideScope::cases())->mapWithKeys( + fn(OverrideScope $scope) => [$scope->value => $scope->label()] + )), TrashedFilter::make(), ]) ->recordActions([ diff --git a/app/Filament/Widgets/DeliveryCalendarWidget.php b/app/Filament/Widgets/DeliveryCalendarWidget.php new file mode 100644 index 0000000..b8f06dc --- /dev/null +++ b/app/Filament/Widgets/DeliveryCalendarWidget.php @@ -0,0 +1,342 @@ + + */ + protected array $inactiveDates = []; + + public function mount(): void + { + if (!$this->record) { + $this->record = ProfitCenterSupplierSchedule::first(); + } + } + + public function fetchEvents(array $info): array + { + if (!$this->record) { + $this->record = ProfitCenterSupplierSchedule::first(); + } + + if (!$this->record) { + return $this->getDemoEvents($info); + } + + /** @var DeliveryCalendarService $service */ + $service = app(DeliveryCalendarService::class); + /** @var WorkCalendarService $workService */ + $workService = app(WorkCalendarService::class); + + $start = Carbon::parse($info['start']); + $end = Carbon::parse($info['end']); + + $events = []; + + // 1. Szállítási napok – zöld háttér + $deliveryDates = $service->getAvailableDeliveryDates( + $this->record->profitCenter, + $this->record->supplier, + $start, + $end + ); + + foreach ($deliveryDates as $date) { + $events[] = [ + 'id' => 'delivery-' . $date->format('Y-m-d'), + 'title' => '', + 'start' => $date->format('Y-m-d'), + 'end' => $date->addDay()->format('Y-m-d'), + 'allDay' => true, + 'display' => 'background', + 'backgroundColor' => '#4CAF50', + 'extendedProps' => ['type' => 'delivery'], + ]; + } + + // 2. Munkaszüneti napok – piros háttér + $workCalendarEntries = $workService->getCalendarEntries($start, $end); + foreach ($workCalendarEntries as $entry) { + if ($entry->type === WorkDayType::Holiday) { + $events[] = [ + 'id' => 'holiday-' . $entry->date->format('Y-m-d'), + 'title' => '', + 'start' => $entry->date->format('Y-m-d'), + 'end' => $entry->date->copy()->addDay()->format('Y-m-d'), + 'allDay' => true, + 'display' => 'background', + 'backgroundColor' => '#EF4444', + 'extendedProps' => ['type' => 'holiday'], + ]; + } elseif ($entry->type === WorkDayType::WorkingDay) { + // 3. Áthelyezett munkanapok – narancssárga háttér + $events[] = [ + 'id' => 'transferred-' . $entry->date->format('Y-m-d'), + 'title' => '', + 'start' => $entry->date->format('Y-m-d'), + 'end' => $entry->date->copy()->addDay()->format('Y-m-d'), + 'allDay' => true, + 'display' => 'background', + 'backgroundColor' => '#F59E0B', + 'extendedProps' => ['type' => 'transferred'], + ]; + } + } + + // 4. Hétvégék – szürke háttér (inaktív napok) + $current = $start->copy(); + while ($current->lte($end)) { + if ($current->isWeekend()) { + $events[] = [ + 'id' => 'weekend-' . $current->format('Y-m-d'), + 'title' => '', + 'start' => $current->format('Y-m-d'), + 'end' => $current->copy()->addDay()->format('Y-m-d'), + 'allDay' => true, + 'display' => 'background', + 'backgroundColor' => '#E5E7EB', + 'extendedProps' => ['type' => 'inactive'], + ]; + } + $current->addDay(); + } + + return $events; + } + + /** + * Demo események generálása, ha nincs valós adat. + * + * @param array{start: string, end: string} $info + * @return array> + */ + protected function getDemoEvents(array $info): array + { + $start = Carbon::parse($info['start']); + $end = Carbon::parse($info['end']); + $events = []; + + $current = $start->copy(); + while ($current->lte($end)) { + $dayOfWeek = $current->dayOfWeek; + $dayOfMonth = $current->day; + $dateStr = $current->format('Y-m-d'); + $nextDateStr = $current->copy()->addDay()->format('Y-m-d'); + + if ($current->isWeekend()) { + // Hétvégék – szürke háttér (inaktív) + $events[] = [ + 'id' => 'weekend-' . $dateStr, + 'title' => '', + 'start' => $dateStr, + 'end' => $nextDateStr, + 'allDay' => true, + 'display' => 'background', + 'backgroundColor' => '#E5E7EB', + 'extendedProps' => ['type' => 'inactive'], + ]; + } elseif ($dayOfWeek === Carbon::MONDAY || $dayOfWeek === Carbon::WEDNESDAY || $dayOfWeek === Carbon::FRIDAY) { + // Hétfő, szerda, péntek – zöld háttér (szállítási napok) + $events[] = [ + 'id' => 'delivery-' . $dateStr, + 'title' => '', + 'start' => $dateStr, + 'end' => $nextDateStr, + 'allDay' => true, + 'display' => 'background', + 'backgroundColor' => '#4CAF50', + 'extendedProps' => ['type' => 'delivery'], + ]; + } + + // Néhány speciális nap bemutatóként + if ($dayOfMonth === 15) { + // Minden hónap 15-e – narancssárga (áthelyezett munkanap) + $events[] = [ + 'id' => 'special-orange-' . $dateStr, + 'title' => '', + 'start' => $dateStr, + 'end' => $nextDateStr, + 'allDay' => true, + 'display' => 'background', + 'backgroundColor' => '#F59E0B', + 'extendedProps' => ['type' => 'transferred'], + ]; + } + + if ($dayOfMonth === 1) { + // Minden hónap 1-je – piros (ünnepnap) + $events[] = [ + 'id' => 'special-red-' . $dateStr, + 'title' => '', + 'start' => $dateStr, + 'end' => $nextDateStr, + 'allDay' => true, + 'display' => 'background', + 'backgroundColor' => '#EF4444', + 'extendedProps' => ['type' => 'holiday'], + ]; + } + + if ($dayOfMonth === 10 || $dayOfMonth === 20) { + // 10-e és 20-a – kék (megrendelési határidő) + $events[] = [ + 'id' => 'special-blue-' . $dateStr, + 'title' => '', + 'start' => $dateStr, + 'end' => $nextDateStr, + 'allDay' => true, + 'display' => 'background', + 'backgroundColor' => '#3B82F6', + 'extendedProps' => ['type' => 'deadline'], + ]; + } + + $current->addDay(); + } + + return $events; + } + + public function onDateSelect(string $start, ?string $end, bool $allDay, ?array $view, ?array $resource): void + { + $this->mountAction('manageOverride', [ + 'date' => Carbon::parse($start)->format('Y-m-d'), + ]); + } + + public function onEventClick(array $event): void + { + $date = data_get($event, 'start'); + + if (!$date) { + return; + } + + $this->mountAction('manageOverride', [ + 'date' => Carbon::parse($date)->format('Y-m-d'), + ]); + } + + protected function headerActions(): array + { + return [ + Action::make('manageOverride') + ->label('Felülbírálás kezelése') + ->modalHeading(fn (array $arguments) => "Szállítás felülbírálása" . (isset($arguments['date']) ? ": " . $arguments['date'] : "")) + ->form(fn (array $arguments) => [ + Placeholder::make('info') + ->hiddenLabel() + ->content('A háttérben látható naptárban egyszerre több hónapot is áttekinthet. Itt egy konkrét nap módosítását végezheti el.'), + \Filament\Forms\Components\DatePicker::make('date') + ->label('Dátum') + ->required() + ->default($arguments['date'] ?? null) + ->disabled(fn () => isset($arguments['date'])) + ->dehydrated(), + Toggle::make('is_delivery_day') + ->label('Szállítási nap?') + ->default(function () use ($arguments) { + $date = $arguments['date'] ?? null; + if (!$date) { + return true; + } + + $override = DeliveryCalendarOverride::where('supplier_id', $this->record->supplier_id) + ->whereDate('date', $date) + ->where('override_scope', OverrideScope::ProfitCenter->value) + ->where('profit_center_id', $this->record->profit_center_id) + ->first(); + + if ($override) { + return $override->is_delivery_day; + } + + return app(DeliveryCalendarService::class)->isDeliveryDay( + $this->record->profitCenter, + $this->record->supplier, + Carbon::parse($date) + ); + }), + TextInput::make('description') + ->label('Indoklás / Megjegyzés') + ->default(fn () => isset($arguments['date']) + ? DeliveryCalendarOverride::where('supplier_id', $this->record->supplier_id) + ->whereDate('date', $arguments['date']) + ->where('override_scope', OverrideScope::ProfitCenter->value) + ->where('profit_center_id', $this->record->profit_center_id) + ->first()?->description + : null + ), + ]) + ->action(function (array $data) { + DeliveryCalendarOverride::updateOrCreate( + [ + 'supplier_id' => $this->record->supplier_id, + 'override_scope' => OverrideScope::ProfitCenter->value, + 'profit_center_id' => $this->record->profit_center_id, + 'date' => $data['date'], + ], + [ + 'is_delivery_day' => (bool) $data['is_delivery_day'], + 'description' => $data['description'], + ] + ); + + $this->refreshRecords(); + }), + ]; + } + + public function config(): array + { + return [ + 'initialView' => 'multiMonthYear', + 'views' => [ + 'multiMonthYear' => [ + 'duration' => ['months' => 6], + ], + ], + 'multiMonthMaxColumns' => 6, + 'headerToolbar' => [ + 'left' => 'prev,next today', + 'center' => 'title', + 'right' => 'multiMonthYear', + ], + 'locale' => 'hu', + 'firstDay' => 1, + 'height' => 'auto', + 'contentHeight' => 'auto', + 'selectable' => true, + 'editable' => false, + 'dayMaxEvents' => false, + 'fixedWeekCount' => false, + ]; + } +} diff --git a/app/Models/DeliveryCalendarOverride.php b/app/Models/DeliveryCalendarOverride.php index c25a0f7..6ff7d5a 100644 --- a/app/Models/DeliveryCalendarOverride.php +++ b/app/Models/DeliveryCalendarOverride.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Enums\OverrideScope; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; @@ -14,10 +15,21 @@ class DeliveryCalendarOverride extends BaseAuditable protected $casts = [ 'date' => 'date', 'is_delivery_day' => 'boolean', + 'override_scope' => OverrideScope::class, ]; public function supplier(): BelongsTo { return $this->belongsTo(Supplier::class); } + + public function profitCenter(): BelongsTo + { + return $this->belongsTo(ProfitCenter::class); + } + + public function deliverySchedule(): BelongsTo + { + return $this->belongsTo(DeliverySchedule::class); + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 236bf62..a078fce 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -57,6 +57,8 @@ public function boot(): void }); \Livewire\Livewire::component('app.filament.pages.price-list-processor', \App\Filament\Pages\PriceListProcessor::class); + \Livewire\Livewire::component('app.filament.pages.calendar-test', \App\Filament\Pages\CalendarTest::class); + \Livewire\Livewire::component('app.filament.widgets.delivery-calendar-widget', \App\Filament\Widgets\DeliveryCalendarWidget::class); /* \Livewire\Livewire::component('filament.livewire.notifications', \Filament\Livewire\Notifications::class); \Livewire\Livewire::component('filament.livewire.database-notifications', \Filament\Livewire\DatabaseNotifications::class); diff --git a/app/Services/DeliveryCalendarService.php b/app/Services/DeliveryCalendarService.php index 6b9b13c..72cef99 100644 --- a/app/Services/DeliveryCalendarService.php +++ b/app/Services/DeliveryCalendarService.php @@ -2,11 +2,12 @@ namespace App\Services; +use App\Enums\OverrideScope; use App\Models\DeliveryCalendarOverride; use App\Models\ProfitCenter; use App\Models\ProfitCenterSupplierSchedule; use App\Models\Supplier; -use Carbon\Carbon; +use Illuminate\Support\Carbon; use Illuminate\Support\Collection; class DeliveryCalendarService extends ServiceBase @@ -18,32 +19,64 @@ public function __construct( /** * Eldönti, hogy egy adott Profit Center, Beszállító és Dátum kombináció esetén van-e szállítás. + * + * A felülbírálás kiértékelése 3 szinten történik (legspecifikusabbtól az általánosig): + * 1. Profitcenter-specifikus override (csak erre az 1 PC-re) + * 2. Régió-specifikus override (a PC városához/régiójához tartozó) + * 3. Általános override (az összes PC-re vonatkozó) + * Ha egyetlen szinten sem található override, folytatódik a normál kiértékelés. */ public function isDeliveryDay(ProfitCenter $pc, Supplier $supplier, Carbon $date): bool { $date = $date->startOfDay(); - // 1. Prioritás: Beszállítói felülbírálás (Override) + // 1. Prioritás: Profitcenter-specifikus felülbírálás $override = DeliveryCalendarOverride::where('supplier_id', $supplier->id) ->whereDate('date', $date) + ->where('override_scope', OverrideScope::ProfitCenter->value) + ->where('profit_center_id', $pc->id) ->first(); if ($override) { return $override->is_delivery_day; } - // 2. Prioritás: Hivatalos ünnepnap (WorkCalendar) - // Ha aznap nem hivatalos munkanap van, akkor nincs szállítás (kivéve ha az 1. pont felülbírálta). - if (! $this->workCalendarService->isWorkDay($date)) { - return false; - } - - // 3. Prioritás: Heti sablon (Schedule) + // PC-hez rendelt szállítási sablon lekérése (2. és 5. prioritáshoz egyaránt szükséges) $assignment = ProfitCenterSupplierSchedule::where('profit_center_id', $pc->id) ->where('supplier_id', $supplier->id) ->with('deliverySchedule') ->first(); + // 2. Prioritás: DeliverySchedule-specifikus felülbírálás (a PC-hez rendelt szállítási sablon alapján) + if ($assignment && $assignment->delivery_schedule_id) { + $override = DeliveryCalendarOverride::where('supplier_id', $supplier->id) + ->whereDate('date', $date) + ->where('override_scope', OverrideScope::Region->value) + ->where('delivery_schedule_id', $assignment->delivery_schedule_id) + ->first(); + + if ($override) { + return $override->is_delivery_day; + } + } + + // 3. Prioritás: Általános felülbírálás (minden PC-re vonatkozik) + $override = DeliveryCalendarOverride::where('supplier_id', $supplier->id) + ->whereDate('date', $date) + ->where('override_scope', OverrideScope::General->value) + ->first(); + + if ($override) { + return $override->is_delivery_day; + } + + // 4. Prioritás: Hivatalos ünnepnap (WorkCalendar) + // Ha aznap nem hivatalos munkanap van, akkor nincs szállítás (kivéve ha az 1-3. pont felülbírálta). + if (! $this->workCalendarService->isWorkDay($date)) { + return false; + } + + // 5. Prioritás: Heti sablon (Schedule) if (! $assignment || ! $assignment->deliverySchedule || ! $assignment->deliverySchedule->is_active) { return false; } diff --git a/database/factories/DeliveryCalendarOverrideFactory.php b/database/factories/DeliveryCalendarOverrideFactory.php index 771d3a0..16430b1 100644 --- a/database/factories/DeliveryCalendarOverrideFactory.php +++ b/database/factories/DeliveryCalendarOverrideFactory.php @@ -2,7 +2,9 @@ namespace Database\Factories; +use App\Enums\OverrideScope; use App\Models\DeliveryCalendarOverride; +use App\Models\DeliverySchedule; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -19,9 +21,39 @@ public function definition(): array { return [ 'supplier_id' => \App\Models\Supplier::factory(), + 'override_scope' => OverrideScope::General, + 'delivery_schedule_id' => null, + 'profit_center_id' => null, 'date' => $this->faker->date(), 'is_delivery_day' => $this->faker->boolean(), 'description' => $this->faker->sentence(), ]; } + + public function general(): static + { + return $this->state([ + 'override_scope' => OverrideScope::General, + 'delivery_schedule_id' => null, + 'profit_center_id' => null, + ]); + } + + public function forDeliverySchedule(DeliverySchedule $schedule): static + { + return $this->state([ + 'override_scope' => OverrideScope::Region, + 'delivery_schedule_id' => $schedule->id, + 'profit_center_id' => null, + ]); + } + + public function forProfitCenter(\App\Models\ProfitCenter $profitCenter): static + { + return $this->state([ + 'override_scope' => OverrideScope::ProfitCenter, + 'delivery_schedule_id' => null, + 'profit_center_id' => $profitCenter->id, + ]); + } } diff --git a/database/migrations/2026_04_20_162446_add_scope_to_delivery_calendar_overrides_table.php b/database/migrations/2026_04_20_162446_add_scope_to_delivery_calendar_overrides_table.php new file mode 100644 index 0000000..dbc9d6e --- /dev/null +++ b/database/migrations/2026_04_20_162446_add_scope_to_delivery_calendar_overrides_table.php @@ -0,0 +1,31 @@ +string('override_scope')->default('general')->after('supplier_id'); + $table->string('region')->nullable()->after('override_scope'); + $table->foreignId('profit_center_id')->nullable()->constrained()->nullOnDelete()->after('region'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('delivery_calendar_overrides', function (Blueprint $table) { + $table->dropForeign(['profit_center_id']); + $table->dropColumn(['override_scope', 'region', 'profit_center_id']); + }); + } +}; diff --git a/database/migrations/2026_04_20_171028_change_region_to_delivery_schedule_id_in_delivery_calendar_overrides.php b/database/migrations/2026_04_20_171028_change_region_to_delivery_schedule_id_in_delivery_calendar_overrides.php new file mode 100644 index 0000000..8ccd66c --- /dev/null +++ b/database/migrations/2026_04_20_171028_change_region_to_delivery_schedule_id_in_delivery_calendar_overrides.php @@ -0,0 +1,31 @@ +dropColumn('region'); + $table->foreignId('delivery_schedule_id')->nullable()->constrained()->nullOnDelete()->after('override_scope'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('delivery_calendar_overrides', function (Blueprint $table) { + $table->dropForeign(['delivery_schedule_id']); + $table->dropColumn('delivery_schedule_id'); + $table->string('region')->nullable()->after('override_scope'); + }); + } +}; diff --git a/resources/views/filament/components/speed-button-nav-bar.blade.php b/resources/views/filament/components/speed-button-nav-bar.blade.php index 1a71e40..d1492d7 100644 --- a/resources/views/filament/components/speed-button-nav-bar.blade.php +++ b/resources/views/filament/components/speed-button-nav-bar.blade.php @@ -19,6 +19,7 @@ ['displayName' => 'Profitcenter ütemezés', 'link' => \App\Filament\Resources\ProfitCenterSupplierSchedules\ProfitCenterSupplierScheduleResource::getUrl()], ['displayName' => 'Munkanaptárak', 'link' => \App\Filament\Resources\WorkCalendars\WorkCalendarResource::getUrl()], ]], + ['name' => 'calendarTest', 'displayName' => 'Naptár Teszt', 'icon' => 'idopontok', 'link' => \App\Filament\Pages\CalendarTest::getUrl(), 'roles' => ['root','developer','admin']], ]; @endphp diff --git a/tests/Feature/DeliveryCalendarServiceTest.php b/tests/Feature/DeliveryCalendarServiceTest.php index ada67bd..395804a 100644 --- a/tests/Feature/DeliveryCalendarServiceTest.php +++ b/tests/Feature/DeliveryCalendarServiceTest.php @@ -1,5 +1,6 @@ create([ 'monday' => true, ]); - ProfitCenterSupplierSchedule::factory()->create([ 'profit_center_id' => $pc->id, 'supplier_id' => $supplier->id, 'delivery_schedule_id' => $schedule->id, ]); - $service = app(DeliveryCalendarService::class); $monday = Carbon::parse('2026-04-13'); // Monday - expect($service->isDeliveryDay($pc, $supplier, $monday))->toBeTrue(); }); @@ -41,16 +39,13 @@ 'monday' => true, 'tuesday' => false, ]); - ProfitCenterSupplierSchedule::factory()->create([ 'profit_center_id' => $pc->id, 'supplier_id' => $supplier->id, 'delivery_schedule_id' => $schedule->id, ]); - $service = app(DeliveryCalendarService::class); $tuesday = Carbon::parse('2026-04-14'); // Tuesday - expect($service->isDeliveryDay($pc, $supplier, $tuesday))->toBeFalse(); }); @@ -58,84 +53,193 @@ $pc = ProfitCenter::factory()->create(); $supplier = Supplier::factory()->create(); $schedule = DeliverySchedule::factory()->create(['monday' => true]); - ProfitCenterSupplierSchedule::factory()->create([ 'profit_center_id' => $pc->id, 'supplier_id' => $supplier->id, 'delivery_schedule_id' => $schedule->id, ]); - $monday = Carbon::parse('2026-04-13'); // Monday - // Mark this Monday as a holiday WorkCalendar::create([ 'date' => '2026-04-13', 'type' => WorkDayType::Holiday, 'data_source' => DataSource::Manual, ]); - $service = app(DeliveryCalendarService::class); - expect($service->isDeliveryDay($pc, $supplier, $monday))->toBeFalse(); }); -it('respects supplier overrides over holidays', function () { +it('respects general supplier override over holidays', function () { $pc = ProfitCenter::factory()->create(); $supplier = Supplier::factory()->create(); $schedule = DeliverySchedule::factory()->create(['monday' => true]); - ProfitCenterSupplierSchedule::factory()->create([ 'profit_center_id' => $pc->id, 'supplier_id' => $supplier->id, 'delivery_schedule_id' => $schedule->id, ]); - $monday = Carbon::parse('2026-04-13'); // Monday - - // Mark this Monday as a holiday WorkCalendar::create([ 'date' => '2026-04-13', 'type' => WorkDayType::Holiday, 'data_source' => DataSource::Manual, ]); - - // Create an override to allow delivery despite the holiday DeliveryCalendarOverride::create([ 'supplier_id' => $supplier->id, + 'override_scope' => OverrideScope::General->value, 'date' => '2026-04-13', 'is_delivery_day' => true, ]); - $service = app(DeliveryCalendarService::class); - expect($service->isDeliveryDay($pc, $supplier, $monday))->toBeTrue(); }); -it('respects supplier overrides to block delivery on a normal day', function () { +it('respects general supplier override to block delivery on a normal day', function () { $pc = ProfitCenter::factory()->create(); $supplier = Supplier::factory()->create(); $schedule = DeliverySchedule::factory()->create(['monday' => true]); - ProfitCenterSupplierSchedule::factory()->create([ 'profit_center_id' => $pc->id, 'supplier_id' => $supplier->id, 'delivery_schedule_id' => $schedule->id, ]); - $monday = Carbon::parse('2026-04-13'); // Monday - - // Create an override to block delivery DeliveryCalendarOverride::create([ 'supplier_id' => $supplier->id, + 'override_scope' => OverrideScope::General->value, 'date' => '2026-04-13', 'is_delivery_day' => false, ]); - $service = app(DeliveryCalendarService::class); - expect($service->isDeliveryDay($pc, $supplier, $monday))->toBeFalse(); }); +it('respects delivery-schedule-specific override over general override', function () { + $pc = ProfitCenter::factory()->create(); + $supplier = Supplier::factory()->create(); + $schedule = DeliverySchedule::factory()->create(['monday' => true]); + ProfitCenterSupplierSchedule::factory()->create([ + 'profit_center_id' => $pc->id, + 'supplier_id' => $supplier->id, + 'delivery_schedule_id' => $schedule->id, + ]); + $monday = Carbon::parse('2026-04-13'); + // Általános tiltás + DeliveryCalendarOverride::create([ + 'supplier_id' => $supplier->id, + 'override_scope' => OverrideScope::General->value, + 'date' => '2026-04-13', + 'is_delivery_day' => false, + ]); + // Szállítási sablon-specifikus engedélyezés + DeliveryCalendarOverride::create([ + 'supplier_id' => $supplier->id, + 'override_scope' => OverrideScope::Region->value, + 'delivery_schedule_id' => $schedule->id, + 'date' => '2026-04-13', + 'is_delivery_day' => true, + ]); + $service = app(DeliveryCalendarService::class); + // A sablon-specifikus győz az általánossal szemben + expect($service->isDeliveryDay($pc, $supplier, $monday))->toBeTrue(); +}); + +it('delivery-schedule override does not affect profit centers with different schedule', function () { + $supplier = Supplier::factory()->create(); + $scheduleA = DeliverySchedule::factory()->create(['monday' => true]); + $scheduleB = DeliverySchedule::factory()->create(['monday' => true]); + $pcA = ProfitCenter::factory()->create(); + $pcB = ProfitCenter::factory()->create(); + ProfitCenterSupplierSchedule::factory()->create([ + 'profit_center_id' => $pcA->id, + 'supplier_id' => $supplier->id, + 'delivery_schedule_id' => $scheduleA->id, + ]); + ProfitCenterSupplierSchedule::factory()->create([ + 'profit_center_id' => $pcB->id, + 'supplier_id' => $supplier->id, + 'delivery_schedule_id' => $scheduleB->id, + ]); + $monday = Carbon::parse('2026-04-13'); + // Csak az A sablont tiltjuk + DeliveryCalendarOverride::create([ + 'supplier_id' => $supplier->id, + 'override_scope' => OverrideScope::Region->value, + 'delivery_schedule_id' => $scheduleA->id, + 'date' => '2026-04-13', + 'is_delivery_day' => false, + ]); + $service = app(DeliveryCalendarService::class); + expect($service->isDeliveryDay($pcA, $supplier, $monday))->toBeFalse(); + expect($service->isDeliveryDay($pcB, $supplier, $monday))->toBeTrue(); +}); + +it('respects profit-center-specific override over region and general overrides', function () { + $pc = ProfitCenter::factory()->create(['city' => 'Budapest']); + $supplier = Supplier::factory()->create(); + $schedule = DeliverySchedule::factory()->create(['monday' => true]); + ProfitCenterSupplierSchedule::factory()->create([ + 'profit_center_id' => $pc->id, + 'supplier_id' => $supplier->id, + 'delivery_schedule_id' => $schedule->id, + ]); + $monday = Carbon::parse('2026-04-13'); + // Általános tiltás + DeliveryCalendarOverride::create([ + 'supplier_id' => $supplier->id, + 'override_scope' => OverrideScope::General->value, + 'date' => '2026-04-13', + 'is_delivery_day' => false, + ]); + // Sablon-specifikus tiltás is + DeliveryCalendarOverride::create([ + 'supplier_id' => $supplier->id, + 'override_scope' => OverrideScope::Region->value, + 'delivery_schedule_id' => $schedule->id, + 'date' => '2026-04-13', + 'is_delivery_day' => false, + ]); + // De erre a konkrét PC-re engedélyezve van + DeliveryCalendarOverride::create([ + 'supplier_id' => $supplier->id, + 'override_scope' => OverrideScope::ProfitCenter->value, + 'profit_center_id' => $pc->id, + 'date' => '2026-04-13', + 'is_delivery_day' => true, + ]); + $service = app(DeliveryCalendarService::class); + expect($service->isDeliveryDay($pc, $supplier, $monday))->toBeTrue(); +}); + +it('profit-center-specific override only affects the targeted profit center', function () { + $pc1 = ProfitCenter::factory()->create(['city' => 'Budapest']); + $pc2 = ProfitCenter::factory()->create(['city' => 'Budapest']); + $supplier = Supplier::factory()->create(); + $schedule = DeliverySchedule::factory()->create(['monday' => true]); + ProfitCenterSupplierSchedule::factory()->create([ + 'profit_center_id' => $pc1->id, + 'supplier_id' => $supplier->id, + 'delivery_schedule_id' => $schedule->id, + ]); + ProfitCenterSupplierSchedule::factory()->create([ + 'profit_center_id' => $pc2->id, + 'supplier_id' => $supplier->id, + 'delivery_schedule_id' => $schedule->id, + ]); + $monday = Carbon::parse('2026-04-13'); + // Csak pc1-re van tiltás + DeliveryCalendarOverride::create([ + 'supplier_id' => $supplier->id, + 'override_scope' => OverrideScope::ProfitCenter->value, + 'profit_center_id' => $pc1->id, + 'date' => '2026-04-13', + 'is_delivery_day' => false, + ]); + $service = app(DeliveryCalendarService::class); + expect($service->isDeliveryDay($pc1, $supplier, $monday))->toBeFalse(); + expect($service->isDeliveryDay($pc2, $supplier, $monday))->toBeTrue(); +}); + it('calculates available delivery dates in a range', function () { $pc = ProfitCenter::factory()->create(); $supplier = Supplier::factory()->create(); @@ -144,19 +248,15 @@ 'wednesday' => true, 'friday' => true, ]); - ProfitCenterSupplierSchedule::factory()->create([ 'profit_center_id' => $pc->id, 'supplier_id' => $supplier->id, 'delivery_schedule_id' => $schedule->id, ]); - $start = Carbon::parse('2026-04-13'); // Monday $end = Carbon::parse('2026-04-19'); // Sunday - $service = app(DeliveryCalendarService::class); $dates = $service->getAvailableDeliveryDates($pc, $supplier, $start, $end); - // Should be Mon, Wed, Fri expect($dates)->toHaveCount(3); $dateStrings = $dates->map(fn($d) => $d->toDateString());