ADD NextDeliveryDateCalculatorService
This commit is contained in:
parent
9facc6a3a7
commit
1e00304eda
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
97
app/Services/NextDeliveryDateCalculatorService.php
Normal file
97
app/Services/NextDeliveryDateCalculatorService.php
Normal 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 supplier–profitcenter 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user