d2d.emegrendeles.hu/app/Filament/Widgets/DeliveryCalendarWidget.php
E98Developer d26ba5e5e6 FIX EV3-466 hétvégi szállítási napok engedélyezése a heti sablonból
A DeliveryCalendarService 4. prioritása a WorkCalendarService::isWorkDay()-t
hívta, ami a naptárban nem szereplő napoknál a hétvégére esett vissza. Így egy
hétvégét is tartalmazó sablon szombat/vasárnap mezőit sosem érte el a
kiértékelés (5. prioritás), és csak egyedi felülbírálással lehetett hétvégi
szállítást engedni.

- WorkCalendarService: új isHoliday(), ami csak a work_calendars ünnepnap
  rekordjaira ad true-t, a naptári hétvégére nem. Az isWorkDay() változatlan.
- DeliveryCalendarService: a 4. prioritás mostantól isHoliday()-t használ, így
  a hétvégéről a heti sablon dönt. Ünnepnap továbbra is tilt.
- NextDeliveryDateCalculatorService: a szállítási nap keresése kikerült az
  isWorkDay() blokkból, különben a hétvége a következő szállítási nap
  számításánál továbbra is kimaradt volna. Az átfutási időbe az új
  countsTowardLeadTime() szerint a sablon hétvégi napjai is beleszámítanak,
  a H-P sablonok viselkedése változatlan.
- DeliveryCalendarWidget: a szürke "inaktív" festés csak a nem szállítási
  hétvégékre megy rá. Mellékesen a szállítási napok ciklusa $date->addDay()-jel
  mutálta a kollekció Carbon objektumait, ez copy()-ra javítva.

7 új teszt, köztük a H-P sablon regresszió-őre.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 15:59:59 +02:00

440 lines
16 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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;
use Livewire\Attributes\On;
class DeliveryCalendarWidget extends FullCalendarWidget
{
/**
* @var ProfitCenterSupplierSchedule|null
*/
public Model | int | string | null $record = null;
public ?int $supplierId = null;
public ?int $profitCenterId = null;
/** Következő szállítási nap dátuma (YYYY-MM-DD formátum), kék kiemeléshez. */
public ?string $nextDeliveryDate = null;
/** Előnézeti szállítási sablon ID (edit oldalon, mentés előtti változás). */
public ?int $previewDeliveryScheduleId = null;
/**
* Inaktív (nem választható) napok listája.
*
* @var array<string>
*/
protected array $inactiveDates = [];
public function mount(): void
{
$this->loadRecordFromFilter();
}
public function updatedSupplierId(): void
{
$this->loadRecordFromFilter();
$this->refreshRecords();
}
public function updatedProfitCenterId(): void
{
$this->loadRecordFromFilter();
$this->refreshRecords();
}
#[On('calendar-filter-changed')]
public function onFilterChanged(?int $supplierId, ?int $profitCenterId): void
{
$this->supplierId = $supplierId;
$this->profitCenterId = $profitCenterId;
$this->record = null;
$this->loadRecordFromFilter();
$this->refreshRecords();
}
#[On('next-delivery-date-changed')]
public function onNextDeliveryDateChanged(?string $date): void
{
$this->nextDeliveryDate = $date;
$this->refreshRecords();
}
#[On('delivery-schedule-changed')]
public function onDeliveryScheduleChanged(?int $deliveryScheduleId): void
{
$this->previewDeliveryScheduleId = $deliveryScheduleId;
$this->refreshRecords();
}
protected function loadRecordFromFilter(): void
{
if ($this->record instanceof ProfitCenterSupplierSchedule) {
return;
}
if ($this->supplierId && $this->profitCenterId) {
$this->record = ProfitCenterSupplierSchedule::where('supplier_id', $this->supplierId)
->where('profit_center_id', $this->profitCenterId)
->first();
return;
}
if (!$this->record) {
$this->record = ProfitCenterSupplierSchedule::first();
}
}
public function fetchEvents(array $info): array
{
if (!($this->record instanceof ProfitCenterSupplierSchedule)) {
$this->loadRecordFromFilter();
}
if (!$this->record) {
$this->dispatch('calendar-events-done');
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
$assignment = $this->record;
if ($this->previewDeliveryScheduleId && $this->previewDeliveryScheduleId !== $this->record->delivery_schedule_id) {
$assignment = clone $this->record;
$assignment->delivery_schedule_id = $this->previewDeliveryScheduleId;
$assignment->setRelation('deliverySchedule', \App\Models\DeliverySchedule::find($this->previewDeliveryScheduleId));
}
$deliveryDates = $service->getAvailableDeliveryDates(
$this->record->profitCenter,
$this->record->supplier,
$start,
$end,
$assignment
);
// A szállítási napok halmaza a hétvége-festéshez (4. pont) is kell.
$deliveryDayLookup = array_flip(
$deliveryDates->map(fn (Carbon $date) => $date->format('Y-m-d'))->all()
);
foreach ($deliveryDates as $date) {
$events[] = [
'id' => 'delivery-' . $date->format('Y-m-d'),
'title' => '',
'start' => $date->format('Y-m-d'),
'end' => $date->copy()->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)
// Csak azok a hétvégi napok, amelyek nem szállítási napok: a heti sablon
// tartalmazhatja a szombatot/vasárnapot is (EV3-466).
$current = $start->copy();
while ($current->lte($end)) {
if ($current->isWeekend() && ! isset($deliveryDayLookup[$current->format('Y-m-d')])) {
$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();
}
// 5. Következő szállítási nap kék háttér
if ($this->nextDeliveryDate) {
$nextDate = Carbon::parse($this->nextDeliveryDate);
if ($nextDate->between($start, $end)) {
$events[] = [
'id' => 'next-delivery-' . $nextDate->format('Y-m-d'),
'title' => '',
'start' => $nextDate->format('Y-m-d'),
'end' => $nextDate->copy()->addDay()->format('Y-m-d'),
'allDay' => true,
'display' => 'background',
'backgroundColor' => '#3B82F6',
'extendedProps' => ['type' => 'next-delivery'],
];
}
}
$this->dispatch('calendar-events-done');
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,
];
}
}