ADD delivery calendar part2

This commit is contained in:
E98Developer 2026-04-20 21:32:05 +02:00
parent 1753871799
commit e31f33d9bf
12 changed files with 705 additions and 47 deletions

View File

@ -0,0 +1,28 @@
<?php
namespace App\Enums;
enum OverrideScope: string
{
case General = 'general'; // Általános adott beszállítóra, minden régióra és profitcenterre
case Region = 'region'; // Régió-specifikus adott régióra vonatkozó felülbírálás
case ProfitCenter = 'profit_center'; // Profitcenter-specifikus csak 1 konkrét profitcenterre
public function label(): string
{
return match($this) {
self::General => 'Á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',
};
}
}

View File

@ -2,10 +2,12 @@
namespace App\Filament\Resources\DeliveryCalendarOverrides\Schemas; namespace App\Filament\Resources\DeliveryCalendarOverrides\Schemas;
use App\Enums\OverrideScope;
use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle; use Filament\Forms\Components\Toggle;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Schema; use Filament\Schemas\Schema;
class DeliveryCalendarOverrideForm class DeliveryCalendarOverrideForm
@ -19,6 +21,26 @@ public static function configure(Schema $schema): Schema
->relationship('supplier', 'name') ->relationship('supplier', 'name')
->searchable() ->searchable()
->required(), ->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') DatePicker::make('date')
->label('Dátum') ->label('Dátum')
->required() ->required()

View File

@ -2,11 +2,15 @@
namespace App\Filament\Resources\DeliveryCalendarOverrides\Tables; namespace App\Filament\Resources\DeliveryCalendarOverrides\Tables;
use App\Enums\OverrideScope;
use Filament\Actions\BulkActionGroup; use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction; use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction; use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction; use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction; 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\Filters\TrashedFilter;
use Filament\Tables\Table; use Filament\Tables\Table;
@ -16,22 +20,42 @@ public static function configure(Table $table): Table
{ {
return $table return $table
->columns([ ->columns([
\Filament\Tables\Columns\TextColumn::make('supplier.name') TextColumn::make('supplier.name')
->label('Beszállító') ->label('Beszállító')
->searchable() ->searchable()
->sortable(), ->sortable(),
\Filament\Tables\Columns\TextColumn::make('date') TextColumn::make('override_scope')
->label('Dátum') ->label('Szint')
->date() ->badge()
->formatStateUsing(fn(OverrideScope $state) => $state->label())
->color(fn(OverrideScope $state) => $state->color())
->sortable(), ->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() ->boolean()
->label('Szállítási nap'), ->label('Szállítási nap'),
\Filament\Tables\Columns\TextColumn::make('description') TextColumn::make('description')
->label('Megjegyzés') ->label('Megjegyzés')
->limit(30), ->limit(30)
->toggleable(isToggledHiddenByDefault: true),
]) ])
->filters([ ->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(), TrashedFilter::make(),
]) ])
->recordActions([ ->recordActions([

View File

@ -0,0 +1,342 @@
<?php
namespace App\Filament\Widgets;
use App\Enums\OverrideScope;
use App\Models\ProfitCenterSupplierSchedule;
use App\Models\DeliveryCalendarOverride;
use App\Models\WorkCalendar;
use App\Enums\WorkDayType;
use App\Services\DeliveryCalendarService;
use App\Services\WorkCalendarService;
use Illuminate\Support\Carbon;
use Saade\FilamentFullCalendar\Widgets\FullCalendarWidget;
use Illuminate\Database\Eloquent\Model;
use Filament\Actions\Action;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\TextInput;
class DeliveryCalendarWidget extends FullCalendarWidget
{
/**
* @var ProfitCenterSupplierSchedule|null
*/
public Model | int | string | null $record = null;
/**
* Inaktív (nem választható) napok listája.
*
* @var array<string>
*/
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<int, array<string, mixed>>
*/
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,
];
}
}

View File

@ -2,6 +2,7 @@
namespace App\Models; namespace App\Models;
use App\Enums\OverrideScope;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -14,10 +15,21 @@ class DeliveryCalendarOverride extends BaseAuditable
protected $casts = [ protected $casts = [
'date' => 'date', 'date' => 'date',
'is_delivery_day' => 'boolean', 'is_delivery_day' => 'boolean',
'override_scope' => OverrideScope::class,
]; ];
public function supplier(): BelongsTo public function supplier(): BelongsTo
{ {
return $this->belongsTo(Supplier::class); return $this->belongsTo(Supplier::class);
} }
public function profitCenter(): BelongsTo
{
return $this->belongsTo(ProfitCenter::class);
}
public function deliverySchedule(): BelongsTo
{
return $this->belongsTo(DeliverySchedule::class);
}
} }

