ADD NextDeliveryDateCalculatorService

This commit is contained in:
E98Developer 2026-04-25 05:54:05 +02:00
parent 9facc6a3a7
commit 1e00304eda
4 changed files with 252 additions and 1 deletions

View File

@ -5,6 +5,7 @@
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 App\Services\NextDeliveryDateCalculatorService;
use Filament\Pages\Page; use Filament\Pages\Page;
class CalendarTest extends Page class CalendarTest extends Page
@ -21,8 +22,16 @@ class CalendarTest extends Page
public ?int $profitCenterId = null; public ?int $profitCenterId = null;
public string $selectedDate = '';
public string $selectedTime = '';
public function mount(): void public function mount(): void
{ {
$now = now();
$this->selectedDate = $now->format('Y-m-d');
$this->selectedTime = $now->format('H:i');
$first = ProfitCenterSupplierSchedule::first(); $first = ProfitCenterSupplierSchedule::first();
if ($first) { if ($first) {
@ -31,6 +40,49 @@ public function mount(): void
} }
} }
public function getSupplier(): ?Supplier
{
if (!$this->supplierId) {
return null;
}
return Supplier::find($this->supplierId);
}
public function getNextDeliveryDate(): ?string
{
if (! $this->supplierId || ! $this->profitCenterId || ! $this->selectedDate || ! $this->selectedTime) {
return null;
}
$dt = \Illuminate\Support\Carbon::parse("{$this->selectedDate} {$this->selectedTime}");
$result = app(NextDeliveryDateCalculatorService::class)->calculate(
supplierId: $this->supplierId,
profitCenterId: $this->profitCenterId,
orderDateTime: $dt,
);
return $result?->translatedFormat('Y. F j., l');
}
public function getNextDeliveryDateRaw(): ?string
{
if (! $this->supplierId || ! $this->profitCenterId || ! $this->selectedDate || ! $this->selectedTime) {
return null;
}
$dt = \Illuminate\Support\Carbon::parse("{$this->selectedDate} {$this->selectedTime}");
$result = app(NextDeliveryDateCalculatorService::class)->calculate(
supplierId: $this->supplierId,
profitCenterId: $this->profitCenterId,
orderDateTime: $dt,
);
return $result?->format('Y-m-d');
}
public function getSuppliers(): array public function getSuppliers(): array
{ {
return Supplier::orderBy('name')->pluck('name', 'id')->toArray(); return Supplier::orderBy('name')->pluck('name', 'id')->toArray();
@ -59,11 +111,28 @@ public function updatedSupplierId(): void
} }
$this->dispatch('calendar-filter-changed', supplierId: $this->supplierId, profitCenterId: $this->profitCenterId); $this->dispatch('calendar-filter-changed', supplierId: $this->supplierId, profitCenterId: $this->profitCenterId);
$this->dispatchNextDeliveryDateChanged();
} }
public function updatedProfitCenterId(): void public function updatedProfitCenterId(): void
{ {
$this->dispatch('calendar-filter-changed', supplierId: $this->supplierId, profitCenterId: $this->profitCenterId); $this->dispatch('calendar-filter-changed', supplierId: $this->supplierId, profitCenterId: $this->profitCenterId);
$this->dispatchNextDeliveryDateChanged();
}
public function updatedSelectedDate(): void
{
$this->dispatchNextDeliveryDateChanged();
}
public function updatedSelectedTime(): void
{
$this->dispatchNextDeliveryDateChanged();
}
protected function dispatchNextDeliveryDateChanged(): void
{
$this->dispatch('next-delivery-date-changed', date: $this->getNextDeliveryDateRaw());
} }
} }

View File

@ -30,6 +30,9 @@ class DeliveryCalendarWidget extends FullCalendarWidget
public ?int $profitCenterId = 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;
/** /**
* Inaktív (nem választható) napok listája. * Inaktív (nem választható) napok listája.
* *
@ -64,6 +67,13 @@ public function onFilterChanged(?int $supplierId, ?int $profitCenterId): void
$this->refreshRecords(); $this->refreshRecords();
} }
#[On('next-delivery-date-changed')]
public function onNextDeliveryDateChanged(?string $date): void
{
$this->nextDeliveryDate = $date;
$this->refreshRecords();
}
protected function loadRecordFromFilter(): void protected function loadRecordFromFilter(): void
{ {
if ($this->record instanceof ProfitCenterSupplierSchedule) { if ($this->record instanceof ProfitCenterSupplierSchedule) {
@ -171,6 +181,23 @@ public function fetchEvents(array $info): array
$current->addDay(); $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'); $this->dispatch('calendar-events-done');
return $events; return $events;
} }

View File

@ -0,0 +1,97 @@
<?php
namespace App\Services;
use App\Models\ProfitCenter;
use App\Models\ProfitCenterSupplierSchedule;
use App\Models\Supplier;
use Illuminate\Support\Carbon;
class NextDeliveryDateCalculatorService extends ServiceBase
{
public function __construct(
protected DeliveryCalendarService $deliveryCalendarService,
protected WorkCalendarService $workCalendarService,
) {
}
/**
* Meghatározza a következő szállítási napot egy adott supplierprofitcenter párosnál,
* a megrendelés leadásának dátuma és időpontja alapján.
*
* Ha a suppliernek van szállítási korlátozása (hasDeliveryConstraint), figyelembe veszi:
* - orderCutOffTime: leadási határidő (óra), ha előtte adjuk le, a nap beleszámít az átfutásba
* - deliveryLeadTime: szállítási átfutási idő (óra munkanapban számítva)
*
* @param int $supplierId
* @param int $profitCenterId
* @param Carbon $orderDateTime A leadás dátuma és időpontja
* @return Carbon|null A következő lehetséges szállítási nap
*/
public function calculate(
int $supplierId,
int $profitCenterId,
Carbon $orderDateTime
): ?Carbon {
$assignment = ProfitCenterSupplierSchedule::where('supplier_id', $supplierId)
->where('profit_center_id', $profitCenterId)
->with('deliverySchedule')
->first();
if (! $assignment?->deliverySchedule) {
return null;
}
$supplier = Supplier::find($supplierId);
$pc = ProfitCenter::find($profitCenterId);
if (! $supplier || ! $pc) {
return null;
}
$leadDays = 0;
$startDay = $orderDateTime->copy()->startOfDay();
if ($supplier->hasDeliveryConstraint) {
$cutOffHour = (int) $supplier->orderCutOffTime;
$leadDays = (int) ($supplier->deliveryLeadTime / 24);
// Ha a leadási határidő UTÁN vagyunk, holnap az első számítandó munkanap
if ($orderDateTime->hour >= $cutOffHour) {
$startDay->addDay();
}
}
return $this->findNextDeliveryDay($startDay, $supplier, $pc, $leadDays);
}
/**
* Munkanapokban számol előre ($leadDays darabot), majd az első olyan napot adja vissza,
* amelyik szállítási nap is.
*/
private function findNextDeliveryDay(
Carbon $candidate,
Supplier $supplier,
ProfitCenter $pc,
int $leadDays,
): ?Carbon {
$workingDaysCounted = 0;
// Max. 365 nap iteráció (végtelen ciklus-védelem)
for ($i = 0; $i < 365; $i++) {
if ($this->workCalendarService->isWorkDay($candidate)) {
if ($workingDaysCounted >= $leadDays) {
// Átfutási idő letelt az első szállítási napot keressük
if ($this->deliveryCalendarService->isDeliveryDay($pc, $supplier, $candidate)) {
return $candidate->copy();
}
} else {
$workingDaysCounted++;
}
}
$candidate->addDay();
}
return null;
}
}

View File