View File

@ -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.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.notifications', \Filament\Livewire\Notifications::class);
\Livewire\Livewire::component('filament.livewire.database-notifications', \Filament\Livewire\DatabaseNotifications::class); \Livewire\Livewire::component('filament.livewire.database-notifications', \Filament\Livewire\DatabaseNotifications::class);

View File

@ -2,11 +2,12 @@
namespace App\Services; namespace App\Services;
use App\Enums\OverrideScope;
use App\Models\DeliveryCalendarOverride; use App\Models\DeliveryCalendarOverride;
use App\Models\ProfitCenter; use App\Models\ProfitCenter;
use App\Models\ProfitCenterSupplierSchedule; use App\Models\ProfitCenterSupplierSchedule;
use App\Models\Supplier; use App\Models\Supplier;
use Carbon\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
class DeliveryCalendarService extends ServiceBase 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. * 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 public function isDeliveryDay(ProfitCenter $pc, Supplier $supplier, Carbon $date): bool
{ {
$date = $date->startOfDay(); $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) $override = DeliveryCalendarOverride::where('supplier_id', $supplier->id)
->whereDate('date', $date) ->whereDate('date', $date)
->where('override_scope', OverrideScope::ProfitCenter->value)
->where('profit_center_id', $pc->id)
->first(); ->first();
if ($override) { if ($override) {
return $override->is_delivery_day; return $override->is_delivery_day;
} }
// 2. Prioritás: Hivatalos ünnepnap (WorkCalendar) // PC-hez rendelt szállítási sablon lekérése (2. és 5. prioritáshoz egyaránt szükséges)
// 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)
$assignment = ProfitCenterSupplierSchedule::where('profit_center_id', $pc->id) $assignment = ProfitCenterSupplierSchedule::where('profit_center_id', $pc->id)
->where('supplier_id', $supplier->id) ->where('supplier_id', $supplier->id)
->with('deliverySchedule') ->with('deliverySchedule')
->first(); ->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) { if (! $assignment || ! $assignment->deliverySchedule || ! $assignment->deliverySchedule->is_active) {
return false; return false;
} }

View File

@ -2,7 +2,9 @@
namespace Database\Factories; namespace Database\Factories;
use App\Enums\OverrideScope;
use App\Models\DeliveryCalendarOverride; use App\Models\DeliveryCalendarOverride;
use App\Models\DeliverySchedule;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
/** /**
@ -19,9 +21,39 @@ public function definition(): array
{ {
return [ return [
'supplier_id' => \App\Models\Supplier::factory(), 'supplier_id' => \App\Models\Supplier::factory(),
'override_scope' => OverrideScope::General,
'delivery_schedule_id' => null,
'profit_center_id' => null,
'date' => $this->faker->date(), 'date' => $this->faker->date(),
'is_delivery_day' => $this->faker->boolean(), 'is_delivery_day' => $this->faker->boolean(),
'description' => $this->faker->sentence(), '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,
]);
}
} }

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('delivery_calendar_overrides', function (Blueprint $table) {
$table->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']);
});
}
};

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('delivery_calendar_overrides', function (Blueprint $table) {
$table->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');
});
}
};

View File

@ -19,6 +19,7 @@
['displayName' => 'Profitcenter ütemezés', 'link' => \App\Filament\Resources\ProfitCenterSupplierSchedules\ProfitCenterSupplierScheduleResource::getUrl()], ['displayName' => 'Profitcenter ütemezés', 'link' => \App\Filament\Resources\ProfitCenterSupplierSchedules\ProfitCenterSupplierScheduleResource::getUrl()],
['displayName' => 'Munkanaptárak', 'link' => \App\Filament\Resources\WorkCalendars\WorkCalendarResource::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 @endphp

View File

@ -1,5 +1,6 @@
<?php <?php
use App\Enums\OverrideScope;
use App\Models\DeliveryCalendarOverride; use App\Models\DeliveryCalendarOverride;
use App\Models\DeliverySchedule; use App\Models\DeliverySchedule;
use App\Models\ProfitCenter; use App\Models\ProfitCenter;
@ -21,16 +22,13 @@
$schedule = DeliverySchedule::factory()->create([ $schedule = DeliverySchedule::factory()->create([
'monday' => true, 'monday' => true,
]); ]);
ProfitCenterSupplierSchedule::factory()->create([ ProfitCenterSupplierSchedule::factory()->create([
'profit_center_id' => $pc->id, 'profit_center_id' => $pc->id,
'supplier_id' => $supplier->id, 'supplier_id' => $supplier->id,
'delivery_schedule_id' => $schedule->id, 'delivery_schedule_id' => $schedule->id,
]); ]);
$service = app(DeliveryCalendarService::class); $service = app(DeliveryCalendarService::class);
$monday = Carbon::parse('2026-04-13'); // Monday $monday = Carbon::parse('2026-04-13'); // Monday
expect($service->isDeliveryDay($pc, $supplier, $monday))->toBeTrue(); expect($service->isDeliveryDay($pc, $supplier, $monday))->toBeTrue();
}); });
@ -41,16 +39,13 @@
'monday' => true, 'monday' => true,
'tuesday' => false, 'tuesday' => false,
]); ]);
ProfitCenterSupplierSchedule::factory()->create([ ProfitCenterSupplierSchedule::factory()->create([
'profit_center_id' => $pc->id, 'profit_center_id' => $pc->id,
'supplier_id' => $supplier->id, 'supplier_id' => $supplier->id,
'delivery_schedule_id' => $schedule->id, 'delivery_schedule_id' => $schedule->id,
]); ]);
$service = app(DeliveryCalendarService::class); $service = app(DeliveryCalendarService::class);
$tuesday = Carbon::parse('2026-04-14'); // Tuesday $tuesday = Carbon::parse('2026-04-14'); // Tuesday
expect($service->isDeliveryDay($pc, $supplier, $tuesday))->toBeFalse(); expect($service->isDeliveryDay($pc, $supplier, $tuesday))->toBeFalse();
}); });
@ -58,84 +53,193 @@
$pc = ProfitCenter::factory()->create(); $pc = ProfitCenter::factory()->create();
$supplier = Supplier::factory()->create(); $supplier = Supplier::factory()->create();
$schedule = DeliverySchedule::factory()->create(['monday' => true]); $schedule = DeliverySchedule::factory()->create(['monday' => true]);
ProfitCenterSupplierSchedule::factory()->create([ ProfitCenterSupplierSchedule::factory()->create([
'profit_center_id' => $pc->id, 'profit_center_id' => $pc->id,
'supplier_id' => $supplier->id, 'supplier_id' => $supplier->id,
'delivery_schedule_id' => $schedule->id, 'delivery_schedule_id' => $schedule->id,
]); ]);
$monday = Carbon::parse('2026-04-13'); // Monday $monday = Carbon::parse('2026-04-13'); // Monday
// Mark this Monday as a holiday // Mark this Monday as a holiday
WorkCalendar::create([ WorkCalendar::create([
'date' => '2026-04-13', 'date' => '2026-04-13',
'type' => WorkDayType::Holiday, 'type' => WorkDayType::Holiday,
'data_source' => DataSource::Manual, 'data_source' => DataSource::Manual,
]); ]);
$service = app(DeliveryCalendarService::class); $service = app(DeliveryCalendarService::class);
expect($service->isDeliveryDay($pc, $supplier, $monday))->toBeFalse(); 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(); $pc = ProfitCenter::factory()->create();
$supplier = Supplier::factory()->create(); $supplier = Supplier::factory()->create();
$schedule = DeliverySchedule::factory()->create(['monday' => true]); $schedule = DeliverySchedule::factory()->create(['monday' => true]);
ProfitCenterSupplierSchedule::factory()->create([ ProfitCenterSupplierSchedule::factory()->create([
'profit_center_id' => $pc->id, 'profit_center_id' => $pc->id,
'supplier_id' => $supplier->id, 'supplier_id' => $supplier->id,
'delivery_schedule_id' => $schedule->id, 'delivery_schedule_id' => $schedule->id,
]); ]);
$monday = Carbon::parse('2026-04-13'); // Monday $monday = Carbon::parse('2026-04-13'); // Monday
// Mark this Monday as a holiday
WorkCalendar::create([ WorkCalendar::create([
'date' => '2026-04-13', 'date' => '2026-04-13',
'type' => WorkDayType::Holiday, 'type' => WorkDayType::Holiday,
'data_source' => DataSource::Manual, 'data_source' => DataSource::Manual,
]); ]);
// Create an override to allow delivery despite the holiday
DeliveryCalendarOverride::create([ DeliveryCalendarOverride::create([
'supplier_id' => $supplier->id, 'supplier_id' => $supplier->id,
'override_scope' => OverrideScope::General->value,
'date' => '2026-04-13', 'date' => '2026-04-13',
'is_delivery_day' => true, 'is_delivery_day' => true,
]); ]);
$service = app(DeliveryCalendarService::class); $service = app(DeliveryCalendarService::class);
expect($service->isDeliveryDay($pc, $supplier, $monday))->toBeTrue(); 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(); $pc = ProfitCenter::factory()->create();
$supplier = Supplier::factory()->create(); $supplier = Supplier::factory()->create();
$schedule = DeliverySchedule::factory()->create(['monday' => true]); $schedule = DeliverySchedule::factory()->create(['monday' => true]);
ProfitCenterSupplierSchedule::factory()->create([ ProfitCenterSupplierSchedule::factory()->create([
'profit_center_id' => $pc->id, 'profit_center_id' => $pc->id,
'supplier_id' => $supplier->id, 'supplier_id' => $supplier->id,
'delivery_schedule_id' => $schedule->id, 'delivery_schedule_id' => $schedule->id,
]); ]);
$monday = Carbon::parse('2026-04-13'); // Monday $monday = Carbon::parse('2026-04-13'); // Monday
// Create an override to block delivery
DeliveryCalendarOverride::create([ DeliveryCalendarOverride::create([
'supplier_id' => $supplier->id, 'supplier_id' => $supplier->id,
'override_scope' => OverrideScope::General->value,
'date' => '2026-04-13', 'date' => '2026-04-13',
'is_delivery_day' => false, 'is_delivery_day' => false,
]); ]);
$service = app(DeliveryCalendarService::class); $service = app(DeliveryCalendarService::class);
expect($service->isDeliveryDay($pc, $supplier, $monday))->toBeFalse(); 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 () { it('calculates available delivery dates in a range', function () {
$pc = ProfitCenter::factory()->create(); $pc = ProfitCenter::factory()->create();
$supplier = Supplier::factory()->create(); $supplier = Supplier::factory()->create();
@ -144,19 +248,15 @@
'wednesday' => true, 'wednesday' => true,
'friday' => true, 'friday' => true,
]); ]);
ProfitCenterSupplierSchedule::factory()->create([ ProfitCenterSupplierSchedule::factory()->create([
'profit_center_id' => $pc->id, 'profit_center_id' => $pc->id,
'supplier_id' => $supplier->id, 'supplier_id' => $supplier->id,
'delivery_schedule_id' => $schedule->id, 'delivery_schedule_id' => $schedule->id,
]); ]);
$start = Carbon::parse('2026-04-13'); // Monday $start = Carbon::parse('2026-04-13'); // Monday
$end = Carbon::parse('2026-04-19'); // Sunday $end = Carbon::parse('2026-04-19'); // Sunday
$service = app(DeliveryCalendarService::class); $service = app(DeliveryCalendarService::class);
$dates = $service->getAvailableDeliveryDates($pc, $supplier, $start, $end); $dates = $service->getAvailableDeliveryDates($pc, $supplier, $start, $end);
// Should be Mon, Wed, Fri // Should be Mon, Wed, Fri
expect($dates)->toHaveCount(3); expect($dates)->toHaveCount(3);
$dateStrings = $dates->map(fn($d) => $d->toDateString()); $dateStrings = $dates->map(fn($d) => $d->toDateString());