@ -33,6 +33,62 @@ class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-
</div> </div>
</div> </div>
{{-- Szállítási korlátozások --}}
<div class="mb-4 rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
<h3 class="mb-3 text-sm font-semibold text-gray-700 dark:text-gray-300">Szállítási korlátozások</h3>
<div class="flex flex-wrap items-center gap-6 text-sm">
@if ($supplierId && ($supplier = $this->getSupplier()))
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400">Szállítási korlátozás:</span>
@if ($supplier->hasDeliveryConstraint)
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800 dark:bg-green-900 dark:text-green-200">Igen</span>
@else
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-300">Nem</span>
@endif
</div>
@if ($supplier->hasDeliveryConstraint)
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400">Leadási határidő:</span>
<span class="font-semibold text-gray-800 dark:text-gray-200">{{ $supplier->orderCutOffTime }}:00</span>
</div>
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400">Szállítási átfutási idő:</span>
<span class="font-semibold text-gray-800 dark:text-gray-200">{{ $supplier->deliveryLeadTime }} óra</span>
</div>
@endif
@endif
<div class="flex items-center gap-2">
<label class="text-gray-500 dark:text-gray-400" for="selectedDate">Dátum:</label>
<input
type="date"
id="selectedDate"
wire:model.live="selectedDate"
class="rounded-lg border border-gray-300 bg-white px-2 py-1 text-sm text-gray-900 shadow-sm focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
>
</div>
<div class="flex items-center gap-2">
<label class="text-gray-500 dark:text-gray-400" for="selectedTime">Időpont:</label>
<input
type="time"
id="selectedTime"
wire:model.live="selectedTime"
class="rounded-lg border border-gray-300 bg-white px-2 py-1 text-sm text-gray-900 shadow-sm focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
>
</div>
@php($nextDelivery = $this->getNextDeliveryDate())
@if ($nextDelivery)
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400">Következő szállítási nap:</span>
<span class="font-semibold text-green-700 dark:text-green-400">{{ $nextDelivery }}</span>
</div>
@elseif ($supplierId && $profitCenterId)
<div class="flex items-center gap-2">
<span class="text-xs text-red-500">Nem található következő szállítási nap.</span>
</div>
@endif
</div>
</div>
{{-- Jelmagyarázat --}} {{-- Jelmagyarázat --}}
<div class="mb-4 flex flex-wrap items-center gap-4 text-sm"> <div class="mb-4 flex flex-wrap items-center gap-4 text-sm">
<span class="font-semibold text-gray-700 dark:text-gray-300">Jelmagyarázat:</span> <span class="font-semibold text-gray-700 dark:text-gray-300">Jelmagyarázat:</span>
@ -50,7 +106,7 @@ class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-
</span> </span>
<span class="inline-flex items-center gap-1.5"> <span class="inline-flex items-center gap-1.5">
<span class="inline-block w-4 h-4 rounded" style="background-color: #3B82F6;"></span> <span class="inline-block w-4 h-4 rounded" style="background-color: #3B82F6;"></span>
Megrendelési határidő Következő szállítási nap
</span> </span>
<span class="inline-flex items-center gap-1.5"> <span class="inline-flex items-center gap-1.5">
<span class="inline-block w-4 h-4 rounded" style="background-color: #E5E7EB;"></span> <span class="inline-block w-4 h-4 rounded" style="background-color: #E5E7EB;"></span>
@ -79,9 +135,11 @@ class="absolute inset-0 z-10 flex items-center justify-center rounded-xl bg-whit
</div> </div>
</div> </div>
@php($nextDeliveryRaw = $this->getNextDeliveryDateRaw())
@livewire(\App\Filament\Widgets\DeliveryCalendarWidget::class, [ @livewire(\App\Filament\Widgets\DeliveryCalendarWidget::class, [
'supplierId' => $supplierId, 'supplierId' => $supplierId,
'profitCenterId' => $profitCenterId, 'profitCenterId' => $profitCenterId,
'nextDeliveryDate' => $nextDeliveryRaw,
]) ])
</div> </div>